use crate::authenticator_data;
use crate::challenge::CHALLENGE_MAX_AGE_SECS;
use crate::client_data;
use crate::credential::{AuthenticationResult, Challenge, Credential, PublicKey};
use crate::crypto::{
rsa_components_to_der, sha256, verify_eddsa, verify_es256, verify_es384, verify_rs256,
};
use crate::error::{Result, WebAuthnError};
use crate::registration::RelyingParty;
#[derive(Debug, Clone)]
pub struct AuthenticatorAssertionResponse {
pub client_data_json: Vec<u8>,
pub authenticator_data: Vec<u8>,
pub signature: Vec<u8>,
pub user_handle: Option<Vec<u8>>,
}
impl RelyingParty {
pub fn verify_authentication(
&self,
stored_credential: &Credential,
challenge: &Challenge,
response: &AuthenticatorAssertionResponse,
) -> Result<AuthenticationResult> {
verify_authentication_inner(self, stored_credential, challenge, response)
}
}
fn verify_authentication_inner(
rp: &RelyingParty,
stored_credential: &Credential,
challenge: &Challenge,
response: &AuthenticatorAssertionResponse,
) -> Result<AuthenticationResult> {
if challenge.is_expired(CHALLENGE_MAX_AGE_SECS) {
return Err(WebAuthnError::ChallengeExpired);
}
let parsed_cd = client_data::parse_client_data(&response.client_data_json)?;
client_data::validate_client_data(
&parsed_cd,
"webauthn.get",
&challenge.bytes,
&rp.allowed_origins,
rp.reject_cross_origin,
)?;
if let Some(ref used) = rp.used_challenges {
let mut set = used
.lock()
.expect("used_challenges mutex is poisoned — a previous ceremony panicked");
if set.contains(&challenge.bytes) {
return Err(WebAuthnError::ChallengePreviouslyUsed);
}
set.insert(challenge.bytes.clone());
}
let client_data_hash = sha256(&parsed_cd.raw_json);
let auth_data = authenticator_data::parse_authenticator_data(&response.authenticator_data)?;
let expected_rp_id_hash = sha256(stored_credential.rp_id.as_bytes());
if auth_data.rp_id_hash != expected_rp_id_hash {
return Err(WebAuthnError::RpIdHashMismatch);
}
if !auth_data.flags.user_present {
return Err(WebAuthnError::UserNotPresent);
}
if rp.require_user_verification && !auth_data.flags.user_verified {
return Err(WebAuthnError::UserNotVerified);
}
if rp.reject_backup_eligible && auth_data.flags.backup_eligible {
return Err(WebAuthnError::BackupEligibleNotAllowed);
}
if rp.require_backup_eligible && !auth_data.flags.backup_eligible {
return Err(WebAuthnError::BackupEligibilityRequired);
}
if auth_data.flags.backup_eligible != stored_credential.backup_eligible {
return Err(WebAuthnError::BackupEligibilityChanged);
}
let mut signed_data = auth_data.raw.clone();
signed_data.extend_from_slice(&client_data_hash);
match &stored_credential.public_key {
PublicKey::ES256 { x, y } => {
let mut pk = Vec::with_capacity(65);
pk.push(0x04);
pk.extend_from_slice(x);
pk.extend_from_slice(y);
verify_es256(&pk, &signed_data, &response.signature)?;
}
PublicKey::ES384 { x, y } => {
let mut pk = Vec::with_capacity(97);
pk.push(0x04);
pk.extend_from_slice(x);
pk.extend_from_slice(y);
verify_es384(&pk, &signed_data, &response.signature)?;
}
PublicKey::EdDSA(pk) => {
verify_eddsa(pk, &signed_data, &response.signature)?;
}
PublicKey::RS256 { n, e } => {
let der = rsa_components_to_der(n, e)?;
verify_rs256(&der, &signed_data, &response.signature)?;
}
}
let received = auth_data.sign_count;
let stored = stored_credential.sign_count;
if (stored > 0 || received > 0) && received <= stored {
return Err(WebAuthnError::SignCountInvalid { stored, received });
}
Ok(AuthenticationResult {
credential_id: stored_credential.id.clone(),
new_sign_count: received,
user_present: auth_data.flags.user_present,
user_verified: auth_data.flags.user_verified,
backup_eligible: auth_data.flags.backup_eligible,
backup_state: auth_data.flags.backup_state,
extensions: auth_data.extensions,
})
}