1use rand_core::{OsRng, RngCore};
9use sha2::{Digest, Sha256};
10
11pub struct PkceVerifier(String);
13
14pub struct PkceChallenge(String);
16
17impl PkceVerifier {
18 pub fn new() -> Self {
20 let mut bytes = [0u8; 32];
21 OsRng.fill_bytes(&mut bytes);
22 PkceVerifier(base64url_encode(&bytes))
23 }
24
25 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29
30 pub fn challenge(&self) -> PkceChallenge {
32 let digest = Sha256::digest(self.0.as_bytes());
33 PkceChallenge(base64url_encode(&digest))
34 }
35}
36
37impl Default for PkceVerifier {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl PkceChallenge {
44 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50const TABLE: &[u8; 64] =
53 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
54
55#[doc(hidden)]
57pub fn base64url_encode(bytes: &[u8]) -> String {
58 let mut out = String::with_capacity(((bytes.len() + 2) / 3) * 4);
59 let mut i = 0;
60 while i + 3 <= bytes.len() {
61 let b0 = bytes[i] as usize;
62 let b1 = bytes[i + 1] as usize;
63 let b2 = bytes[i + 2] as usize;
64 out.push(TABLE[b0 >> 2] as char);
65 out.push(TABLE[((b0 & 3) << 4) | (b1 >> 4)] as char);
66 out.push(TABLE[((b1 & 0xf) << 2) | (b2 >> 6)] as char);
67 out.push(TABLE[b2 & 0x3f] as char);
68 i += 3;
69 }
70 let rem = bytes.len() - i;
71 if rem == 1 {
72 let b0 = bytes[i] as usize;
73 out.push(TABLE[b0 >> 2] as char);
74 out.push(TABLE[(b0 & 3) << 4] as char);
75 } else if rem == 2 {
76 let b0 = bytes[i] as usize;
77 let b1 = bytes[i + 1] as usize;
78 out.push(TABLE[b0 >> 2] as char);
79 out.push(TABLE[((b0 & 3) << 4) | (b1 >> 4)] as char);
80 out.push(TABLE[(b1 & 0xf) << 2] as char);
81 }
82 out
83}
84
85#[doc(hidden)]
88pub fn base64url_decode(s: &str) -> Result<Vec<u8>, String> {
89 Ok(crate::core::base64::Decoder::EITHER.decode(s)?)
90}
91
92#[cfg(test)]
93mod tests {
94 #[test]
95 fn pkce_takes_either_alphabet() {
96 assert_eq!(vec![0xfb, 0xff], super::base64url_decode("-_8").unwrap());
98 assert_eq!(vec![0xfb, 0xff], super::base64url_decode("+/8=").unwrap());
99 assert!(super::base64url_decode("a b").is_err());
100 }
101}