pkce/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! This is a minimal library with functions to generate random code verifiers and challenges to be used for OAuth [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636).
//!
//! ```
//! extern crate pkce;
//!
//! fn main() {
//!     let code_verify = pkce::code_verifier(128);
//!     let code_challenge = pkce::code_challenge(&code_verify);
//!
//!     println!("Code challenge generated: {}", code_challenge);
//! }
//! ```

extern crate base64;
extern crate rand;
extern crate sha2;

use base64::Engine;
use rand::{thread_rng, Rng};
use sha2::{Digest, Sha256};

const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
    abcdefghijklmnopqrstuvwxyz\
    0123456789-.~_";

/// Generate a random code verifier.
///
/// # Arguments
///
/// * `length` - The desired length in bytes of the code verifier. This value should be between 43 and 128 or else the function will panic.
pub fn code_verifier(length: usize) -> Vec<u8> {
    assert!(
        (43..=128).contains(&length),
        "Code verifier length must be between 43 and 128 bytes"
    );

    let mut rng = thread_rng();

    (0..length)
        .map(|_| {
            let i = rng.gen_range(0..CHARS.len());
            CHARS[i]
        })
        .collect()
}

fn base64_url_encode(input: &[u8]) -> String {
    let b64 = base64::engine::general_purpose::STANDARD.encode(input);
    b64.chars()
        .filter_map(|c| match c {
            '=' => None,
            '+' => Some('-'),
            '/' => Some('_'),
            x => Some(x),
        })
        .collect()
}

/// Generate a code challenge from a given code verifier with SHA256 and base64.
///
/// # Arguments
///
/// * `code_verifier` - The code verifier, such as the one generated by the [`code_verifier`] function.
pub fn code_challenge(code_verifier: &[u8]) -> String {
    let mut sha = Sha256::new();
    sha.update(code_verifier);
    let result = sha.finalize();
    base64_url_encode(&result[..])
}