light_openid/
code_challenge.rs1use crate::utils::crypt_utils::sha256;
2use base64::Engine as _;
3use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_SAFE_NO_PAD;
4use rand::distr::{Alphanumeric, SampleString};
5
6#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
8pub struct CodeChallengeVerifier(pub String);
9
10#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
14pub struct CodeChallenge {
15 pub code_challenge: String,
16 pub code_challenge_method: String,
17}
18
19impl CodeChallenge {
20 pub fn new_s256() -> (Self, CodeChallengeVerifier) {
21 let code_verifier = Alphanumeric.sample_string(&mut rand::rng(), 70);
22 (
23 Self {
24 code_challenge: BASE64_URL_SAFE_NO_PAD.encode(sha256(code_verifier.as_bytes())),
25 code_challenge_method: "S256".to_string(),
26 },
27 CodeChallengeVerifier(code_verifier),
28 )
29 }
30
31 pub fn verify_code(&self, verifier: &CodeChallengeVerifier) -> bool {
32 if verifier.0.len() < 43 || verifier.0.len() > 128 {
34 tracing::error!("Invalid code verifier len ({})!", verifier.0.len());
35 return false;
36 }
37
38 match self.code_challenge_method.as_str() {
39 "plain" => verifier.0.eq(&self.code_challenge),
40 "S256" => {
41 let encoded = BASE64_URL_SAFE_NO_PAD.encode(sha256(verifier.0.as_bytes()));
42
43 encoded.eq(&self.code_challenge)
44 }
45 s => {
46 tracing::error!("Unknown code challenge method: {s}");
47 false
48 }
49 }
50 }
51}
52
53#[cfg(test)]
54mod test {
55 use crate::code_challenge::{CodeChallenge, CodeChallengeVerifier};
56
57 #[test]
58 fn test_plain() {
59 let chal = CodeChallenge {
60 code_challenge_method: "plain".to_string(),
61 code_challenge: "cho9ohv9fuG9ongei9eegaicoungoh0tuna0pohr8thekaing4".to_string(),
62 };
63
64 assert!(chal.verify_code(&CodeChallengeVerifier(
65 "cho9ohv9fuG9ongei9eegaicoungoh0tuna0pohr8thekaing4".into()
66 )));
67 assert!(!chal.verify_code(&CodeChallengeVerifier(
68 "niz8pha4gu0faibahtahghuofoom1yaagh5ipaiWicei2Roo6u".into()
69 )));
70 }
71
72 #[test]
73 fn test_s256() {
74 let chal = CodeChallenge {
75 code_challenge_method: "S256".to_string(),
76 code_challenge: "uSOvC48D8TMh6RgW-36XppMlMgys-6KAE_wEIev9W2g".to_string(),
77 };
78
79 assert!(chal.verify_code(&CodeChallengeVerifier("HIwht3lCHfnsruA+7Sq8NP2mPj5cBZe0Ewf23eK9UQhK4TdCIt3SK7Fr/giCdnfjxYQILOPG2D562emggAa2lA==".into())));
80 assert!(!chal.verify_code(&CodeChallengeVerifier("text1".into())));
81 }
82
83 #[test]
84 fn test_s256_2() {
85 let chal = CodeChallenge {
86 code_challenge_method: "S256".to_string(),
87 code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM".to_string(),
88 };
89
90 assert!(chal.verify_code(&CodeChallengeVerifier(
91 "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk".into()
92 )));
93 assert!(!chal.verify_code(&CodeChallengeVerifier("text1".into())));
94 }
95}