webauthn_rp

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

auth.rs (72162B)


      1 #[cfg(test)]
      2 mod tests;
      3 #[cfg(doc)]
      4 use super::{
      5     super::response::{
      6         CollectedClientData, Flag,
      7         auth::AuthenticatorData,
      8         register::{
      9             AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState,
     10             DynamicState, StaticState,
     11         },
     12     },
     13     AsciiDomain, AsciiDomainStatic, DomainOrigin, Url,
     14     register::{self, PublicKeyCredentialCreationOptions},
     15 };
     16 use super::{
     17     super::{
     18         AuthenticatedCredential,
     19         response::{
     20             AuthenticatorAttachment,
     21             auth::{
     22                 Authentication, AuthenticatorExtensionOutput, DiscoverableAuthentication,
     23                 HmacSecret, NonDiscoverableAuthentication,
     24                 error::{AuthCeremonyErr, ExtensionErr, OneOrTwo},
     25             },
     26             register::{CompressedPubKey, CredentialProtectionPolicy},
     27         },
     28     },
     29     Backup, BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId,
     30     CredentialMediationRequirement, Credentials, ExtensionReq, FIVE_MINUTES, Hints, Origin,
     31     PrfInput, PublicKeyCredentialDescriptor, RpId, SentChallenge, TimedCeremony,
     32     UserVerificationRequirement,
     33     auth::error::{
     34         DiscoverableCredentialRequestOptionsErr, NonDiscoverableCredentialRequestOptionsErr,
     35     },
     36 };
     37 use core::{
     38     borrow::Borrow,
     39     cmp::Ordering,
     40     hash::{Hash, Hasher},
     41     num::{NonZeroU32, NonZeroU64},
     42     time::Duration,
     43 };
     44 #[cfg(any(doc, not(feature = "serializable_server_state")))]
     45 use std::time::Instant;
     46 #[cfg(any(doc, feature = "serializable_server_state"))]
     47 use std::time::SystemTime;
     48 /// Contains error types.
     49 pub mod error;
     50 /// Contains functionality to serialize data to a client.
     51 #[cfg(feature = "serde")]
     52 pub mod ser;
     53 /// Contains functionality to (de)serialize [`DiscoverableAuthenticationServerState`] and
     54 /// [`NonDiscoverableAuthenticationServerState`] to a data store.
     55 #[cfg(feature = "serializable_server_state")]
     56 pub mod ser_server_state;
     57 /// Controls how [signature counter](https://www.w3.org/TR/webauthn-3/#signature-counter) is enforced.
     58 ///
     59 /// Note that if the previous signature counter is positive and the new counter is not strictly greater, then the
     60 /// authenticator is likely a clone (i.e., there are at least two copies of the private key).
     61 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
     62 pub enum SignatureCounterEnforcement {
     63     /// Fail the authentication ceremony if the counter is less than or equal to the previous value when the
     64     /// previous value is positive.
     65     #[default]
     66     Fail,
     67     /// When the counter is less than the previous value, don't fail and update the value.
     68     ///
     69     /// Note in the special case that the new signature counter is 0, [`DynamicState::sign_count`] _won't_
     70     /// be updated since that would allow an attacker to permanently disable the counter.
     71     Update,
     72     /// When the counter is less than the previous value, don't fail but don't update the value.
     73     Ignore,
     74 }
     75 impl SignatureCounterEnforcement {
     76     /// Validates the signature counter based on `self`.
     77     const fn validate(self, prev: u32, cur: u32) -> Result<u32, AuthCeremonyErr> {
     78         if prev == 0 || cur > prev {
     79             Ok(cur)
     80         } else {
     81             match self {
     82                 Self::Fail => Err(AuthCeremonyErr::SignatureCounter),
     83                 // When the new counter is `0`, we use the previous counter to avoid an attacker from
     84                 // being able to permanently disable it.
     85                 Self::Update => Ok(if cur == 0 { prev } else { cur }),
     86                 Self::Ignore => Ok(prev),
     87             }
     88         }
     89     }
     90 }
     91 /// Owned version of [`PrfInput`].
     92 ///
     93 /// When relying on [`NonDiscoverableCredentialRequestOptions`], it's recommended to use credential-specific PRF
     94 /// inputs that are continuously rolled over. One uses this type for such a thing.
     95 #[derive(Clone, Debug, Eq, PartialEq)]
     96 pub struct PrfInputOwned {
     97     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first).
     98     pub first: Vec<u8>,
     99     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second).
    100     pub second: Option<Vec<u8>>,
    101     /// Note this is only applicable for authenticators that implement the
    102     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the
    103     /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-extension)
    104     /// extension since the data is encrypted and is part of the [`AuthenticatorData`].
    105     pub ext_req: ExtensionReq,
    106 }
    107 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client.
    108 #[derive(Clone, Copy, Debug)]
    109 pub struct Extension<'prf_first, 'prf_second> {
    110     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension).
    111     ///
    112     /// If both [`CredentialSpecificExtension::prf`] and this are [`Some`], then `CredentialSpecificExtension::prf`
    113     /// takes priority.
    114     ///
    115     /// Note `ExtensionReq` is only applicable for authenticators that implement the
    116     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the
    117     /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-extension)
    118     /// extension since the data is encrypted and is part of the [`AuthenticatorData`].
    119     pub prf: Option<(PrfInput<'prf_first, 'prf_second>, ExtensionReq)>,
    120 }
    121 impl<'prf_first, 'prf_second> Extension<'prf_first, 'prf_second> {
    122     /// Returns an `Extension` with [`Self::prf`] set to `None`.
    123     #[inline]
    124     #[must_use]
    125     pub const fn none() -> Self {
    126         Self { prf: None }
    127     }
    128     /// Returns an `Extension` with [`Self::prf`] set to `None`.
    129     #[expect(single_use_lifetimes, reason = "false positive")]
    130     #[inline]
    131     #[must_use]
    132     pub const fn with_prf<'a: 'prf_first, 'b: 'prf_second>(
    133         input: PrfInput<'a, 'b>,
    134         req: ExtensionReq,
    135     ) -> Self {
    136         Self {
    137             prf: Some((input, req)),
    138         }
    139     }
    140 }
    141 impl Default for Extension<'_, '_> {
    142     /// Same as [`Self::none`].
    143     #[inline]
    144     fn default() -> Self {
    145         Self::none()
    146     }
    147 }
    148 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client that
    149 /// are credential-specific which among other things implies a non-discoverable request.
    150 #[derive(Clone, Debug, Default)]
    151 pub struct CredentialSpecificExtension {
    152     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension).
    153     ///
    154     /// If both [`Extension::prf`] and this are [`Some`], then this take priority.
    155     pub prf: Option<PrfInputOwned>,
    156 }
    157 /// Registered credential used in
    158 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    159 #[derive(Clone, Debug)]
    160 pub struct AllowedCredential {
    161     /// The registered credential.
    162     pub credential: PublicKeyCredentialDescriptor<Box<[u8]>>,
    163     /// Credential-specific extensions.
    164     pub extension: CredentialSpecificExtension,
    165 }
    166 impl From<PublicKeyCredentialDescriptor<Box<[u8]>>> for AllowedCredential {
    167     #[inline]
    168     fn from(credential: PublicKeyCredentialDescriptor<Box<[u8]>>) -> Self {
    169         Self {
    170             credential,
    171             extension: CredentialSpecificExtension::default(),
    172         }
    173     }
    174 }
    175 impl From<AllowedCredential> for PublicKeyCredentialDescriptor<Box<[u8]>> {
    176     #[inline]
    177     fn from(credential: AllowedCredential) -> Self {
    178         credential.credential
    179     }
    180 }
    181 /// Queue of unique [`AllowedCredential`]s.
    182 #[derive(Clone, Debug, Default)]
    183 pub struct AllowedCredentials {
    184     /// Allowed credentials.
    185     creds: Vec<AllowedCredential>,
    186     /// Number of `AllowedCredential`s that have PRF inputs.
    187     ///
    188     /// Useful to help serialization.
    189     prf_count: usize,
    190 }
    191 impl Credentials for AllowedCredentials {
    192     type Credential = AllowedCredential;
    193     /// # Examples
    194     ///
    195     /// ```
    196     /// # use webauthn_rp::request::{auth::AllowedCredentials, Credentials};
    197     /// assert!(AllowedCredentials::with_capacity(1).as_ref().is_empty());
    198     /// ```
    199     #[inline]
    200     fn with_capacity(capacity: usize) -> Self {
    201         Self {
    202             creds: Vec::with_capacity(capacity),
    203             prf_count: 0,
    204         }
    205     }
    206     /// # Examples
    207     ///
    208     /// ```
    209     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    210     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    211     /// # use webauthn_rp::{
    212     /// #     request::{auth::AllowedCredentials, PublicKeyCredentialDescriptor, Credentials},
    213     /// #     response::{AuthTransports, CredentialId},
    214     /// # };
    215     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    216     /// /// from the database.
    217     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    218     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    219     ///     // ⋮
    220     /// #     AuthTransports::decode(32)
    221     /// }
    222     /// let mut creds = AllowedCredentials::with_capacity(1);
    223     /// assert!(creds.as_ref().is_empty());
    224     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    225     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    226     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    227     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    228     /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?;
    229     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    230     /// let transports = get_transports((&id).into())?;
    231     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    232     /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into()));
    233     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    234     /// let id_copy = CredentialId::try_from(vec![0; 16].into_boxed_slice())?;
    235     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    236     /// let transports_2 = AuthTransports::NONE;
    237     /// // Duplicate `CredentialId`s don't get added.
    238     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    239     /// assert!(!creds.push(
    240     ///     PublicKeyCredentialDescriptor {
    241     ///         id: id_copy,
    242     ///         transports: transports_2
    243     ///     }
    244     ///     .into()
    245     /// ));
    246     /// # Ok::<_, webauthn_rp::AggErr>(())
    247     /// ```
    248     #[expect(
    249         clippy::arithmetic_side_effects,
    250         reason = "comment explains how overflow is not possible"
    251     )]
    252     #[inline]
    253     fn push(&mut self, cred: Self::Credential) -> bool {
    254         self.creds
    255             .iter()
    256             .try_fold((), |(), c| {
    257                 if c.credential.id == cred.credential.id {
    258                     Err(())
    259                 } else {
    260                     Ok(())
    261                 }
    262             })
    263             .is_ok_and(|()| {
    264                 // This can't overflow since `self.creds.push` would `panic` since
    265                 // `self.prf_count <= self.creds.len()`.
    266                 self.prf_count += usize::from(cred.extension.prf.is_some());
    267                 self.creds.push(cred);
    268                 true
    269             })
    270     }
    271     #[inline]
    272     fn len(&self) -> usize {
    273         self.creds.len()
    274     }
    275 }
    276 impl AsRef<[AllowedCredential]> for AllowedCredentials {
    277     #[inline]
    278     fn as_ref(&self) -> &[AllowedCredential] {
    279         self.creds.as_slice()
    280     }
    281 }
    282 impl From<&AllowedCredentials> for Box<[CredInfo]> {
    283     #[inline]
    284     fn from(value: &AllowedCredentials) -> Self {
    285         let len = value.creds.len();
    286         value
    287             .creds
    288             .iter()
    289             .fold(Vec::with_capacity(len), |mut creds, cred| {
    290                 creds.push(CredInfo {
    291                     id: cred.credential.id.clone(),
    292                     ext: (&cred.extension).into(),
    293                 });
    294                 creds
    295             })
    296             .into_boxed_slice()
    297     }
    298 }
    299 impl From<AllowedCredentials> for Vec<PublicKeyCredentialDescriptor<Box<[u8]>>> {
    300     #[inline]
    301     fn from(value: AllowedCredentials) -> Self {
    302         let mut creds = Self::with_capacity(value.creds.len());
    303         value.creds.into_iter().fold((), |(), cred| {
    304             creds.push(cred.credential);
    305         });
    306         creds
    307     }
    308 }
    309 impl From<Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>> for AllowedCredentials {
    310     #[inline]
    311     fn from(value: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>) -> Self {
    312         let mut creds = Self::with_capacity(value.len());
    313         value.into_iter().fold((), |(), credential| {
    314             _ = creds.push(AllowedCredential {
    315                 credential,
    316                 extension: CredentialSpecificExtension { prf: None },
    317             });
    318         });
    319         creds
    320     }
    321 }
    322 /// Helper that verifies the overlap of [`DiscoverableCredentialRequestOptions::start_ceremony`] and
    323 /// [`DiscoverableAuthenticationServerState::decode`].
    324 ///
    325 /// Returns `true` iff the options are valid.
    326 const fn validate_discoverable_options_helper(
    327     ext: ServerExtensionInfo,
    328     uv: UserVerificationRequirement,
    329 ) -> bool {
    330     // If PRF is set, the user has to verify themselves.
    331     matches!(ext.prf, ServerPrfInfo::None) || matches!(uv, UserVerificationRequirement::Required)
    332 }
    333 /// Helper that verifies the overlap of [`NonDiscoverableCredentialRequestOptions::start_ceremony`] and
    334 /// [`NonDiscoverableAuthenticationServerState::decode`].
    335 fn validate_non_discoverable_options_helper(
    336     uv: UserVerificationRequirement,
    337     creds: &[CredInfo],
    338 ) -> Result<(), NonDiscoverableCredentialRequestOptionsErr> {
    339     creds.iter().try_fold((), |(), cred| {
    340         // If PRF is set, the user has to verify themselves.
    341         if matches!(cred.ext.prf, ServerPrfInfo::None)
    342             || matches!(uv, UserVerificationRequirement::Required)
    343         {
    344             Ok(())
    345         } else {
    346             Err(NonDiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification)
    347         }
    348     })
    349 }
    350 /// [`CredentialUiMode`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialuimode)
    351 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    352 pub enum CredentialUiMode {
    353     /// [`immediate`](https://www.w3.org/TR/credential-management-1/#dom-credentialuimode-immediate).
    354     Immediate,
    355 }
    356 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
    357 /// to send to the client when authenticating a discoverable credential.
    358 ///
    359 /// Upon saving the [`DiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send
    360 /// [`DiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created
    361 /// [`DiscoverableAuthentication`], it is validated using [`DiscoverableAuthenticationServerState::verify`].
    362 #[derive(Debug)]
    363 pub struct DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    364     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-mediation).
    365     ///
    366     /// Note if this is [`CredentialMediationRequirement::Conditional`], user agents are instructed to not
    367     /// enforce any timeout; as result, one may want to set [`PublicKeyCredentialRequestOptions::timeout`] to
    368     /// [`NonZeroU32::MAX`].
    369     pub mediation: CredentialMediationRequirement,
    370     /// [`uiMode`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-uimode).
    371     pub ui_mode: Option<CredentialUiMode>,
    372     /// `public-key` [credential type](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry).
    373     pub public_key: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    374 }
    375 impl<'rp_id, 'prf_first, 'prf_second>
    376     DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>
    377 {
    378     /// Creates a `DiscoverableCredentialRequestOptions` containing [`CredentialMediationRequirement::default`] and
    379     /// [`PublicKeyCredentialRequestOptions::passkey`].
    380     ///
    381     /// # Examples
    382     ///
    383     /// ```
    384     /// # use webauthn_rp::request::{auth::DiscoverableCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    385     /// assert!(matches!(
    386     ///     DiscoverableCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).public_key.user_verification,
    387     ///     UserVerificationRequirement::Required
    388     /// ));
    389     /// # Ok::<_, webauthn_rp::AggErr>(())
    390     /// ```
    391     #[expect(single_use_lifetimes, reason = "false positive")]
    392     #[inline]
    393     #[must_use]
    394     pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    395         Self {
    396             mediation: CredentialMediationRequirement::default(),
    397             ui_mode: None,
    398             public_key: PublicKeyCredentialRequestOptions::passkey(rp_id),
    399         }
    400     }
    401     /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming
    402     /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so
    403     /// `DiscoverableAuthenticationClientState` MUST be sent ASAP. In order to complete authentication, the returned
    404     /// `DiscoverableAuthenticationServerState` MUST be saved so that it can later be used to verify the credential
    405     /// assertion with [`DiscoverableAuthenticationServerState::verify`].
    406     ///
    407     /// # Errors
    408     ///
    409     /// Errors iff `self` contains incompatible configuration.
    410     #[inline]
    411     pub fn start_ceremony(
    412         self,
    413     ) -> Result<
    414         (
    415             DiscoverableAuthenticationServerState,
    416             DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
    417         ),
    418         DiscoverableCredentialRequestOptionsErr,
    419     > {
    420         let extensions = self.public_key.extensions.into();
    421         if validate_discoverable_options_helper(extensions, self.public_key.user_verification) {
    422             #[cfg(not(feature = "serializable_server_state"))]
    423             let res = Instant::now();
    424             #[cfg(feature = "serializable_server_state")]
    425             let res = SystemTime::now();
    426             res.checked_add(Duration::from_millis(
    427                 NonZeroU64::from(self.public_key.timeout).get(),
    428             ))
    429             .ok_or(DiscoverableCredentialRequestOptionsErr::InvalidTimeout)
    430             .map(|expiration| {
    431                 (
    432                     DiscoverableAuthenticationServerState(AuthenticationServerState {
    433                         challenge: SentChallenge(self.public_key.challenge.0),
    434                         user_verification: self.public_key.user_verification,
    435                         extensions,
    436                         expiration,
    437                     }),
    438                     DiscoverableAuthenticationClientState(self),
    439                 )
    440             })
    441         } else {
    442             Err(DiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification)
    443         }
    444     }
    445     /// Same as [`Self::start_ceremony`] except the raw challenge is returned instead of
    446     /// [`DiscoverableAuthenticationClientState`].
    447     ///
    448     /// Note this is useful when one configures the authentication ceremony client-side and only needs the
    449     /// server-generated challenge. It's of course essential that `self` is configured exactly the same as
    450     /// how it is configured client-side. See
    451     /// [`challengeURL`](https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-challengeURL) for more
    452     /// information.
    453     ///
    454     /// # Errors
    455     ///
    456     /// Read [`Self::start_ceremony`].
    457     #[inline]
    458     pub fn start_ceremony_challenge_only(
    459         self,
    460     ) -> Result<
    461         (DiscoverableAuthenticationServerState, [u8; 16]),
    462         DiscoverableCredentialRequestOptionsErr,
    463     > {
    464         self.start_ceremony()
    465             .map(|(server, client)| (server, client.0.public_key.challenge.into_array()))
    466     }
    467 }
    468 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
    469 /// to send to the client when authenticating non-discoverable credentials.
    470 ///
    471 /// Upon saving the [`NonDiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send
    472 /// [`NonDiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created
    473 /// [`NonDiscoverableAuthentication`], it is validated using [`NonDiscoverableAuthenticationServerState::verify`].
    474 #[derive(Debug)]
    475 pub struct NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    476     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-mediation).
    477     pub mediation: CredentialMediationRequirement,
    478     /// [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions).
    479     pub options: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    480     /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    481     pub allow_credentials: AllowedCredentials,
    482 }
    483 impl<'rp_id, 'prf_first, 'prf_second>
    484     NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>
    485 {
    486     /// Creates a `NonDiscoverableCredentialRequestOptions` containing
    487     /// [`CredentialMediationRequirement::default`],
    488     /// [`PublicKeyCredentialRequestOptions::second_factor`], and the passed [`AllowedCredentials`].
    489     ///
    490     /// # Examples
    491     ///
    492     /// ```
    493     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    494     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    495     /// # use webauthn_rp::{
    496     /// #     request::{
    497     /// #         auth::{AllowedCredentials, NonDiscoverableCredentialRequestOptions},
    498     /// #         AsciiDomain, RpId, PublicKeyCredentialDescriptor, Credentials
    499     /// #     },
    500     /// #     response::{AuthTransports, CredentialId},
    501     /// # };
    502     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    503     /// /// from the database.
    504     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    505     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    506     ///     // ⋮
    507     /// #     AuthTransports::decode(32)
    508     /// }
    509     /// let mut creds = AllowedCredentials::with_capacity(1);
    510     /// assert!(creds.as_ref().is_empty());
    511     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    512     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    513     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    514     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    515     /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?;
    516     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    517     /// let transports = get_transports((&id).into())?;
    518     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    519     /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into()));
    520     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    521     /// assert_eq!(
    522     ///     NonDiscoverableCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), creds)
    523     ///         .allow_credentials
    524     ///         .len(),
    525     ///     1
    526     /// );
    527     /// # Ok::<_, webauthn_rp::AggErr>(())
    528     /// ```
    529     #[expect(single_use_lifetimes, reason = "false positive")]
    530     #[inline]
    531     #[must_use]
    532     pub fn second_factor<'a: 'rp_id>(
    533         rp_id: &'a RpId,
    534         allow_credentials: AllowedCredentials,
    535     ) -> Self {
    536         Self {
    537             mediation: CredentialMediationRequirement::default(),
    538             options: PublicKeyCredentialRequestOptions::second_factor(rp_id),
    539             allow_credentials,
    540         }
    541     }
    542     /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming
    543     /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `NonDiscoverableAuthenticationClientState`
    544     /// MUST be sent ASAP. In order to complete authentication, the returned `NonDiscoverableAuthenticationServerState`
    545     /// MUST be saved so that it can later be used to verify the credential assertion with
    546     /// [`NonDiscoverableAuthenticationServerState::verify`].
    547     ///
    548     /// # Errors
    549     ///
    550     /// Errors iff `self` contains incompatible configuration.
    551     #[inline]
    552     pub fn start_ceremony(
    553         self,
    554     ) -> Result<
    555         (
    556             NonDiscoverableAuthenticationServerState,
    557             NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
    558         ),
    559         NonDiscoverableCredentialRequestOptionsErr,
    560     > {
    561         if self.allow_credentials.is_empty() {
    562             Err(NonDiscoverableCredentialRequestOptionsErr::EmptyAllowedCredentials)
    563         } else if matches!(self.mediation, CredentialMediationRequirement::Conditional) {
    564             Err(NonDiscoverableCredentialRequestOptionsErr::ConditionalMediationRequested)
    565         } else {
    566             let extensions = self.options.extensions.into();
    567             if validate_discoverable_options_helper(extensions, self.options.user_verification) {
    568                 let allow_credentials = Box::from(&self.allow_credentials);
    569                 validate_non_discoverable_options_helper(
    570                     self.options.user_verification,
    571                     &allow_credentials,
    572                 )
    573                 .and_then(|()| {
    574                     #[cfg(not(feature = "serializable_server_state"))]
    575                     let res = Instant::now();
    576                     #[cfg(feature = "serializable_server_state")]
    577                     let res = SystemTime::now();
    578                     res.checked_add(Duration::from_millis(
    579                         NonZeroU64::from(self.options.timeout).get(),
    580                     ))
    581                     .ok_or(NonDiscoverableCredentialRequestOptionsErr::InvalidTimeout)
    582                     .map(|expiration| {
    583                         (
    584                             NonDiscoverableAuthenticationServerState {
    585                                 state: AuthenticationServerState {
    586                                     challenge: SentChallenge(self.options.challenge.0),
    587                                     user_verification: self.options.user_verification,
    588                                     extensions,
    589                                     expiration,
    590                                 },
    591                                 allow_credentials,
    592                             },
    593                             NonDiscoverableAuthenticationClientState(self),
    594                         )
    595                     })
    596                 })
    597             } else {
    598                 Err(NonDiscoverableCredentialRequestOptionsErr::PrfWithoutUserVerification)
    599             }
    600         }
    601     }
    602 }
    603 /// The [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions)
    604 /// to send to the client when authenticating a credential.
    605 ///
    606 /// This does _not_ contain [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    607 #[derive(Debug)]
    608 pub struct PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    609     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge).
    610     pub challenge: Challenge,
    611     /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-timeout).
    612     ///
    613     /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives
    614     /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled
    615     /// when authenticating credentials as no timeout would make out-of-memory (OOM) conditions more likely.
    616     pub timeout: NonZeroU32,
    617     /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-rpid).
    618     ///
    619     /// This MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when the credential was registered.
    620     pub rp_id: &'rp_id RpId,
    621     /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification).
    622     pub user_verification: UserVerificationRequirement,
    623     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints).
    624     pub hints: Hints,
    625     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions).
    626     pub extensions: Extension<'prf_first, 'prf_second>,
    627 }
    628 impl<'rp_id> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> {
    629     /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to
    630     /// [`UserVerificationRequirement::Required`] and [`Self::timeout`] set to [`FIVE_MINUTES`].
    631     ///
    632     /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the
    633     /// credential was registered.
    634     ///
    635     /// # Examples
    636     ///
    637     /// ```
    638     /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    639     /// assert!(matches!(
    640     ///     PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification,
    641     ///     UserVerificationRequirement::Required
    642     /// ));
    643     /// # Ok::<_, webauthn_rp::AggErr>(())
    644     /// ```
    645     #[expect(single_use_lifetimes, reason = "false positive")]
    646     #[inline]
    647     #[must_use]
    648     pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    649         Self {
    650             challenge: Challenge::new(),
    651             timeout: FIVE_MINUTES,
    652             rp_id,
    653             user_verification: UserVerificationRequirement::Required,
    654             hints: Hints::EMPTY,
    655             extensions: Extension::default(),
    656         }
    657     }
    658     /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to
    659     /// [`UserVerificationRequirement::Discouraged`] and [`Self::timeout`] set to [`FIVE_MINUTES`].
    660     ///
    661     /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the
    662     /// credentials were registered.
    663     ///
    664     /// # Examples
    665     ///
    666     /// ```
    667     /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    668     /// assert!(matches!(
    669     ///     PublicKeyCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification,
    670     ///     UserVerificationRequirement::Discouraged
    671     /// ));
    672     /// # Ok::<_, webauthn_rp::AggErr>(())
    673     /// ```
    674     #[expect(single_use_lifetimes, reason = "false positive")]
    675     #[inline]
    676     #[must_use]
    677     pub fn second_factor<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    678         let mut opts = Self::passkey(rp_id);
    679         opts.user_verification = UserVerificationRequirement::Discouraged;
    680         opts
    681     }
    682 }
    683 /// Container of a [`DiscoverableCredentialRequestOptions`] that has been used to start the authentication ceremony.
    684 /// This gets sent to the client ASAP.
    685 #[derive(Debug)]
    686 pub struct DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>(
    687     DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    688 );
    689 impl<'rp_id, 'prf_first, 'prf_second>
    690     DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>
    691 {
    692     /// Returns the `DiscoverableCredentialRequestOptions` that was used to start an authentication ceremony.
    693     #[inline]
    694     #[must_use]
    695     pub const fn options(
    696         &self,
    697     ) -> &DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    698         &self.0
    699     }
    700 }
    701 /// Container of a [`NonDiscoverableCredentialRequestOptions`] that has been used to start the authentication
    702 /// ceremony. This gets sent to the client ASAP.
    703 #[derive(Debug)]
    704 pub struct NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>(
    705     NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    706 );
    707 impl<'rp_id, 'prf_first, 'prf_second>
    708     NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>
    709 {
    710     /// Returns the `NonDiscoverableCredentialRequestOptions` that was used to start an authentication ceremony.
    711     #[inline]
    712     #[must_use]
    713     pub const fn options(
    714         &self,
    715     ) -> &NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    716         &self.0
    717     }
    718 }
    719 /// The possible combinations of an [`AuthenticatedCredential`]'s [`StaticState`]'s
    720 /// `extensions.hmac_secret` and `client_extension_results.prf`.
    721 ///
    722 /// Note we ensure in `crate::verify_static_and_dynamic_state` that `hmac_secret` does not exist when
    723 /// `prf` does not exist, `hmac_secret` does not exist or is `false` when `prf` is `false`, or
    724 /// `hmac_secret` does not exist or is `true` when `prf` is `true`.
    725 #[derive(Clone, Copy)]
    726 enum CredPrf {
    727     /// No `prf` or `hmac_secret`.
    728     None,
    729     /// `prf.enabled` is `false` but there is no `hmac_secret`.
    730     FalseNoHmac,
    731     /// `prf.enabled` and `hmac_secret` are `false`.
    732     FalseFalseHmac,
    733     /// `prf.enabled` is `true` but there is no `hmac_secret`.
    734     TrueNoHmac,
    735     /// `prf.enabled` and `hmac_secret` are `true`.
    736     TrueTrueHmac,
    737 }
    738 /// `PrfInput` and `PrfInputOwned` without the actual data sent to reduce memory usage when storing
    739 /// [`DiscoverableAuthenticationServerState`] in an in-memory collection.
    740 #[derive(Clone, Copy, Debug)]
    741 enum ServerPrfInfo {
    742     /// No `PrfInput`.
    743     None,
    744     /// `PrfInput::second` was `None`.
    745     One(ExtensionReq),
    746     /// `PrfInput::second` was `Some`.
    747     Two(ExtensionReq),
    748 }
    749 impl ServerPrfInfo {
    750     /// Validates `val` based on the passed arguments.
    751     ///
    752     /// It's not possible to request the PRF extension without sending `UserVerificationRequirement::Required`;
    753     /// thus `user_verified` will always be `true` when sending PRF; otherwise ceremony validation will error.
    754     /// However when we _don't_ send the PRF extension _and_ we don't error on an unsolicited response, it's
    755     /// possible to receive an `HmacSecret` without the user having been verified; thus we only ensure
    756     /// `user_verified` is true when we don't error on unsolicted responses _and_ we didn't send the PRF extension.
    757     const fn validate(
    758         self,
    759         user_verified: bool,
    760         cred_prf: CredPrf,
    761         hmac: HmacSecret,
    762         err_unsolicited: bool,
    763     ) -> Result<(), ExtensionErr> {
    764         match hmac {
    765             HmacSecret::None => match self {
    766                 Self::None => Ok(()),
    767                 Self::One(req) | Self::Two(req) => {
    768                     if matches!(req, ExtensionReq::Allow)
    769                         || !matches!(cred_prf, CredPrf::TrueTrueHmac)
    770                     {
    771                         Ok(())
    772                     } else {
    773                         Err(ExtensionErr::MissingHmacSecret)
    774                     }
    775                 }
    776             },
    777             HmacSecret::One => match self {
    778                 Self::None => {
    779                     if err_unsolicited {
    780                         Err(ExtensionErr::ForbiddenHmacSecret)
    781                     } else if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) {
    782                         if user_verified {
    783                             Ok(())
    784                         } else {
    785                             Err(ExtensionErr::UserNotVerifiedHmacSecret)
    786                         }
    787                     } else {
    788                         Err(ExtensionErr::HmacSecretForNonHmacSecretCredential)
    789                     }
    790                 }
    791                 Self::One(_) => {
    792                     if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) {
    793                         Ok(())
    794                     } else {
    795                         Err(ExtensionErr::HmacSecretForNonHmacSecretCredential)
    796                     }
    797                 }
    798                 Self::Two(_) => Err(ExtensionErr::InvalidHmacSecretValue(
    799                     OneOrTwo::Two,
    800                     OneOrTwo::One,
    801                 )),
    802             },
    803             HmacSecret::Two => match self {
    804                 Self::None => {
    805                     if err_unsolicited {
    806                         Err(ExtensionErr::ForbiddenHmacSecret)
    807                     } else if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) {
    808                         if user_verified {
    809                             Ok(())
    810                         } else {
    811                             Err(ExtensionErr::UserNotVerifiedHmacSecret)
    812                         }
    813                     } else {
    814                         Err(ExtensionErr::HmacSecretForNonHmacSecretCredential)
    815                     }
    816                 }
    817                 Self::One(_) => Err(ExtensionErr::InvalidHmacSecretValue(
    818                     OneOrTwo::One,
    819                     OneOrTwo::Two,
    820                 )),
    821                 Self::Two(_) => {
    822                     if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) {
    823                         Ok(())
    824                     } else {
    825                         Err(ExtensionErr::HmacSecretForNonHmacSecretCredential)
    826                     }
    827                 }
    828             },
    829         }
    830     }
    831 }
    832 #[cfg(test)]
    833 impl PartialEq for ServerPrfInfo {
    834     fn eq(&self, other: &Self) -> bool {
    835         match *self {
    836             Self::None => matches!(*other, Self::None),
    837             Self::One(req) => matches!(*other, Self::One(req2) if req == req2),
    838             Self::Two(req) => matches!(*other, Self::Two(req2) if req == req2),
    839         }
    840     }
    841 }
    842 impl From<Option<(PrfInput<'_, '_>, ExtensionReq)>> for ServerPrfInfo {
    843     fn from(value: Option<(PrfInput<'_, '_>, ExtensionReq)>) -> Self {
    844         value.map_or(Self::None, |val| {
    845             val.0
    846                 .second
    847                 .map_or_else(|| Self::One(val.1), |_| Self::Two(val.1))
    848         })
    849     }
    850 }
    851 impl From<&PrfInputOwned> for ServerPrfInfo {
    852     fn from(value: &PrfInputOwned) -> Self {
    853         value
    854             .second
    855             .as_ref()
    856             .map_or_else(|| Self::One(value.ext_req), |_| Self::Two(value.ext_req))
    857     }
    858 }
    859 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`]
    860 /// in an in-memory collection.
    861 #[derive(Clone, Copy, Debug)]
    862 struct ServerExtensionInfo {
    863     /// `Extension::prf`.
    864     prf: ServerPrfInfo,
    865 }
    866 impl From<Extension<'_, '_>> for ServerExtensionInfo {
    867     fn from(value: Extension<'_, '_>) -> Self {
    868         Self {
    869             prf: value.prf.into(),
    870         }
    871     }
    872 }
    873 #[cfg(test)]
    874 impl PartialEq for ServerExtensionInfo {
    875     fn eq(&self, other: &Self) -> bool {
    876         self.prf == other.prf
    877     }
    878 }
    879 /// `CredentialSpecificExtension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`]
    880 /// in an in-memory collection.
    881 #[derive(Clone, Copy, Debug)]
    882 struct ServerCredSpecificExtensionInfo {
    883     /// `CredentialSpecificExtension::prf`.
    884     prf: ServerPrfInfo,
    885 }
    886 #[cfg(test)]
    887 impl PartialEq for ServerCredSpecificExtensionInfo {
    888     fn eq(&self, other: &Self) -> bool {
    889         self.prf == other.prf
    890     }
    891 }
    892 impl From<&CredentialSpecificExtension> for ServerCredSpecificExtensionInfo {
    893     fn from(value: &CredentialSpecificExtension) -> Self {
    894         Self {
    895             prf: value
    896                 .prf
    897                 .as_ref()
    898                 .map_or(ServerPrfInfo::None, ServerPrfInfo::from),
    899         }
    900     }
    901 }
    902 impl ServerExtensionInfo {
    903     /// Validates the extensions.
    904     ///
    905     /// Note that this MUST only be called internally by `auth::validate_extensions`.
    906     const fn validate_extensions(
    907         self,
    908         user_verified: bool,
    909         auth_ext: AuthenticatorExtensionOutput,
    910         error_unsolicited: bool,
    911         cred_prf: CredPrf,
    912     ) -> Result<(), ExtensionErr> {
    913         ServerPrfInfo::validate(
    914             self.prf,
    915             user_verified,
    916             cred_prf,
    917             auth_ext.hmac_secret,
    918             error_unsolicited,
    919         )
    920     }
    921 }
    922 /// Validates the extensions.
    923 fn validate_extensions(
    924     ext: ServerExtensionInfo,
    925     user_verified: bool,
    926     cred_ext: Option<ServerCredSpecificExtensionInfo>,
    927     auth_ext: AuthenticatorExtensionOutput,
    928     error_unsolicited: bool,
    929     cred_prf: CredPrf,
    930 ) -> Result<(), ExtensionErr> {
    931     cred_ext.map_or_else(
    932         || {
    933             // No credental-specific extensions, so we can simply focus on `ext`.
    934             ext.validate_extensions(user_verified, auth_ext, error_unsolicited, cred_prf)
    935         },
    936         |c_ext| {
    937             // Must carefully process each extension based on overlap and which gets priority over the other.
    938             if matches!(c_ext.prf, ServerPrfInfo::None) {
    939                 ext.prf.validate(
    940                     user_verified,
    941                     cred_prf,
    942                     auth_ext.hmac_secret,
    943                     error_unsolicited,
    944                 )
    945             } else {
    946                 c_ext.prf.validate(
    947                     user_verified,
    948                     cred_prf,
    949                     auth_ext.hmac_secret,
    950                     error_unsolicited,
    951                 )
    952             }
    953         },
    954     )
    955 }
    956 /// [`AllowedCredential`] with less data to reduce memory usage when storing [`AuthenticationServerState`]
    957 /// in an in-memory collection.
    958 #[derive(Debug)]
    959 struct CredInfo {
    960     /// The Credential ID.
    961     id: CredentialId<Box<[u8]>>,
    962     /// Any credential-specific extensions.
    963     ext: ServerCredSpecificExtensionInfo,
    964 }
    965 #[cfg(test)]
    966 impl PartialEq for CredInfo {
    967     fn eq(&self, other: &Self) -> bool {
    968         self.id == other.id && self.ext == other.ext
    969     }
    970 }
    971 /// Controls how to handle a change in [`DynamicState::authenticator_attachment`].
    972 ///
    973 /// Note when `DynamicState::authenticator_attachment` is [`AuthenticatorAttachment::None`], then it will
    974 /// be updated regardless. Similarly when [`Authentication::authenticator_attachment`] is
    975 /// `AuthenticatorAttachment::None`, it will never update `DynamicState::authenticator_attachment`.
    976 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    977 pub enum AuthenticatorAttachmentEnforcement {
    978     /// Fail the authentication ceremony if [`AuthenticatorAttachment`] is not the same.
    979     ///
    980     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    981     Fail(bool),
    982     /// Update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`].
    983     ///
    984     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    985     Update(bool),
    986     /// Do not update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`].
    987     ///
    988     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    989     Ignore(bool),
    990 }
    991 impl AuthenticatorAttachmentEnforcement {
    992     /// Validates `cur` based on `self` and `prev`.
    993     const fn validate(
    994         self,
    995         prev: AuthenticatorAttachment,
    996         cur: AuthenticatorAttachment,
    997     ) -> Result<AuthenticatorAttachment, AuthCeremonyErr> {
    998         match cur {
    999             AuthenticatorAttachment::None => match self {
   1000                 Self::Fail(require) | Self::Update(require) | Self::Ignore(require) => {
   1001                     if require {
   1002                         Err(AuthCeremonyErr::MissingAuthenticatorAttachment)
   1003                     } else {
   1004                         // We don't overwrite the previous one with [`AuthenticatorAttachment::None`].
   1005                         Ok(prev)
   1006                     }
   1007                 }
   1008             },
   1009             AuthenticatorAttachment::Platform => match self {
   1010                 Self::Fail(_) => {
   1011                     if matches!(prev, AuthenticatorAttachment::CrossPlatform) {
   1012                         Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch)
   1013                     } else {
   1014                         // We don't fail when we previously had [`AuthenticatorAttachment::None`].
   1015                         Ok(cur)
   1016                     }
   1017                 }
   1018                 Self::Update(_) => Ok(cur),
   1019                 Self::Ignore(_) => {
   1020                     if matches!(prev, AuthenticatorAttachment::None) {
   1021                         // We overwrite the previous one when it is [`AuthenticatorAttachment::None`].
   1022                         Ok(cur)
   1023                     } else {
   1024                         Ok(prev)
   1025                     }
   1026                 }
   1027             },
   1028             AuthenticatorAttachment::CrossPlatform => match self {
   1029                 Self::Fail(_) => {
   1030                     if matches!(prev, AuthenticatorAttachment::Platform) {
   1031                         Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch)
   1032                     } else {
   1033                         // We don't fail when we previously had [`AuthenticatorAttachment::None`].
   1034                         Ok(cur)
   1035                     }
   1036                 }
   1037                 Self::Update(_) => Ok(cur),
   1038                 Self::Ignore(_) => {
   1039                     if matches!(prev, AuthenticatorAttachment::None) {
   1040                         // We overwrite the previous one when it is [`AuthenticatorAttachment::None`].
   1041                         Ok(cur)
   1042                     } else {
   1043                         Ok(prev)
   1044                     }
   1045                 }
   1046             },
   1047         }
   1048     }
   1049 }
   1050 impl Default for AuthenticatorAttachmentEnforcement {
   1051     /// Returns [`Self::Ignore`] containing `false`.
   1052     #[inline]
   1053     fn default() -> Self {
   1054         Self::Ignore(false)
   1055     }
   1056 }
   1057 /// Backup state requirements for the credential.
   1058 ///
   1059 /// Note the backup eligibility of a credential is not allowed to change. This is only used to allow one to
   1060 /// require the existence or lack of existence of a backup.
   1061 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1062 pub enum BackupStateReq {
   1063     /// No requirements on the backup state.
   1064     ///
   1065     /// The backup eligibility of the credential must be the same as required by the spec, but the existence
   1066     /// of a backup doesn't matter.
   1067     #[default]
   1068     None,
   1069     /// Credential must be backed up.
   1070     Exists,
   1071     /// Credential must not be backed up.
   1072     DoesntExist,
   1073 }
   1074 /// Additional verification options to perform in [`DiscoverableAuthenticationServerState::verify`] and
   1075 /// [`NonDiscoverableAuthenticationServerState::verify`].
   1076 #[derive(Clone, Copy, Debug)]
   1077 pub struct AuthenticationVerificationOptions<'origins, 'top_origins, O, T> {
   1078     /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   1079     ///
   1080     /// When this is empty, the origin that will be used will be based on the [`RpId`] passed to
   1081     /// [`DiscoverableAuthenticationServerState::verify`] or [`NonDiscoverableAuthenticationServerState::verify`].
   1082     /// If [`RpId::Domain`] or [`RpId::StaticDomain`], then the [`DomainOrigin`] returned from passing
   1083     /// [`AsciiDomain::as_ref`] and [`AsciiDomainStatic::as_str`] to [`DomainOrigin::new`] respectively will be
   1084     /// used; otherwise the [`Url`] in [`RpId::Url`] will be used.
   1085     pub allowed_origins: &'origins [O],
   1086     /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin)
   1087     /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   1088     ///
   1089     /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained
   1090     /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`,
   1091     /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`.
   1092     pub allowed_top_origins: Option<&'top_origins [T]>,
   1093     /// The required [`Backup`] state of the credential.
   1094     pub backup_state_requirement: BackupStateReq,
   1095     /// Error when unsolicited extensions are sent back iff `true`.
   1096     pub error_on_unsolicited_extensions: bool,
   1097     /// Dictates what happens when [`Authentication::authenticator_attachment`] is not the same as
   1098     /// [`DynamicState::authenticator_attachment`].
   1099     pub auth_attachment_enforcement: AuthenticatorAttachmentEnforcement,
   1100     /// [`DynamicState::user_verified`] will be set to `true` iff [`Flag::user_verified`] when `true`.
   1101     pub update_uv: bool,
   1102     /// Dictates what happens when [`AuthenticatorData::sign_count`] is not updated to a strictly greater value.
   1103     pub sig_counter_enforcement: SignatureCounterEnforcement,
   1104     /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`.
   1105     #[cfg(feature = "serde_relaxed")]
   1106     pub client_data_json_relaxed: bool,
   1107 }
   1108 impl<O, T> AuthenticationVerificationOptions<'_, '_, O, T> {
   1109     /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`,
   1110     /// [`Self::backup_state_requirement`] is [`BackupStateReq::None`], [`Self::error_on_unsolicited_extensions`] is
   1111     /// `true`, [`Self::auth_attachment_enforcement`] is [`AuthenticatorAttachmentEnforcement::default`],
   1112     /// [`Self::update_uv`] is `false`, [`Self::sig_counter_enforcement`] is
   1113     /// [`SignatureCounterEnforcement::default`], and [`Self::client_data_json_relaxed`] is `true`.
   1114     ///
   1115     /// Note `O` and `T` should implement `PartialEq<Origin<'_>>` (e.g., `&str`).
   1116     #[inline]
   1117     #[must_use]
   1118     pub const fn new() -> Self {
   1119         Self {
   1120             allowed_origins: [].as_slice(),
   1121             allowed_top_origins: None,
   1122             backup_state_requirement: BackupStateReq::None,
   1123             error_on_unsolicited_extensions: true,
   1124             auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::Ignore(false),
   1125             update_uv: false,
   1126             sig_counter_enforcement: SignatureCounterEnforcement::Fail,
   1127             #[cfg(feature = "serde_relaxed")]
   1128             client_data_json_relaxed: true,
   1129         }
   1130     }
   1131 }
   1132 impl<O, T> Default for AuthenticationVerificationOptions<'_, '_, O, T> {
   1133     /// Same as [`Self::new`].
   1134     #[inline]
   1135     fn default() -> Self {
   1136         Self::new()
   1137     }
   1138 }
   1139 // This is essentially the `DiscoverableCredentialRequestOptions` used to create it; however to reduce
   1140 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size
   1141 // `x86_64-unknown-linux-gnu` platforms.
   1142 //
   1143 /// State needed to be saved when beginning the authentication ceremony.
   1144 ///
   1145 /// Saves the necessary information associated with the [`DiscoverableCredentialRequestOptions`] used to create it
   1146 /// via [`DiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be
   1147 /// performed with [`Self::verify`].
   1148 ///
   1149 /// `DiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct
   1150 /// `DiscoverableAuthenticationServerState` associated with a [`DiscoverableAuthentication`], one should use its
   1151 /// corresponding [`DiscoverableAuthentication::challenge`].
   1152 #[derive(Debug)]
   1153 pub struct DiscoverableAuthenticationServerState(AuthenticationServerState);
   1154 impl DiscoverableAuthenticationServerState {
   1155     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1156     /// iff `cred` was mutated.
   1157     ///
   1158     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1159     /// ceremony.
   1160     ///
   1161     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1162     /// is returned.
   1163     ///
   1164     /// # Errors
   1165     ///
   1166     /// Errors iff `response` is not valid according to the
   1167     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1168     /// of the settings in `options`.
   1169     #[inline]
   1170     pub fn verify<
   1171         'a,
   1172         const USER_LEN: usize,
   1173         O: PartialEq<Origin<'a>>,
   1174         T: PartialEq<Origin<'a>>,
   1175         MlDsa87Key: AsRef<[u8]>,
   1176         MlDsa65Key: AsRef<[u8]>,
   1177         MlDsa44Key: AsRef<[u8]>,
   1178         EdKey: AsRef<[u8]>,
   1179         P256Key: AsRef<[u8]>,
   1180         P384Key: AsRef<[u8]>,
   1181         RsaKey: AsRef<[u8]>,
   1182     >(
   1183         self,
   1184         rp_id: &RpId,
   1185         response: &'a DiscoverableAuthentication<USER_LEN>,
   1186         cred: &mut AuthenticatedCredential<
   1187             '_,
   1188             '_,
   1189             USER_LEN,
   1190             CompressedPubKey<MlDsa87Key, MlDsa65Key, MlDsa44Key, EdKey, P256Key, P384Key, RsaKey>,
   1191         >,
   1192         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1193     ) -> Result<bool, AuthCeremonyErr> {
   1194         // Step 6 item 2.
   1195         if cred.user_id == response.response.user_handle() {
   1196             // Step 6 item 2.
   1197             if cred.id.as_ref() == response.raw_id.as_ref() {
   1198                 self.0.verify(rp_id, response, cred, options, None)
   1199             } else {
   1200                 Err(AuthCeremonyErr::CredentialIdMismatch)
   1201             }
   1202         } else {
   1203             Err(AuthCeremonyErr::UserHandleMismatch)
   1204         }
   1205     }
   1206     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1207     fn is_eq(&self, other: &Self) -> bool {
   1208         self.0.is_eq(&other.0)
   1209     }
   1210 }
   1211 // This is essentially the `NonDiscoverableCredentialRequestOptions` used to create it; however to reduce
   1212 // memory usage, we remove all unnecessary data making an instance of this as small as 64 bytes in size on
   1213 // `x86_64-unknown-linux-gnu` platforms. This does not include the size of each `CredInfo` which should exist
   1214 // elsewhere on the heap but obviously contributes memory overall.
   1215 /// State needed to be saved when beginning the authentication ceremony.
   1216 ///
   1217 /// Saves the necessary information associated with the [`NonDiscoverableCredentialRequestOptions`] used to create
   1218 /// it via [`NonDiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be
   1219 /// performed with [`Self::verify`].
   1220 ///
   1221 /// `NonDiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the
   1222 /// correct `NonDiscoverableAuthenticationServerState` associated with a [`NonDiscoverableAuthentication`], one
   1223 /// should use its corresponding [`NonDiscoverableAuthentication::challenge`].
   1224 #[derive(Debug)]
   1225 pub struct NonDiscoverableAuthenticationServerState {
   1226     /// Most server state.
   1227     state: AuthenticationServerState,
   1228     /// The set of credentials that are allowed.
   1229     allow_credentials: Box<[CredInfo]>,
   1230 }
   1231 impl NonDiscoverableAuthenticationServerState {
   1232     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1233     /// iff `cred` was mutated.
   1234     ///
   1235     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1236     /// ceremony.
   1237     ///
   1238     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1239     /// is returned.
   1240     ///
   1241     /// # Errors
   1242     ///
   1243     /// Errors iff `response` is not valid according to the
   1244     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1245     /// of the settings in `options`.
   1246     #[inline]
   1247     pub fn verify<
   1248         'a,
   1249         const USER_LEN: usize,
   1250         O: PartialEq<Origin<'a>>,
   1251         T: PartialEq<Origin<'a>>,
   1252         MlDsa87Key: AsRef<[u8]>,
   1253         MlDsa65Key: AsRef<[u8]>,
   1254         MlDsa44Key: AsRef<[u8]>,
   1255         EdKey: AsRef<[u8]>,
   1256         P256Key: AsRef<[u8]>,
   1257         P384Key: AsRef<[u8]>,
   1258         RsaKey: AsRef<[u8]>,
   1259     >(
   1260         self,
   1261         rp_id: &RpId,
   1262         response: &'a NonDiscoverableAuthentication<USER_LEN>,
   1263         cred: &mut AuthenticatedCredential<
   1264             '_,
   1265             '_,
   1266             USER_LEN,
   1267             CompressedPubKey<MlDsa87Key, MlDsa65Key, MlDsa44Key, EdKey, P256Key, P384Key, RsaKey>,
   1268         >,
   1269         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1270     ) -> Result<bool, AuthCeremonyErr> {
   1271         response
   1272             .response
   1273             .user_handle()
   1274             .as_ref()
   1275             .map_or(Ok(()), |user| {
   1276                 // Step 6 item 1.
   1277                 if *user == cred.user_id() {
   1278                     Ok(())
   1279                 } else {
   1280                     Err(AuthCeremonyErr::UserHandleMismatch)
   1281                 }
   1282             })
   1283             .and_then(|()| {
   1284                 self.allow_credentials
   1285                     .iter()
   1286                     // Step 5.
   1287                     .find(|c| c.id == response.raw_id)
   1288                     .ok_or(AuthCeremonyErr::NoMatchingAllowedCredential)
   1289                     .and_then(|c| {
   1290                         // Step 6 item 1.
   1291                         if c.id.as_ref() == cred.id.as_ref() {
   1292                             self.state
   1293                                 .verify(rp_id, response, cred, options, Some(c.ext))
   1294                         } else {
   1295                             Err(AuthCeremonyErr::CredentialIdMismatch)
   1296                         }
   1297                     })
   1298             })
   1299     }
   1300     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1301     fn is_eq(&self, other: &Self) -> bool {
   1302         self.state.is_eq(&other.state) && self.allow_credentials == other.allow_credentials
   1303     }
   1304 }
   1305 // This is essentially the `PublicKeyCredentialRequestOptions` used to create it; however to reduce
   1306 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size on
   1307 // `x86_64-unknown-linux-gnu` platforms.
   1308 /// Shared state used by [`DiscoverableAuthenticationServerState`] and [`NonDiscoverableAuthenticationServerState`].
   1309 #[derive(Debug)]
   1310 struct AuthenticationServerState {
   1311     // This is a `SentChallenge` since we need `AuthenticationServerState` to be fetchable after receiving the
   1312     // response from the client. This response must obviously be constructable; thus its challenge is a
   1313     // `SentChallenge`.
   1314     //
   1315     // This must never be mutated since we want to ensure it is actually a `Challenge` (which
   1316     // can only be constructed via `Challenge::new`). This is guaranteed to be true iff
   1317     // `serializable_server_state` is not enabled.
   1318     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge).
   1319     challenge: SentChallenge,
   1320     /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification).
   1321     user_verification: UserVerificationRequirement,
   1322     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions).
   1323     extensions: ServerExtensionInfo,
   1324     /// `Instant` the ceremony expires.
   1325     #[cfg(not(feature = "serializable_server_state"))]
   1326     expiration: Instant,
   1327     /// `SystemTime` the ceremony expires.
   1328     #[cfg(feature = "serializable_server_state")]
   1329     expiration: SystemTime,
   1330 }
   1331 impl AuthenticationServerState {
   1332     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1333     /// iff `cred` was mutated.
   1334     ///
   1335     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1336     /// ceremony.
   1337     ///
   1338     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1339     /// is returned.
   1340     ///
   1341     /// # Errors
   1342     ///
   1343     /// Errors iff `response` is not valid according to the
   1344     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1345     /// of the settings in `options`.
   1346     fn verify<
   1347         'a,
   1348         const USER_LEN: usize,
   1349         const DISCOVERABLE: bool,
   1350         O: PartialEq<Origin<'a>>,
   1351         T: PartialEq<Origin<'a>>,
   1352         MlDsa87Key: AsRef<[u8]>,
   1353         MlDsa65Key: AsRef<[u8]>,
   1354         MlDsa44Key: AsRef<[u8]>,
   1355         EdKey: AsRef<[u8]>,
   1356         P256Key: AsRef<[u8]>,
   1357         P384Key: AsRef<[u8]>,
   1358         RsaKey: AsRef<[u8]>,
   1359     >(
   1360         self,
   1361         rp_id: &RpId,
   1362         response: &'a Authentication<USER_LEN, DISCOVERABLE>,
   1363         cred: &mut AuthenticatedCredential<
   1364             '_,
   1365             '_,
   1366             USER_LEN,
   1367             CompressedPubKey<MlDsa87Key, MlDsa65Key, MlDsa44Key, EdKey, P256Key, P384Key, RsaKey>,
   1368         >,
   1369         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1370         cred_ext: Option<ServerCredSpecificExtensionInfo>,
   1371     ) -> Result<bool, AuthCeremonyErr> {
   1372         // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion)
   1373         // is handled by:
   1374         //
   1375         // 1. Calling code.
   1376         // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]).
   1377         // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]).
   1378         // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]).
   1379         // 5. [`NonDiscoverableAuthenticationServerState::verify`].
   1380         // 6. [`DiscoverableAuthenticationServerState::verify`] and [`NonDiscoverableAuthenticationServerState::verify`].
   1381         // 7. Informative only in that it defines variables.
   1382         // 8. [`Self::partial_validate`].
   1383         // 9. [`Self::partial_validate`].
   1384         // 10. [`Self::partial_validate`].
   1385         // 11. [`Self::partial_validate`].
   1386         // 12. [`Self::partial_validate`].
   1387         // 13. [`Self::partial_validate`].
   1388         // 14. [`Self::partial_validate`].
   1389         // 15. [`Self::partial_validate`].
   1390         // 16. [`Self::partial_validate`].
   1391         // 17. [`Self::partial_validate`].
   1392         // 18. [`Self::partial_validate`].
   1393         // 19. [`Self::partial_validate`].
   1394         // 20. [`Self::partial_validate`].
   1395         // 21. [`Self::partial_validate`].
   1396         // 22. Below.
   1397         // 23. Below.
   1398         // 24. Below.
   1399         // 25. Below.
   1400 
   1401         // Steps 8–21.
   1402         self.partial_validate(
   1403             rp_id,
   1404             response,
   1405             (&cred.static_state.credential_public_key).into(),
   1406             &CeremonyOptions {
   1407                 allowed_origins: options.allowed_origins,
   1408                 allowed_top_origins: options.allowed_top_origins,
   1409                 backup_requirement: if cred.dynamic_state.backup == Backup::NotEligible {
   1410                     BackupReq::NotEligible
   1411                 } else {
   1412                     match options.backup_state_requirement {
   1413                         BackupStateReq::None => BackupReq::Eligible,
   1414                         BackupStateReq::Exists => BackupReq::Exists,
   1415                         BackupStateReq::DoesntExist => BackupReq::EligibleNotExists,
   1416                     }
   1417                 },
   1418                 #[cfg(feature = "serde_relaxed")]
   1419                 client_data_json_relaxed: options.client_data_json_relaxed,
   1420             },
   1421         )
   1422         .map_err(AuthCeremonyErr::from)
   1423         .and_then(|auth_data| {
   1424             options
   1425                 .auth_attachment_enforcement
   1426                 .validate(
   1427                     cred.dynamic_state.authenticator_attachment,
   1428                     response.authenticator_attachment,
   1429                 )
   1430                 .and_then(|auth_attachment| {
   1431                     let flags = auth_data.flags();
   1432                     // Step 23.
   1433                     validate_extensions(
   1434                         self.extensions,
   1435                         flags.user_verified,
   1436                         cred_ext,
   1437                         auth_data.extensions(),
   1438                         options.error_on_unsolicited_extensions,
   1439                         cred.static_state.client_extension_results.prf.map_or(
   1440                             CredPrf::None,
   1441                             |prf| {
   1442                                 if prf.enabled {
   1443                                     cred.static_state
   1444                                         .extensions
   1445                                         .hmac_secret
   1446                                         .map_or(CredPrf::TrueNoHmac, |_| CredPrf::TrueTrueHmac)
   1447                                 } else {
   1448                                     cred.static_state
   1449                                         .extensions
   1450                                         .hmac_secret
   1451                                         .map_or(CredPrf::FalseNoHmac, |_| CredPrf::FalseFalseHmac)
   1452                                 }
   1453                             },
   1454                         ),
   1455                     )
   1456                     .map_err(AuthCeremonyErr::Extension)
   1457                     .and_then(|()| {
   1458                         // Step 22.
   1459                         options
   1460                             .sig_counter_enforcement
   1461                             .validate(cred.dynamic_state.sign_count, auth_data.sign_count())
   1462                             .and_then(|sig_counter| {
   1463                                 let prev_dyn_state = cred.dynamic_state;
   1464                                 // Step 24 item 2.
   1465                                 cred.dynamic_state.backup = flags.backup;
   1466                                 if options.update_uv && flags.user_verified {
   1467                                     // Step 24 item 3.
   1468                                     cred.dynamic_state.user_verified = true;
   1469                                 }
   1470                                 // Step 24 item 1.
   1471                                 cred.dynamic_state.sign_count = sig_counter;
   1472                                 cred.dynamic_state.authenticator_attachment = auth_attachment;
   1473                                 // Step 25.
   1474                                 if flags.user_verified {
   1475                                     Ok(())
   1476                                 } else {
   1477                                     match cred.static_state.extensions.cred_protect {
   1478                                         CredentialProtectionPolicy::None | CredentialProtectionPolicy::UserVerificationOptional => Ok(()),
   1479                                         CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
   1480                                             if DISCOVERABLE {
   1481                                                 Err(AuthCeremonyErr::DiscoverableCredProtectCredentialIdList)
   1482                                             } else {
   1483                                                 Ok(())
   1484                                             }
   1485                                         }
   1486                                         CredentialProtectionPolicy::UserVerificationRequired => {
   1487                                             Err(AuthCeremonyErr::UserNotVerifiedCredProtectRequired)
   1488                                         }
   1489                                     }
   1490                                 }.inspect_err(|_| {
   1491                                     cred.dynamic_state = prev_dyn_state;
   1492                                 }).map(|()| {
   1493                                     prev_dyn_state != cred.dynamic_state
   1494                                 })
   1495                             })
   1496                     })
   1497                 })
   1498         })
   1499     }
   1500     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1501     fn is_eq(&self, other: &Self) -> bool {
   1502         self.challenge == other.challenge
   1503             && self.user_verification == other.user_verification
   1504             && self.extensions == other.extensions
   1505             && self.expiration == other.expiration
   1506     }
   1507 }
   1508 impl TimedCeremony for AuthenticationServerState {
   1509     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1510     #[inline]
   1511     fn expiration(&self) -> Instant {
   1512         self.expiration
   1513     }
   1514     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1515     #[inline]
   1516     fn expiration(&self) -> SystemTime {
   1517         self.expiration
   1518     }
   1519 }
   1520 impl TimedCeremony for DiscoverableAuthenticationServerState {
   1521     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1522     #[inline]
   1523     fn expiration(&self) -> Instant {
   1524         self.0.expiration()
   1525     }
   1526     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1527     #[inline]
   1528     fn expiration(&self) -> SystemTime {
   1529         self.0.expiration()
   1530     }
   1531 }
   1532 impl TimedCeremony for NonDiscoverableAuthenticationServerState {
   1533     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1534     #[inline]
   1535     fn expiration(&self) -> Instant {
   1536         self.state.expiration()
   1537     }
   1538     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1539     #[inline]
   1540     fn expiration(&self) -> SystemTime {
   1541         self.state.expiration()
   1542     }
   1543 }
   1544 impl<const USER_LEN: usize, const DISCOVERABLE: bool> Ceremony<USER_LEN, DISCOVERABLE>
   1545     for AuthenticationServerState
   1546 {
   1547     type R = Authentication<USER_LEN, DISCOVERABLE>;
   1548     fn rand_challenge(&self) -> SentChallenge {
   1549         self.challenge
   1550     }
   1551     #[cfg(not(feature = "serializable_server_state"))]
   1552     fn expiry(&self) -> Instant {
   1553         self.expiration
   1554     }
   1555     #[cfg(feature = "serializable_server_state")]
   1556     fn expiry(&self) -> SystemTime {
   1557         self.expiration
   1558     }
   1559     fn user_verification(&self) -> UserVerificationRequirement {
   1560         self.user_verification
   1561     }
   1562 }
   1563 impl Borrow<SentChallenge> for AuthenticationServerState {
   1564     #[inline]
   1565     fn borrow(&self) -> &SentChallenge {
   1566         &self.challenge
   1567     }
   1568 }
   1569 impl PartialEq for AuthenticationServerState {
   1570     #[inline]
   1571     fn eq(&self, other: &Self) -> bool {
   1572         self.challenge == other.challenge
   1573     }
   1574 }
   1575 impl PartialEq<&Self> for AuthenticationServerState {
   1576     #[inline]
   1577     fn eq(&self, other: &&Self) -> bool {
   1578         *self == **other
   1579     }
   1580 }
   1581 impl PartialEq<AuthenticationServerState> for &AuthenticationServerState {
   1582     #[inline]
   1583     fn eq(&self, other: &AuthenticationServerState) -> bool {
   1584         **self == *other
   1585     }
   1586 }
   1587 impl Eq for AuthenticationServerState {}
   1588 impl Hash for AuthenticationServerState {
   1589     #[inline]
   1590     fn hash<H: Hasher>(&self, state: &mut H) {
   1591         self.challenge.hash(state);
   1592     }
   1593 }
   1594 impl PartialOrd for AuthenticationServerState {
   1595     #[inline]
   1596     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1597         Some(self.cmp(other))
   1598     }
   1599 }
   1600 impl Ord for AuthenticationServerState {
   1601     #[inline]
   1602     fn cmp(&self, other: &Self) -> Ordering {
   1603         self.challenge.cmp(&other.challenge)
   1604     }
   1605 }
   1606 impl Borrow<SentChallenge> for DiscoverableAuthenticationServerState {
   1607     #[inline]
   1608     fn borrow(&self) -> &SentChallenge {
   1609         self.0.borrow()
   1610     }
   1611 }
   1612 impl PartialEq for DiscoverableAuthenticationServerState {
   1613     #[inline]
   1614     fn eq(&self, other: &Self) -> bool {
   1615         self.0 == other.0
   1616     }
   1617 }
   1618 impl PartialEq<&Self> for DiscoverableAuthenticationServerState {
   1619     #[inline]
   1620     fn eq(&self, other: &&Self) -> bool {
   1621         self.0 == other.0
   1622     }
   1623 }
   1624 impl PartialEq<DiscoverableAuthenticationServerState> for &DiscoverableAuthenticationServerState {
   1625     #[inline]
   1626     fn eq(&self, other: &DiscoverableAuthenticationServerState) -> bool {
   1627         self.0 == other.0
   1628     }
   1629 }
   1630 impl Eq for DiscoverableAuthenticationServerState {}
   1631 impl Hash for DiscoverableAuthenticationServerState {
   1632     #[inline]
   1633     fn hash<H: Hasher>(&self, state: &mut H) {
   1634         self.0.hash(state);
   1635     }
   1636 }
   1637 impl PartialOrd for DiscoverableAuthenticationServerState {
   1638     #[inline]
   1639     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1640         Some(self.cmp(other))
   1641     }
   1642 }
   1643 impl Ord for DiscoverableAuthenticationServerState {
   1644     #[inline]
   1645     fn cmp(&self, other: &Self) -> Ordering {
   1646         self.0.cmp(&other.0)
   1647     }
   1648 }
   1649 impl Borrow<SentChallenge> for NonDiscoverableAuthenticationServerState {
   1650     #[inline]
   1651     fn borrow(&self) -> &SentChallenge {
   1652         self.state.borrow()
   1653     }
   1654 }
   1655 impl PartialEq for NonDiscoverableAuthenticationServerState {
   1656     #[inline]
   1657     fn eq(&self, other: &Self) -> bool {
   1658         self.state == other.state
   1659     }
   1660 }
   1661 impl PartialEq<&Self> for NonDiscoverableAuthenticationServerState {
   1662     #[inline]
   1663     fn eq(&self, other: &&Self) -> bool {
   1664         self.state == other.state
   1665     }
   1666 }
   1667 impl PartialEq<NonDiscoverableAuthenticationServerState>
   1668     for &NonDiscoverableAuthenticationServerState
   1669 {
   1670     #[inline]
   1671     fn eq(&self, other: &NonDiscoverableAuthenticationServerState) -> bool {
   1672         self.state == other.state
   1673     }
   1674 }
   1675 impl Eq for NonDiscoverableAuthenticationServerState {}
   1676 impl Hash for NonDiscoverableAuthenticationServerState {
   1677     #[inline]
   1678     fn hash<H: Hasher>(&self, state: &mut H) {
   1679         self.state.hash(state);
   1680     }
   1681 }
   1682 impl PartialOrd for NonDiscoverableAuthenticationServerState {
   1683     #[inline]
   1684     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1685         Some(self.cmp(other))
   1686     }
   1687 }
   1688 impl Ord for NonDiscoverableAuthenticationServerState {
   1689     #[inline]
   1690     fn cmp(&self, other: &Self) -> Ordering {
   1691         self.state.cmp(&other.state)
   1692     }
   1693 }