commit e417cff57c7d18af13f0b34d3024cd12a83e4b58
parent fcd94da3dcb4e3353a974c9973cad59a8b083dab
Author: Zack Newman <zack@philomathiclife.com>
Date: Thu, 12 Jun 2025 14:00:54 -0600
dont require uv for prf
Diffstat:
8 files changed, 93 insertions(+), 162 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -47,7 +47,6 @@ implicit_return = "allow"
min_ident_chars = "allow"
missing_trait_methods = "allow"
module_name_repetitions = "allow"
-multiple_crate_versions = "allow"
pub_with_shorthand = "allow"
pub_use = "allow"
question_mark_used = "allow"
diff --git a/src/lib.rs b/src/lib.rs
@@ -510,6 +510,10 @@
//!
//! [^note]: `panic`s related to memory allocations or stack overflow are possible since such issues are not
//! formally guarded against.
+#![expect(
+ clippy::multiple_crate_versions,
+ reason = "RustCrypto hasn't updated rand yet"
+)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(not(any(feature = "custom", all(feature = "bin", feature = "serde"))))]
compile_error!("'custom' must be enabled or both 'bin' and 'serde' must be enabled");
@@ -558,7 +562,7 @@ use crate::{
};
use crate::{
request::{
- auth::error::{RequestOptionsErr, SecondFactorErr},
+ auth::error::{InvalidTimeout, SecondFactorErr},
error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr},
register::{
ResidentKeyRequirement, USER_HANDLE_MAX_LEN, UserHandle,
@@ -695,10 +699,9 @@ pub enum CredentialErr {
/// Variant when [`CredentialProtectionPolicy::UserVerificationRequired`], but
/// [`DynamicState::user_verified`] is `false`.
CredProtectUserVerificationRequiredWithoutUserVerified,
- /// Variant when [`ClientExtensionsOutputs::prf`] is
- /// `Some(AuthenticationExtensionsPRFOutputs { enabled: true })` and
+ /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)` and
/// [`DynamicState::user_verified`] is `false`.
- PrfWithoutUserVerified,
+ HmacSecretWithoutUserVerified,
/// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)`, but
/// [`ClientExtensionsOutputs::prf`] is `Some(AuthenticationExtensionsPRFOutputs { enabled: false })`
/// or `AuthenticatorExtensionOutput::hmac_secret` is `Some`, but
@@ -719,7 +722,9 @@ impl Display for CredentialErr {
Self::CredProtectUserVerificationRequiredWithoutUserVerified => {
"credProtect requires user verification, but the user is not verified"
}
- Self::PrfWithoutUserVerified => "prf is enabled, but the user is not verified",
+ Self::HmacSecretWithoutUserVerified => {
+ "hmac-secret was enabled, but the user is not verified"
+ }
Self::HmacSecretWithoutPrf => "hmac-secret was enabled but prf was not",
Self::PrfWithoutHmacSecret => "prf was enabled, but hmac-secret was not",
Self::ResidentKeyRequiredServerCredentialCreated => {
@@ -746,11 +751,11 @@ fn verify_static_and_dynamic_state<T>(
) {
Err(CredentialErr::CredProtectUserVerificationRequiredWithoutUserVerified)
} else if static_state
- .client_extension_results
- .prf
- .is_some_and(|prf| prf.enabled)
+ .extensions
+ .hmac_secret
+ .is_some_and(convert::identity)
{
- Err(CredentialErr::PrfWithoutUserVerified)
+ Err(CredentialErr::HmacSecretWithoutUserVerified)
} else {
Ok(())
}
@@ -1127,7 +1132,7 @@ pub enum AggErr {
/// Variant when [`DiscoverableCredentialRequestOptions::start_ceremony`] or
/// [`NonDiscoverableCredentialRequestOptions::start_ceremony`]
/// error.
- RequestOptions(RequestOptionsErr),
+ InvalidTimeout(InvalidTimeout),
/// Variant when [`NonDiscoverableCredentialRequestOptions::second_factor`] errors.
SecondFactor(SecondFactorErr),
/// Variant when [`CredentialCreationOptions::start_ceremony`] errors.
@@ -1243,10 +1248,10 @@ impl From<PortParseErr> for AggErr {
Self::Port(value)
}
}
-impl From<RequestOptionsErr> for AggErr {
+impl From<InvalidTimeout> for AggErr {
#[inline]
- fn from(value: RequestOptionsErr) -> Self {
- Self::RequestOptions(value)
+ fn from(value: InvalidTimeout) -> Self {
+ Self::InvalidTimeout(value)
}
}
impl From<SecondFactorErr> for AggErr {
@@ -1420,7 +1425,7 @@ impl Display for AggErr {
Self::Scheme(err) => err.fmt(f),
Self::DomainOrigin(ref err) => err.fmt(f),
Self::Port(ref err) => err.fmt(f),
- Self::RequestOptions(err) => err.fmt(f),
+ Self::InvalidTimeout(err) => err.fmt(f),
Self::SecondFactor(err) => err.fmt(f),
Self::CreationOptions(err) => err.fmt(f),
Self::Nickname(err) => err.fmt(f),
diff --git a/src/request/auth.rs b/src/request/auth.rs
@@ -27,7 +27,7 @@ use super::{
BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId, CredentialMediationRequirement,
Credentials, ExtensionReq, Hint, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId,
SentChallenge, THREE_HUNDRED_THOUSAND, TimedCeremony, UserVerificationRequirement,
- auth::error::{RequestOptionsErr, SecondFactorErr},
+ auth::error::{InvalidTimeout, SecondFactorErr},
};
use core::{
borrow::Borrow,
@@ -288,38 +288,6 @@ impl From<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>> for AllowedCredentials {
creds
}
}
-/// Helper that verifies the overlap of [`DiscoverableCredentialRequestOptions::start_ceremony`] and
-/// [`DiscoverableAuthenticationServerState::decode`].
-const fn validate_discoverable_options_helper(
- ext: ServerExtensionInfo,
- uv: UserVerificationRequirement,
-) -> Result<(), RequestOptionsErr> {
- // If PRF is set, the user has to verify themselves.
- if matches!(ext.prf, ServerPrfInfo::One(_) | ServerPrfInfo::Two(_))
- && !matches!(uv, UserVerificationRequirement::Required)
- {
- Err(RequestOptionsErr::PrfWithoutUserVerification)
- } else {
- Ok(())
- }
-}
-/// Helper that verifies the overlap of [`NonDiscoverableCredentialRequestOptions::start_ceremony`] and
-/// [`NonDiscoverableAuthenticationServerState::decode`].
-fn validate_non_discoverable_options_helper(
- uv: UserVerificationRequirement,
- creds: &[CredInfo],
-) -> Result<(), RequestOptionsErr> {
- creds.iter().try_fold((), |(), cred| {
- // If PRF is set, the user has to verify themselves.
- if matches!(cred.ext.prf, ServerPrfInfo::One(_) | ServerPrfInfo::Two(_))
- && !matches!(uv, UserVerificationRequirement::Required)
- {
- Err(RequestOptionsErr::PrfWithoutUserVerification)
- } else {
- Ok(())
- }
- })
-}
/// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
/// to send to the client when authenticating a discoverable credentential.
///
@@ -374,31 +342,27 @@ impl<'rp_id, 'prf_first, 'prf_second>
DiscoverableAuthenticationServerState,
DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
),
- RequestOptionsErr,
+ InvalidTimeout,
> {
- let extensions = self.public_key.extensions.into();
- validate_discoverable_options_helper(extensions, self.public_key.user_verification)
- .and_then(|()| {
- #[cfg(not(feature = "serializable_server_state"))]
- let res = Instant::now();
- #[cfg(feature = "serializable_server_state")]
- let res = SystemTime::now();
- res.checked_add(Duration::from_millis(
- NonZeroU64::from(self.public_key.timeout).get(),
- ))
- .ok_or(RequestOptionsErr::InvalidTimeout)
- .map(|expiration| {
- (
- DiscoverableAuthenticationServerState(AuthenticationServerState {
- challenge: SentChallenge(self.public_key.challenge.0),
- user_verification: self.public_key.user_verification,
- extensions,
- expiration,
- }),
- DiscoverableAuthenticationClientState(self),
- )
- })
- })
+ #[cfg(not(feature = "serializable_server_state"))]
+ let res = Instant::now();
+ #[cfg(feature = "serializable_server_state")]
+ let res = SystemTime::now();
+ res.checked_add(Duration::from_millis(
+ NonZeroU64::from(self.public_key.timeout).get(),
+ ))
+ .ok_or(InvalidTimeout)
+ .map(|expiration| {
+ (
+ DiscoverableAuthenticationServerState(AuthenticationServerState {
+ challenge: SentChallenge(self.public_key.challenge.0),
+ user_verification: self.public_key.user_verification,
+ extensions: self.public_key.extensions.into(),
+ expiration,
+ }),
+ DiscoverableAuthenticationClientState(self),
+ )
+ })
}
}
/// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
@@ -516,42 +480,30 @@ impl<'rp_id, 'prf_first, 'prf_second>
NonDiscoverableAuthenticationServerState,
NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
),
- RequestOptionsErr,
+ InvalidTimeout,
> {
- let extensions = self.options.extensions.into();
- validate_discoverable_options_helper(extensions, self.options.user_verification).and_then(
- |()| {
- let allow_credentials = Vec::from(&self.allow_credentials);
- validate_non_discoverable_options_helper(
- self.options.user_verification,
- allow_credentials.as_slice(),
- )
- .and_then(|()| {
- #[cfg(not(feature = "serializable_server_state"))]
- let res = Instant::now();
- #[cfg(feature = "serializable_server_state")]
- let res = SystemTime::now();
- res.checked_add(Duration::from_millis(
- NonZeroU64::from(self.options.timeout).get(),
- ))
- .ok_or(RequestOptionsErr::InvalidTimeout)
- .map(|expiration| {
- (
- NonDiscoverableAuthenticationServerState {
- state: AuthenticationServerState {
- challenge: SentChallenge(self.options.challenge.0),
- user_verification: self.options.user_verification,
- extensions,
- expiration,
- },
- allow_credentials,
- },
- NonDiscoverableAuthenticationClientState(self),
- )
- })
- })
- },
- )
+ #[cfg(not(feature = "serializable_server_state"))]
+ let res = Instant::now();
+ #[cfg(feature = "serializable_server_state")]
+ let res = SystemTime::now();
+ res.checked_add(Duration::from_millis(
+ NonZeroU64::from(self.options.timeout).get(),
+ ))
+ .ok_or(InvalidTimeout)
+ .map(|expiration| {
+ (
+ NonDiscoverableAuthenticationServerState {
+ state: AuthenticationServerState {
+ challenge: SentChallenge(self.options.challenge.0),
+ user_verification: self.options.user_verification,
+ extensions: self.options.extensions.into(),
+ expiration,
+ },
+ allow_credentials: Vec::from(&self.allow_credentials),
+ },
+ NonDiscoverableAuthenticationClientState(self),
+ )
+ })
}
}
/// The [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions)
diff --git a/src/request/auth/error.rs b/src/request/auth/error.rs
@@ -22,25 +22,16 @@ impl Display for SecondFactorErr {
}
impl Error for SecondFactorErr {}
/// Error returned by [`DiscoverableCredentialRequestOptions::start_ceremony`]
-/// and [`NonDiscoverableCredentialRequestOptions::start_ceremony`]
+/// and [`NonDiscoverableCredentialRequestOptions::start_ceremony`].
+///
+/// This happens when [`PublicKeyCredentialRequestOptions::timeout`] could not be added to [`Instant::now`] or
+/// [`SystemTime::now`].
#[derive(Clone, Copy, Debug)]
-pub enum RequestOptionsErr {
- /// Error when [`Extension::prf`] or [`CredentialSpecificExtension::prf`] is [`Some`] but
- /// [`PublicKeyCredentialRequestOptions::user_verification`] is not
- /// [`UserVerificationRequirement::Required`].
- PrfWithoutUserVerification,
- /// [`PublicKeyCredentialRequestOptions::timeout`] could not be added to [`Instant::now`] or [`SystemTime::now`].
- InvalidTimeout,
-}
-impl Display for RequestOptionsErr {
+pub struct InvalidTimeout;
+impl Display for InvalidTimeout {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- f.write_str(match *self {
- Self::PrfWithoutUserVerification => {
- "prf extension was requested without requiring user verification"
- }
- Self::InvalidTimeout => "the timeout could not be added to the current Instant",
- })
+ f.write_str("the timeout could not be added to the current Instant")
}
}
-impl Error for RequestOptionsErr {}
+impl Error for InvalidTimeout {}
diff --git a/src/request/auth/ser_server_state.rs b/src/request/auth/ser_server_state.rs
@@ -213,16 +213,12 @@ impl<'a> DecodeBuffer<'a> for AuthenticationServerState {
SentChallenge::decode_from_buffer(data).and_then(|challenge| {
UserVerificationRequirement::decode_from_buffer(data).and_then(|user_verification| {
ServerExtensionInfo::decode_from_buffer(data).and_then(|extensions| {
- super::validate_discoverable_options_helper(extensions, user_verification)
- .map_err(|_e| EncDecErr)
- .and_then(|()| {
- SystemTime::decode_from_buffer(data).map(|expiration| Self {
- challenge,
- user_verification,
- extensions,
- expiration,
- })
- })
+ SystemTime::decode_from_buffer(data).map(|expiration| Self {
+ challenge,
+ user_verification,
+ extensions,
+ expiration,
+ })
})
})
})
@@ -295,21 +291,14 @@ impl Decode for NonDiscoverableAuthenticationServerState {
Vec::decode_from_buffer(&mut input)
.map_err(|_e| DecodeNonDiscoverableAuthenticationServerStateErr::Other)
.and_then(|allow_credentials| {
- super::validate_non_discoverable_options_helper(
- state.user_verification,
- allow_credentials.as_slice(),
- )
- .map_err(|_e| DecodeNonDiscoverableAuthenticationServerStateErr::Other)
- .and({
- if input.is_empty() {
- Ok(Self {
- state,
- allow_credentials,
- })
- } else {
- Err(DecodeNonDiscoverableAuthenticationServerStateErr::TrailingData)
- }
- })
+ if input.is_empty() {
+ Ok(Self {
+ state,
+ allow_credentials,
+ })
+ } else {
+ Err(DecodeNonDiscoverableAuthenticationServerStateErr::TrailingData)
+ }
})
})
}
diff --git a/src/request/register.rs b/src/request/register.rs
@@ -1282,8 +1282,6 @@ const fn validate_options_helper(
UserVerificationRequirement::Required
) {
Ok(())
- } else if !matches!(extensions.prf, ServerPrfInfo::None) {
- Err(CreationOptionsErr::PrfWithoutUserVerification)
} else if matches!(
extensions.cred_protect,
CredProtect::UserVerificationRequired(_, _)
@@ -3656,10 +3654,11 @@ mod tests {
validate(opts)?;
opts.request.prf_uv = PrfUvOptions::None(false);
opts.response.user_verified = false;
- opts.response.hmac = HmacSecret::None;
+ opts.response.hmac = HmacSecret::Enabled;
opts.response.prf = Some(true);
- assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::PrfWithoutUserVerified))), |_| false));
+ assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::HmacSecretWithoutUserVerified))), |_| false));
opts.response.prf = None;
+ opts.response.hmac = HmacSecret::None;
validate(opts)?;
Ok(())
}
diff --git a/src/request/register/error.rs b/src/request/register/error.rs
@@ -53,9 +53,6 @@ impl Error for UsernameErr {}
/// Error returned by [`CredentialCreationOptions::start_ceremony`].
#[derive(Clone, Copy, Debug)]
pub enum CreationOptionsErr {
- /// Error when [`Extension::prf`] is [`Some`] but [`AuthenticatorSelectionCriteria::user_verification`] is not
- /// [`UserVerificationRequirement::Required`].
- PrfWithoutUserVerification,
/// Error when [`Extension::cred_protect`] is [`CredProtect::UserVerificationRequired`] but [`AuthenticatorSelectionCriteria::user_verification`] is not
/// [`UserVerificationRequirement::Required`].
CredProtectRequiredWithoutUserVerification,
@@ -66,7 +63,6 @@ impl Display for CreationOptionsErr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
- Self::PrfWithoutUserVerification => "prf extension was requested without requiring user verification",
Self::CredProtectRequiredWithoutUserVerification => "credProtect extension with a value of user verification required was requested without requiring user verification",
Self::InvalidTimeout => "the timeout could not be added to the current Instant",
})
diff --git a/src/response.rs b/src/response.rs
@@ -33,7 +33,7 @@ use ser_relaxed::SerdeJsonErr;
/// # use data_encoding::BASE64URL_NOPAD;
/// # use webauthn_rp::{
/// # hash::hash_set::FixedCapHashSet,
-/// # request::{auth::{error::RequestOptionsErr, DiscoverableAuthenticationClientState, DiscoverableCredentialRequestOptions, AuthenticationVerificationOptions}, error::AsciiDomainErr, register::{UserHandle, USER_HANDLE_MAX_LEN, UserHandle64}, AsciiDomain, BackupReq, RpId},
+/// # request::{auth::{error::InvalidTimeout, DiscoverableAuthenticationClientState, DiscoverableCredentialRequestOptions, AuthenticationVerificationOptions}, error::AsciiDomainErr, register::{UserHandle, USER_HANDLE_MAX_LEN, UserHandle64}, AsciiDomain, BackupReq, RpId},
/// # response::{auth::{error::AuthCeremonyErr, DiscoverableAuthentication64}, error::CollectedClientDataErr, register::{AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState, CredentialProtectionPolicy, DynamicState, Ed25519PubKey, CompressedPubKey, StaticState}, AuthenticatorAttachment, Backup, CollectedClientData, CredentialId},
/// # AuthenticatedCredential, CredentialErr
/// # };
@@ -41,7 +41,7 @@ use ser_relaxed::SerdeJsonErr;
/// # enum E {
/// # CollectedClientData(CollectedClientDataErr),
/// # RpId(AsciiDomainErr),
-/// # RequestOptions(RequestOptionsErr),
+/// # InvalidTimeout(InvalidTimeout),
/// # SerdeJson(serde_json::Error),
/// # MissingUserHandle,
/// # MissingCeremony,
@@ -59,9 +59,9 @@ use ser_relaxed::SerdeJsonErr;
/// # Self::CollectedClientData(value)
/// # }
/// # }
-/// # impl From<RequestOptionsErr> for E {
-/// # fn from(value: RequestOptionsErr) -> Self {
-/// # Self::RequestOptions(value)
+/// # impl From<InvalidTimeout> for E {
+/// # fn from(value: InvalidTimeout) -> Self {
+/// # Self::InvalidTimeout(value)
/// # }
/// # }
/// # impl From<serde_json::Error> for E {