Skip to main content

pas_client/
pkce.rs

1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use rand::RngExt;
4use sha2::{Digest, Sha256};
5
6/// Generates a cryptographically random code verifier for PKCE.
7///
8/// Returns a 64-character URL-safe string (RFC 7636 compliant, 43-128 chars).
9#[must_use]
10pub fn generate_code_verifier() -> String {
11    let random_bytes: [u8; 48] = rand::rng().random();
12    URL_SAFE_NO_PAD.encode(random_bytes)
13}
14
15/// Computes the S256 code challenge from a code verifier.
16///
17/// `challenge = BASE64URL(SHA256(verifier))`
18#[must_use]
19pub fn generate_code_challenge(verifier: &str) -> String {
20    let hash = Sha256::digest(verifier.as_bytes());
21    URL_SAFE_NO_PAD.encode(hash)
22}
23
24/// Generates a cryptographically random state parameter for `OAuth2`.
25///
26/// Returns a 22-character URL-safe string (16 random bytes → base64url).
27#[must_use]
28pub fn generate_state() -> String {
29    let random_bytes: [u8; 16] = rand::rng().random();
30    URL_SAFE_NO_PAD.encode(random_bytes)
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn test_code_verifier_length() {
39        let verifier = generate_code_verifier();
40        assert_eq!(verifier.len(), 64);
41    }
42
43    #[test]
44    fn test_code_verifier_url_safe() {
45        let verifier = generate_code_verifier();
46        assert!(
47            verifier
48                .chars()
49                .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
50            "verifier should be URL-safe: {}",
51            verifier
52        );
53    }
54
55    #[test]
56    fn test_code_verifier_uniqueness() {
57        let v1 = generate_code_verifier();
58        let v2 = generate_code_verifier();
59        assert_ne!(v1, v2, "verifiers should be unique");
60    }
61
62    #[test]
63    fn test_code_challenge_deterministic() {
64        let verifier = "test_verifier_string";
65        let c1 = generate_code_challenge(verifier);
66        let c2 = generate_code_challenge(verifier);
67        assert_eq!(c1, c2, "challenge should be deterministic");
68    }
69
70    #[test]
71    fn test_code_challenge_different_for_different_verifiers() {
72        let c1 = generate_code_challenge("verifier_1");
73        let c2 = generate_code_challenge("verifier_2");
74        assert_ne!(c1, c2);
75    }
76
77    #[test]
78    fn test_state_length() {
79        let state = generate_state();
80        assert_eq!(state.len(), 22);
81    }
82
83    #[test]
84    fn test_state_uniqueness() {
85        let s1 = generate_state();
86        let s2 = generate_state();
87        assert_ne!(s1, s2, "states should be unique");
88    }
89}