webauthn_rp

WebAuthn RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

commit 22b48502a5726985237faea0df5e2e5d2541ce4c
parent e0087e5726455892f7df87ab29951b672dac68e4
Author: Zack Newman <zack@philomathiclife.com>
Date:   Fri,  8 May 2026 10:42:16 -0600

update deps. require uv for prf

Diffstat:
MCargo.toml | 28++++++++++++++--------------
Msrc/lib.rs | 42++++++++++++++++++++----------------------
Msrc/request/auth.rs | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/request/auth/error.rs | 30+++++++++++++++++++++++-------
Msrc/request/auth/ser_server_state.rs | 45++++++++++++++++++++++++++++-----------------
Msrc/request/register.rs | 2++
Msrc/request/register/error.rs | 4++++
Msrc/request/register/tests.rs | 2+-
Msrc/response.rs | 10+++++-----
9 files changed, 190 insertions(+), 109 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "webauthn_rp" readme = "README.md" repository = "https://git.philomathiclife.com/repos/webauthn_rp/" -rust-version = "1.94.1" +rust-version = "1.95.0" version = "0.4.0+spec-3" [lints.rust] @@ -115,24 +115,24 @@ targets = [ ] [dependencies] -base64url_nopad = { version = "0.1.4", default-features = false } -ed25519-dalek = { version = "3.0.0-pre.6", default-features = false } -hashbrown = { version = "0.16.1", default-features = false } -ml-dsa = { version = "0.1.0-rc.8", default-features = false } -p256 = { version = "0.14.0-rc.8", default-features = false, features = ["ecdsa"] } -p384 = { version = "0.14.0-rc.8", default-features = false, features = ["ecdsa"] } -rand = { version = "0.10.0", default-features = false, features = ["thread_rng"] } -rsa = { version = "0.10.0-rc.17", default-features = false, features = ["encoding", "sha2"] } +base64url_nopad = { version = "0.1.5", default-features = false } +ed25519-dalek = { version = "3.0.0-pre.7", default-features = false } +hashbrown = { version = "0.17.0", default-features = false } +ml-dsa = { version = "0.1.0-rc.9", default-features = false } +p256 = { version = "0.14.0-rc.9", default-features = false, features = ["ecdsa"] } +p384 = { version = "0.14.0-rc.9", default-features = false, features = ["ecdsa"] } +rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] } +rsa = { version = "0.10.0-rc.18", default-features = false, features = ["encoding", "sha2"] } serde = { version = "1.0.228", default-features = false, features = ["alloc"], optional = true } serde_json = { version = "1.0.149", default-features = false, features = ["alloc"], optional = true } url = { version = "2.5.8", default-features = false } [dev-dependencies] -base64url_nopad = { version = "0.1.4", default-features = false, features = ["alloc"] } -ed25519-dalek = { version = "3.0.0-pre.6", default-features = false, features = ["alloc", "pkcs8"] } -ml-dsa = { version = "0.1.0-rc.8", default-features = false, features = ["alloc", "pkcs8"] } -p256 = { version = "0.14.0-rc.8", default-features = false, features = ["pem"] } -p384 = { version = "0.14.0-rc.8", default-features = false, features = ["pkcs8"] } +base64url_nopad = { version = "0.1.5", default-features = false, features = ["alloc"] } +ed25519-dalek = { version = "3.0.0-pre.7", default-features = false, features = ["alloc", "pkcs8"] } +ml-dsa = { version = "0.1.0-rc.9", default-features = false, features = ["alloc", "pkcs8"] } +p256 = { version = "0.14.0-rc.9", default-features = false, features = ["pem"] } +p384 = { version = "0.14.0-rc.9", default-features = false, features = ["pkcs8"] } serde_json = { version = "1.0.149", default-features = false, features = ["preserve_order"] } diff --git a/src/lib.rs b/src/lib.rs @@ -559,7 +559,9 @@ use crate::{ }; use crate::{ request::{ - auth::error::{InvalidTimeout, NonDiscoverableCredentialRequestOptionsErr}, + auth::error::{ + DiscoverableCredentialRequestOptionsErr, NonDiscoverableCredentialRequestOptionsErr, + }, error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr}, register::{ ResidentKeyRequirement, USER_HANDLE_MAX_LEN, UserHandle, error::CreationOptionsErr, @@ -694,9 +696,9 @@ pub enum CredentialErr { /// Variant when [`CredentialProtectionPolicy::UserVerificationRequired`], but /// [`DynamicState::user_verified`] is `false`. CredProtectUserVerificationRequiredWithoutUserVerified, - /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)` and - /// [`DynamicState::user_verified`] is `false`. - HmacSecretWithoutUserVerified, + /// Variant when [`ClientExtensionsOutputs::prf`] is + /// `Some(AuthenticationExtensionsPRFOutputs { enabled: true })` and [`DynamicState::user_verified`] is `false`. + PrfWithoutUserVerified, /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)`, but /// [`ClientExtensionsOutputs::prf`] is `Some(AuthenticationExtensionsPRFOutputs { enabled: false })` /// or `AuthenticatorExtensionOutput::hmac_secret` is `Some`, but @@ -717,9 +719,7 @@ impl Display for CredentialErr { Self::CredProtectUserVerificationRequiredWithoutUserVerified => { "credProtect requires user verification, but the user is not verified" } - Self::HmacSecretWithoutUserVerified => { - "hmac-secret was enabled, but the user is not verified" - } + Self::PrfWithoutUserVerified => "prf is 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 +746,11 @@ fn verify_static_and_dynamic_state<T>( ) { Err(CredentialErr::CredProtectUserVerificationRequiredWithoutUserVerified) } else if static_state - .extensions - .hmac_secret - .is_some_and(convert::identity) + .client_extension_results + .prf + .is_some_and(|prf| prf.enabled) { - Err(CredentialErr::HmacSecretWithoutUserVerified) + Err(CredentialErr::PrfWithoutUserVerified) } else { Ok(()) } @@ -1131,12 +1131,10 @@ pub enum AggErr { DomainOrigin(DomainOriginParseErr), /// Variant when [`Port::from_str`] errors. Port(PortParseErr), - /// Variant when [`DiscoverableCredentialRequestOptions::start_ceremony`] or - /// [`NonDiscoverableCredentialRequestOptions::start_ceremony`] - /// error. - InvalidTimeout(InvalidTimeout), /// Variant when [`CredentialCreationOptions::start_ceremony`] errors. CreationOptions(CreationOptionsErr), + /// Variant when [`DiscoverableCredentialRequestOptions::start_ceremony`] errors. + DiscoverableCredentialRequestOptions(DiscoverableCredentialRequestOptionsErr), /// Variant when [`NonDiscoverableCredentialRequestOptions::start_ceremony`] errors. NonDiscoverableCredentialRequestOptions(NonDiscoverableCredentialRequestOptionsErr), /// Variant when [`RegistrationServerState::verify`] errors. @@ -1234,18 +1232,18 @@ impl From<PortParseErr> for AggErr { Self::Port(value) } } -impl From<InvalidTimeout> for AggErr { - #[inline] - fn from(value: InvalidTimeout) -> Self { - Self::InvalidTimeout(value) - } -} impl From<CreationOptionsErr> for AggErr { #[inline] fn from(value: CreationOptionsErr) -> Self { Self::CreationOptions(value) } } +impl From<DiscoverableCredentialRequestOptionsErr> for AggErr { + #[inline] + fn from(value: DiscoverableCredentialRequestOptionsErr) -> Self { + Self::DiscoverableCredentialRequestOptions(value) + } +} impl From<NonDiscoverableCredentialRequestOptionsErr> for AggErr { #[inline] fn from(value: NonDiscoverableCredentialRequestOptionsErr) -> Self { @@ -1387,8 +1385,8 @@ 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::InvalidTimeout(err) => err.fmt(f), Self::CreationOptions(err) => err.fmt(f), + Self::DiscoverableCredentialRequestOptions(err) => err.fmt(f), Self::NonDiscoverableCredentialRequestOptions(err) => err.fmt(f), Self::RegCeremony(ref err) => err.fmt(f), Self::AuthCeremony(ref err) => err.fmt(f), diff --git a/src/request/auth.rs b/src/request/auth.rs @@ -30,7 +30,9 @@ use super::{ CredentialMediationRequirement, Credentials, ExtensionReq, FIVE_MINUTES, Hints, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId, SentChallenge, TimedCeremony, UserVerificationRequirement, - auth::error::{InvalidTimeout, NonDiscoverableCredentialRequestOptionsErr}, + auth::error::{ + DiscoverableCredentialRequestOptionsErr, NonDiscoverableCredentialRequestOptionsErr, + }, }; use core::{ borrow::Borrow, @@ -317,6 +319,34 @@ impl From<Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>> for AllowedCredentials creds } } +/// Helper that verifies the overlap of [`DiscoverableCredentialRequestOptions::start_ceremony`] and +/// [`DiscoverableAuthenticationServerState::decode`]. +/// +/// Returns `true` iff the options are valid. +const fn validate_discoverable_options_helper( + ext: ServerExtensionInfo, + uv: UserVerificationRequirement, +) -> bool { + // If PRF is set, the user has to verify themselves. + matches!(ext.prf, ServerPrfInfo::None) || matches!(uv, UserVerificationRequirement::Required) +} +/// Helper that verifies the overlap of [`NonDiscoverableCredentialRequestOptions::start_ceremony`] and +/// [`NonDiscoverableAuthenticationServerState::decode`]. +fn validate_non_discoverable_options_helper( + uv: UserVerificationRequirement, + creds: &[CredInfo], +) -> Result<(), NonDiscoverableCredentialRequestOptionsErr> { + creds.iter().try_fold((), |(), cred| { + // If PRF is set, the user has to verify themselves. + if matches!(cred.ext.prf, ServerPrfInfo::None) + || matches!(uv, UserVerificationRequirement::Required) + { + Ok(()) + } else { + Err(NonDiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification) + } + }) +} /// [`CredentialUiMode`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialuimode) #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CredentialUiMode { @@ -385,27 +415,32 @@ impl<'rp_id, 'prf_first, 'prf_second> DiscoverableAuthenticationServerState, DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>, ), - InvalidTimeout, + DiscoverableCredentialRequestOptionsErr, > { - #[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), - ) - }) + let extensions = self.public_key.extensions.into(); + if validate_discoverable_options_helper(extensions, self.public_key.user_verification) { + #[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(DiscoverableCredentialRequestOptionsErr::InvalidTimeout) + .map(|expiration| { + ( + DiscoverableAuthenticationServerState(AuthenticationServerState { + challenge: SentChallenge(self.public_key.challenge.0), + user_verification: self.public_key.user_verification, + extensions, + expiration, + }), + DiscoverableAuthenticationClientState(self), + ) + }) + } else { + Err(DiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification) + } } /// Same as [`Self::start_ceremony`] except the raw challenge is returned instead of /// [`DiscoverableAuthenticationClientState`]. @@ -422,7 +457,10 @@ impl<'rp_id, 'prf_first, 'prf_second> #[inline] pub fn start_ceremony_challenge_only( self, - ) -> Result<(DiscoverableAuthenticationServerState, [u8; 16]), InvalidTimeout> { + ) -> Result< + (DiscoverableAuthenticationServerState, [u8; 16]), + DiscoverableCredentialRequestOptionsErr, + > { self.start_ceremony() .map(|(server, client)| (server, client.0.public_key.challenge.into_array())) } @@ -525,28 +563,40 @@ impl<'rp_id, 'prf_first, 'prf_second> } else if matches!(self.mediation, CredentialMediationRequirement::Conditional) { Err(NonDiscoverableCredentialRequestOptionsErr::ConditionalMediationRequested) } else { - #[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(NonDiscoverableCredentialRequestOptionsErr::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: Box::from(&self.allow_credentials), - }, - NonDiscoverableAuthenticationClientState(self), + let extensions = self.options.extensions.into(); + if validate_discoverable_options_helper(extensions, self.options.user_verification) { + let allow_credentials = Box::from(&self.allow_credentials); + validate_non_discoverable_options_helper( + self.options.user_verification, + &allow_credentials, ) - }) + .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(NonDiscoverableCredentialRequestOptionsErr::InvalidTimeout) + .map(|expiration| { + ( + NonDiscoverableAuthenticationServerState { + state: AuthenticationServerState { + challenge: SentChallenge(self.options.challenge.0), + user_verification: self.options.user_verification, + extensions, + expiration, + }, + allow_credentials, + }, + NonDiscoverableAuthenticationClientState(self), + ) + }) + }) + } else { + Err(NonDiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification) + } } } } diff --git a/src/request/auth/error.rs b/src/request/auth/error.rs @@ -11,18 +11,27 @@ use core::{ #[cfg(doc)] use std::time::{Instant, SystemTime}; /// Error returned by [`DiscoverableCredentialRequestOptions::start_ceremony`]. -/// -/// This happens when [`PublicKeyCredentialRequestOptions::timeout`] could not be added to [`Instant::now`] or -/// [`SystemTime::now`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct InvalidTimeout; -impl Display for InvalidTimeout { +pub enum DiscoverableCredentialRequestOptionsErr { + /// Error when [`Extension::prf`] is [`Some`] but [`PublicKeyCredentialRequestOptions::user_verification`] is + /// not [`UserVerificationRequirement::Required`]. + PrfWithoutUserVerification, + /// Variant when [`PublicKeyCredentialRequestOptions::timeout`] could not be added to [`Instant::now`] or + /// [`SystemTime::now`]. + InvalidTimeout, +} +impl Display for DiscoverableCredentialRequestOptionsErr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str("the timeout could not be added to the current Instant") + 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", + }) } } -impl Error for InvalidTimeout {} +impl Error for DiscoverableCredentialRequestOptionsErr {} /// Error returned by [`NonDiscoverableCredentialRequestOptions::start_ceremony`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum NonDiscoverableCredentialRequestOptionsErr { @@ -32,6 +41,10 @@ pub enum NonDiscoverableCredentialRequestOptionsErr { /// Variant when [`NonDiscoverableCredentialRequestOptions::mediation`] is /// [`CredentialMediationRequirement::Conditional`]. ConditionalMediationRequested, + /// Error when [`Extension::prf`] or [`CredentialSpecificExtension::prf`] is [`Some`] but + /// [`PublicKeyCredentialRequestOptions::user_verification`] is not + /// [`UserVerificationRequirement::Required`]. + PrfWithoutUserVerification, /// Variant when [`PublicKeyCredentialRequestOptions::timeout`] could not be added to [`Instant::now`] or /// [`SystemTime::now`]. InvalidTimeout, @@ -46,6 +59,9 @@ impl Display for NonDiscoverableCredentialRequestOptionsErr { Self::ConditionalMediationRequested => { "non-discoverable requests are not allowed to use conditional mediation" } + Self::PrfWithoutUserVerification => { + "prf extension was requested without requiring user verification" + } Self::InvalidTimeout => "the timeout could not be added to the current Instant", }) } diff --git a/src/request/auth/ser_server_state.rs b/src/request/auth/ser_server_state.rs @@ -229,12 +229,16 @@ 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| { - SystemTime::decode_from_buffer(data).map(|expiration| Self { - challenge, - user_verification, - extensions, - expiration, - }) + if super::validate_discoverable_options_helper(extensions, user_verification) { + SystemTime::decode_from_buffer(data).map(|expiration| Self { + challenge, + user_verification, + extensions, + expiration, + }) + } else { + Err(EncDecErr) + } }) }) }) @@ -304,19 +308,26 @@ impl Decode for NonDiscoverableAuthenticationServerState { AuthenticationServerState::decode_from_buffer(&mut input) .map_err(|_e| DecodeNonDiscoverableAuthenticationServerStateErr::Other) .and_then(|state| { - Box::<[_]>::decode_from_buffer(&mut input) + Box::decode_from_buffer(&mut input) .map_err(|_e| DecodeNonDiscoverableAuthenticationServerStateErr::Other) .and_then(|allow_credentials| { - if allow_credentials.is_empty() { - Err(DecodeNonDiscoverableAuthenticationServerStateErr::Other) - } else if input.is_empty() { - Ok(Self { - state, - allow_credentials, - }) - } else { - Err(DecodeNonDiscoverableAuthenticationServerStateErr::TrailingData) - } + super::validate_non_discoverable_options_helper( + state.user_verification, + &allow_credentials, + ) + .map_err(|_e| DecodeNonDiscoverableAuthenticationServerStateErr::Other) + .and({ + if allow_credentials.is_empty() { + Err(DecodeNonDiscoverableAuthenticationServerStateErr::Other) + } else 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 @@ -977,6 +977,8 @@ 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(_, _) diff --git a/src/request/register/error.rs b/src/request/register/error.rs @@ -13,6 +13,9 @@ use std::time::{Instant, SystemTime}; /// Error returned by [`CredentialCreationOptions::start_ceremony`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] 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, @@ -26,6 +29,7 @@ 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::HintsIncompatibleWithAuthAttachment => "hints are not compatible with the requested authenticator attachment modality", Self::InvalidTimeout => "the timeout could not be added to the current Instant", diff --git a/src/request/register/tests.rs b/src/request/register/tests.rs @@ -846,7 +846,7 @@ fn prf() -> Result<(), AggErr> { opts.response.user_verified = false; opts.response.hmac = HmacSecret::Enabled; opts.response.prf = Some(true); - assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::HmacSecretWithoutUserVerified))))); + assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::PrfWithoutUserVerified))))); opts.response.prf = None; opts.response.hmac = HmacSecret::None; validate(opts)?; diff --git a/src/response.rs b/src/response.rs @@ -33,14 +33,14 @@ use ser_relaxed::SerdeJsonErr; /// # use core::convert; /// # use webauthn_rp::{ /// # hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, -/// # request::{auth::{error::InvalidTimeout, DiscoverableAuthenticationClientState, DiscoverableCredentialRequestOptions, AuthenticationVerificationOptions}, register::{BackupReq, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64}, RpId}, +/// # request::{auth::{error::DiscoverableCredentialRequestOptionsErr, DiscoverableAuthenticationClientState, DiscoverableCredentialRequestOptions, AuthenticationVerificationOptions}, register::{BackupReq, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64}, RpId}, /// # response::{auth::{error::AuthCeremonyErr, DiscoverableAuthentication64}, error::CollectedClientDataErr, register::{AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState, CredentialProtectionPolicy, DynamicState, Ed25519PubKey, CompressedPubKeyOwned, StaticState}, AuthenticatorAttachment, Backup, CollectedClientData, CredentialId}, /// # AuthenticatedCredential, CredentialErr /// # }; /// # #[derive(Debug)] /// # enum E { /// # CollectedClientData(CollectedClientDataErr), -/// # InvalidTimeout(InvalidTimeout), +/// # DiscoverableCredentialRequestOptions(DiscoverableCredentialRequestOptionsErr), /// # SerdeJson(serde_json::Error), /// # MissingUserHandle, /// # MissingCeremony, @@ -53,9 +53,9 @@ use ser_relaxed::SerdeJsonErr; /// # Self::CollectedClientData(value) /// # } /// # } -/// # impl From<InvalidTimeout> for E { -/// # fn from(value: InvalidTimeout) -> Self { -/// # Self::InvalidTimeout(value) +/// # impl From<DiscoverableCredentialRequestOptionsErr> for E { +/// # fn from(value: DiscoverableCredentialRequestOptionsErr) -> Self { +/// # Self::DiscoverableCredentialRequestOptions(value) /// # } /// # } /// # impl From<serde_json::Error> for E {