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}