1use base64::Engine;
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use rand::Rng;
6use sha2::Digest;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PkceMethod {
11 S256,
13 Plain,
15}
16
17pub fn generate_pkce_pair() -> (String, String) {
23 let mut verifier_bytes = [0u8; 32];
24 rand::rng().fill(&mut verifier_bytes[..]);
25 let verifier = URL_SAFE_NO_PAD.encode(verifier_bytes);
26
27 let challenge = sha256_base64url(&verifier);
28 (verifier, challenge)
29}
30
31pub fn verify_pkce(code_verifier: &str, stored_challenge: &str, method: &str) -> bool {
35 match method {
36 "S256" => {
37 let computed = sha256_base64url(code_verifier);
38 constant_time_eq(computed.as_bytes(), stored_challenge.as_bytes())
39 }
40 "plain" => constant_time_eq(code_verifier.as_bytes(), stored_challenge.as_bytes()),
41 _ => false,
42 }
43}
44
45fn sha256_base64url(input: &str) -> String {
46 let hash = sha2::Sha256::digest(input.as_bytes());
47 URL_SAFE_NO_PAD.encode(hash)
48}
49
50fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
51 if a.len() != b.len() {
52 return false;
53 }
54 a.iter()
55 .zip(b.iter())
56 .fold(0u8, |acc, (x, y)| acc | (x ^ y))
57 == 0
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn generate_pkce_pair_deterministic_challenge() {
66 let (v, c) = generate_pkce_pair();
67 let expected = sha256_base64url(&v);
68 assert_eq!(c, expected);
69 }
70
71 #[test]
72 fn verify_pkce_s256_correct() {
73 let (verifier, challenge) = generate_pkce_pair();
74 assert!(verify_pkce(&verifier, &challenge, "S256"));
75 }
76
77 #[test]
78 fn verify_pkce_s256_wrong_verifier() {
79 let (_, challenge) = generate_pkce_pair();
80 assert!(!verify_pkce("wrong-verifier", &challenge, "S256"));
81 }
82
83 #[test]
84 fn verify_pkce_plain_correct() {
85 assert!(verify_pkce("test", "test", "plain"));
86 }
87
88 #[test]
89 fn verify_pkce_plain_wrong() {
90 assert!(!verify_pkce("test", "wrong", "plain"));
91 }
92
93 #[test]
94 fn verify_pkce_unknown_method() {
95 assert!(!verify_pkce("a", "b", "unknown"));
96 }
97}