use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
pub fn verify_s256(code_verifier: &str, code_challenge: &str) -> bool {
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
let computed = URL_SAFE_NO_PAD.encode(hasher.finalize());
computed.as_bytes().ct_eq(code_challenge.as_bytes()).into()
}
pub fn compute_s256_challenge(code_verifier: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
URL_SAFE_NO_PAD.encode(hasher.finalize())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_s256_roundtrip() {
let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
let challenge = compute_s256_challenge(verifier);
assert!(verify_s256(verifier, &challenge));
}
#[test]
fn test_s256_rfc7636_example() {
let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
let expected_challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
assert!(verify_s256(verifier, expected_challenge));
}
#[test]
fn test_s256_wrong_verifier() {
let challenge = compute_s256_challenge("correct-verifier");
assert!(!verify_s256("wrong-verifier", &challenge));
}
#[test]
fn test_s256_empty_strings() {
let challenge = compute_s256_challenge("");
assert!(verify_s256("", &challenge));
assert!(!verify_s256("notempty", &challenge));
}
}