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