use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use graph_error::{IdentityResult, AF};
use ring::rand::SecureRandom;
pub trait GenPkce {
fn code_challenge_method() -> String {
"S256".into()
}
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());
let code_challenge = URL_SAFE_NO_PAD.encode(context.finish().as_ref());
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 {
pub code_verifier: String,
pub code_challenge: String,
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);
}
}