Skip to main content

light_openid/
code_challenge.rs

1use 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/// Code challenge, as specified in <https://datatracker.ietf.org/doc/rfc7636/>
7///
8/// See some implementation help in <https://docs.hidglobal.com/activid-as-v8.5/api/openid/leverage-pkce-auth-code-grant-flow.htm>
9#[derive(Debug, Clone, Eq, PartialEq)]
10pub struct CodeChallenge {
11    pub code_challenge: String,
12    pub code_challenge_method: String,
13}
14
15impl CodeChallenge {
16    pub fn new_s256() -> (Self, String) {
17        let code_verifier = Alphanumeric.sample_string(&mut rand::rng(), 70);
18        (
19            Self {
20                code_challenge: BASE64_URL_SAFE_NO_PAD.encode(sha256(code_verifier.as_bytes())),
21                code_challenge_method: "S256".to_string(),
22            },
23            code_verifier,
24        )
25    }
26
27    pub fn verify_code(&self, verifier: &str) -> bool {
28        // RFC 7636 - Section 4.1 - Client Creates a Code Verifier
29        if verifier.len() < 43 || verifier.len() > 128 {
30            tracing::error!("Invalid code verifier len ({})!", verifier.len());
31            return false;
32        }
33
34        match self.code_challenge_method.as_str() {
35            "plain" => verifier.eq(&self.code_challenge),
36            "S256" => {
37                let encoded = BASE64_URL_SAFE_NO_PAD.encode(sha256(verifier.as_bytes()));
38
39                encoded.eq(&self.code_challenge)
40            }
41            s => {
42                tracing::error!("Unknown code challenge method: {s}");
43                false
44            }
45        }
46    }
47}
48
49#[cfg(test)]
50mod test {
51    use crate::code_challenge::CodeChallenge;
52
53    #[test]
54    fn test_plain() {
55        let chal = CodeChallenge {
56            code_challenge_method: "plain".to_string(),
57            code_challenge: "cho9ohv9fuG9ongei9eegaicoungoh0tuna0pohr8thekaing4".to_string(),
58        };
59
60        assert!(chal.verify_code("cho9ohv9fuG9ongei9eegaicoungoh0tuna0pohr8thekaing4"));
61        assert!(!chal.verify_code("niz8pha4gu0faibahtahghuofoom1yaagh5ipaiWicei2Roo6u"));
62    }
63
64    #[test]
65    fn test_s256() {
66        let chal = CodeChallenge {
67            code_challenge_method: "S256".to_string(),
68            code_challenge: "uSOvC48D8TMh6RgW-36XppMlMgys-6KAE_wEIev9W2g".to_string(),
69        };
70
71        assert!(chal.verify_code("HIwht3lCHfnsruA+7Sq8NP2mPj5cBZe0Ewf23eK9UQhK4TdCIt3SK7Fr/giCdnfjxYQILOPG2D562emggAa2lA=="));
72        assert!(!chal.verify_code("text1"));
73    }
74
75    #[test]
76    fn test_s256_2() {
77        let chal = CodeChallenge {
78            code_challenge_method: "S256".to_string(),
79            code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM".to_string(),
80        };
81
82        assert!(chal.verify_code("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"));
83        assert!(!chal.verify_code("text1"));
84    }
85}