You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-28 11:02:02 +03:00
Move the PKCE validation logic to oauth2-types
This commit is contained in:
@ -18,18 +18,75 @@ use data_encoding::BASE64URL_NOPAD;
|
||||
use mas_iana::oauth::PkceCodeChallengeMethod;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum CodeChallengeError {
|
||||
#[error("code_verifier should be at least 43 characters long")]
|
||||
TooShort,
|
||||
|
||||
#[error("code_verifier should be at most 128 characters long")]
|
||||
TooLong,
|
||||
|
||||
#[error("code_verifier contains invalid characters")]
|
||||
InvalidCharacters,
|
||||
|
||||
#[error("challenge verification failed")]
|
||||
VerificationFailed,
|
||||
}
|
||||
|
||||
fn validate_verifier(verifier: &str) -> Result<(), CodeChallengeError> {
|
||||
if verifier.len() < 43 {
|
||||
return Err(CodeChallengeError::TooShort);
|
||||
}
|
||||
|
||||
if verifier.len() > 128 {
|
||||
return Err(CodeChallengeError::TooLong);
|
||||
}
|
||||
|
||||
if !verifier
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '_' || c == '~')
|
||||
{
|
||||
return Err(CodeChallengeError::InvalidCharacters);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait CodeChallengeMethodExt {
|
||||
#[must_use]
|
||||
fn compute_challenge(self, verifier: &str) -> Cow<'_, str>;
|
||||
/// Compute the challenge for a given verifier
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the verifier did not adhere to the rules defined by
|
||||
/// the RFC in terms of length and allowed characters
|
||||
fn compute_challenge(self, verifier: &str) -> Result<Cow<'_, str>, CodeChallengeError>;
|
||||
|
||||
#[must_use]
|
||||
fn verify(self, challenge: &str, verifier: &str) -> bool;
|
||||
/// Verify that a given verifier is valid for the given challenge
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the verifier did not match the challenge, or if the
|
||||
/// verifier did not adhere to the rules defined by the RFC in terms of
|
||||
/// length and allowed characters
|
||||
fn verify(self, challenge: &str, verifier: &str) -> Result<(), CodeChallengeError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if self.compute_challenge(verifier)? == challenge {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CodeChallengeError::VerificationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeChallengeMethodExt for PkceCodeChallengeMethod {
|
||||
fn compute_challenge(self, verifier: &str) -> Cow<'_, str> {
|
||||
match self {
|
||||
fn compute_challenge(self, verifier: &str) -> Result<Cow<'_, str>, CodeChallengeError> {
|
||||
validate_verifier(verifier)?;
|
||||
|
||||
let challenge = match self {
|
||||
Self::Plain => verifier.into(),
|
||||
Self::S256 => {
|
||||
let mut hasher = Sha256::new();
|
||||
@ -38,11 +95,9 @@ impl CodeChallengeMethodExt for PkceCodeChallengeMethod {
|
||||
let verifier = BASE64URL_NOPAD.encode(&hash);
|
||||
verifier.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn verify(self, challenge: &str, verifier: &str) -> bool {
|
||||
self.compute_challenge(verifier) == challenge
|
||||
Ok(challenge)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,3 +111,44 @@ pub struct AuthorizationRequest {
|
||||
pub struct TokenRequest {
|
||||
pub code_challenge_verifier: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pkce_verification() {
|
||||
use PkceCodeChallengeMethod::{Plain, S256};
|
||||
// This challenge comes from the RFC7636 appendices
|
||||
let challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
|
||||
|
||||
assert!(S256
|
||||
.verify(challenge, "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
|
||||
.is_ok());
|
||||
|
||||
assert!(Plain.verify(challenge, challenge).is_ok());
|
||||
|
||||
assert_eq!(
|
||||
S256.verify(challenge, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"),
|
||||
Err(CodeChallengeError::VerificationFailed),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
S256.verify(challenge, "tooshort"),
|
||||
Err(CodeChallengeError::TooShort),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
S256.verify(challenge, "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"),
|
||||
Err(CodeChallengeError::TooLong),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
S256.verify(
|
||||
challenge,
|
||||
"this is long enough but has invalid characters in it"
|
||||
),
|
||||
Err(CodeChallengeError::InvalidCharacters),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user