graph_core/crypto/
pkce.rs

1use base64::engine::general_purpose::URL_SAFE_NO_PAD;
2use base64::Engine;
3use graph_error::{IdentityResult, AF};
4use ring::rand::SecureRandom;
5
6/*
7pub(crate) fn sha256_secure_string() -> IdentityResult<(String, String)> {
8    let mut buf = [0; 32];
9
10    let rng = ring::rand::SystemRandom::new();
11    rng.fill(&mut buf)
12        .map_err(|_| AuthorizationFailure::unknown("ring::error::Unspecified"))?;
13
14    // Known as code_verifier in proof key for code exchange
15    let base_64_random_string = URL_SAFE_NO_PAD.encode(buf);
16
17    let mut context = ring::digest::Context::new(&ring::digest::SHA256);
18    context.update(base_64_random_string.as_bytes());
19
20    // Known as code_challenge in proof key for code exchange
21    let secure_string = URL_SAFE_NO_PAD.encode(context.finish().as_ref());
22
23    // code verifier, code challenge
24    Ok((base_64_random_string, secure_string))
25}
26 */
27
28pub trait GenPkce {
29    fn code_challenge_method() -> String {
30        "S256".into()
31    }
32
33    /// Known as code_verifier in proof key for code exchange
34    /// Uses the Rust ring crypto library to generate a secure random
35    /// 32-octet sequence that is base64 URL encoded (no padding)
36    fn code_verifier() -> String {
37        let mut buf = [0; 32];
38
39        let rng = ring::rand::SystemRandom::new();
40        rng.fill(&mut buf).expect("ring::error::Unspecified");
41
42        URL_SAFE_NO_PAD.encode(buf)
43    }
44
45    fn code_challenge(code_verifier: &String) -> String {
46        let mut context = ring::digest::Context::new(&ring::digest::SHA256);
47        context.update(code_verifier.as_bytes());
48
49        // Known as code_challenge in proof key for code exchange
50        let code_challenge = URL_SAFE_NO_PAD.encode(context.finish().as_ref());
51
52        // code verifier, code challenge
53        code_challenge
54    }
55
56    /// Generate a code challenge and code verifier for the
57    /// authorization code grant flow using proof key for
58    /// code exchange (PKCE) and SHA256.
59    ///
60    /// [ProofKeyCodeExchange] contains a code_verifier,
61    /// code_challenge, and code_challenge_method for use in the authorization code grant.
62    ///
63    /// For authorization, the code_challenge_method parameter in the request body
64    /// is automatically set to 'S256'.
65    ///
66    /// Internally this method uses the Rust ring cyrpto library to generate a secure random
67    /// 32-octet sequence that is base64 URL encoded (no padding) and known as the code verifier.
68    /// This sequence is hashed using SHA256 and base64 URL encoded (no padding) resulting in a
69    /// 43-octet URL safe string which is known as the code challenge.
70    fn oneshot() -> IdentityResult<ProofKeyCodeExchange> {
71        let code_verifier = ProofKeyCodeExchange::code_verifier();
72        let code_challenge = ProofKeyCodeExchange::code_challenge(&code_verifier);
73        ProofKeyCodeExchange::new(
74            code_verifier,
75            code_challenge,
76            ProofKeyCodeExchange::code_challenge_method(),
77        )
78    }
79}
80
81impl GenPkce for ProofKeyCodeExchange {}
82
83#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
84pub struct ProofKeyCodeExchange {
85    /// Used to verify the the
86    /// The code verifier is not included in the authorization URL.
87    /// You can set the code verifier here and then use the From trait
88    /// for [AuthorizationCodeCredential] which does use the code verifier.
89    pub code_verifier: String,
90    /// Used to secure authorization code grants by using Proof Key for Code Exchange (PKCE).
91    /// Required if code_challenge_method is included. For more information, see the PKCE RFC.
92    /// This parameter is now recommended for all application types, both public and confidential
93    /// clients, and required by the Microsoft identity platform for single page apps using the
94    /// authorization code flow.
95    pub code_challenge: String,
96    /// The method used to encode the code_verifier for the code_challenge parameter.
97    /// This SHOULD be S256, but the spec allows the use of plain if the client can't support SHA256.
98    ///
99    /// If excluded, code_challenge is assumed to be plaintext if code_challenge is included.
100    /// The Microsoft identity platform supports both plain and S256.
101    /// For more information, see the PKCE RFC. This parameter is required for single page
102    /// apps using the authorization code flow.
103    pub code_challenge_method: String,
104}
105
106impl ProofKeyCodeExchange {
107    pub fn new<T: AsRef<str>>(
108        code_verifier: T,
109        code_challenge: T,
110        code_challenge_method: T,
111    ) -> IdentityResult<ProofKeyCodeExchange> {
112        let code_challenge = code_challenge.as_ref().to_owned();
113        if code_challenge.len() != 43 {
114            return Err(AF::msg_err("code_challenge", "Must be 43-octet sequence"));
115        }
116        Ok(ProofKeyCodeExchange {
117            code_verifier: code_verifier.as_ref().to_owned(),
118            code_challenge,
119            code_challenge_method: code_challenge_method.as_ref().to_owned(),
120        })
121    }
122}
123
124#[cfg(test)]
125mod test {
126    use super::*;
127
128    #[test]
129    fn pkce_generate() {
130        let pkce = ProofKeyCodeExchange::oneshot().unwrap();
131        assert_eq!(pkce.code_challenge.len(), 43);
132    }
133
134    #[test]
135    fn validate_pkce_challenge_and_verifier() {
136        let pkce = ProofKeyCodeExchange::oneshot().unwrap();
137        let mut context = ring::digest::Context::new(&ring::digest::SHA256);
138        context.update(pkce.code_verifier.as_bytes());
139        let verifier = URL_SAFE_NO_PAD.encode(context.finish().as_ref());
140        assert_eq!(verifier, pkce.code_challenge);
141    }
142}