webauthn_rp

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

auth.rs (93087B)


      1 #[cfg(doc)]
      2 use super::{
      3     super::response::{
      4         Backup, CollectedClientData, Flag,
      5         auth::AuthenticatorData,
      6         register::{
      7             AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState,
      8             DynamicState, StaticState,
      9         },
     10     },
     11     AsciiDomain, DomainOrigin, Url,
     12     register::{self, PublicKeyCredentialCreationOptions},
     13 };
     14 use super::{
     15     super::{
     16         AuthenticatedCredential,
     17         response::{
     18             AuthenticatorAttachment,
     19             auth::{
     20                 Authentication, AuthenticatorExtensionOutput, DiscoverableAuthentication,
     21                 HmacSecret, NonDiscoverableAuthentication,
     22                 error::{AuthCeremonyErr, ExtensionErr, OneOrTwo},
     23             },
     24             register::{CompressedPubKey, CredentialProtectionPolicy},
     25         },
     26     },
     27     BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId, CredentialMediationRequirement,
     28     Credentials, ExtensionReq, Hint, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId,
     29     SentChallenge, THREE_HUNDRED_THOUSAND, TimedCeremony, UserVerificationRequirement,
     30     auth::error::{InvalidTimeout, SecondFactorErr},
     31 };
     32 use core::{
     33     borrow::Borrow,
     34     cmp::Ordering,
     35     hash::{Hash, Hasher},
     36     num::{NonZeroU32, NonZeroU64},
     37     time::Duration,
     38 };
     39 #[cfg(any(doc, not(feature = "serializable_server_state")))]
     40 use std::time::Instant;
     41 #[cfg(any(doc, feature = "serializable_server_state"))]
     42 use std::time::SystemTime;
     43 /// Contains error types.
     44 pub mod error;
     45 /// Contains functionality to serialize data to a client.
     46 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
     47 #[cfg(feature = "serde")]
     48 mod ser;
     49 /// Contains functionality to (de)serialize [`DiscoverableAuthenticationServerState`] and
     50 /// [`NonDiscoverableAuthenticationServerState`] to a data store.
     51 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
     52 #[cfg(feature = "serializable_server_state")]
     53 pub mod ser_server_state;
     54 /// Controls how [signature counter](https://www.w3.org/TR/webauthn-3/#signature-counter) is enforced.
     55 ///
     56 /// Note that if the previous signature counter is positive and the new counter is not strictly greater, then the
     57 /// authenticator is likely a clone (i.e., there are at least two copies of the private key).
     58 #[derive(Clone, Copy, Debug, Default)]
     59 pub enum SignatureCounterEnforcement {
     60     /// Fail the authentication ceremony if the counter is less than or equal to the previous value when the
     61     /// previous value is positive.
     62     #[default]
     63     Fail,
     64     /// When the counter is less than the previous value, don't fail and update the value.
     65     ///
     66     /// Note in the special case that the new signature counter is 0, [`DynamicState::sign_count`] _won't_
     67     /// be updated since that would allow an attacker to permanently disable the counter.
     68     Update,
     69     /// When the counter is less than the previous value, don't fail but don't update the value.
     70     Ignore,
     71 }
     72 impl SignatureCounterEnforcement {
     73     /// Validates the signature counter based on `self`.
     74     const fn validate(self, prev: u32, cur: u32) -> Result<u32, AuthCeremonyErr> {
     75         if prev == 0 || cur > prev {
     76             Ok(cur)
     77         } else {
     78             match self {
     79                 Self::Fail => Err(AuthCeremonyErr::SignatureCounter),
     80                 // When the new counter is `0`, we use the previous counter to avoid an attacker from
     81                 // being able to permanently disable it.
     82                 Self::Update => Ok(if cur == 0 { prev } else { cur }),
     83                 Self::Ignore => Ok(prev),
     84             }
     85         }
     86     }
     87 }
     88 /// Owned version of [`PrfInput`].
     89 ///
     90 /// When relying on [`NonDiscoverableCredentialRequestOptions`], it's recommended to use credential-specific PRF
     91 /// inputs that are continuously rolled over. One uses this type for such a thing.
     92 #[derive(Clone, Debug)]
     93 pub struct PrfInputOwned {
     94     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first).
     95     pub first: Vec<u8>,
     96     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second).
     97     pub second: Option<Vec<u8>>,
     98     /// Note this is only applicable for authenticators that implement the
     99     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the
    100     /// [`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)
    101     /// extension since the data is encrypted and is part of the [`AuthenticatorData`].
    102     pub ext_req: ExtensionReq,
    103 }
    104 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client.
    105 #[derive(Clone, Copy, Debug, Default)]
    106 pub struct Extension<'prf_first, 'prf_second> {
    107     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension).
    108     ///
    109     /// If both [`CredentialSpecificExtension::prf`] and this are [`Some`], then `CredentialSpecificExtension::prf`
    110     /// takes priority.
    111     ///
    112     /// Note `ExtensionReq` is only applicable for authenticators that implement the
    113     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the
    114     /// [`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)
    115     /// extension since the data is encrypted and is part of the [`AuthenticatorData`].
    116     pub prf: Option<(PrfInput<'prf_first, 'prf_second>, ExtensionReq)>,
    117 }
    118 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client that
    119 /// are credential-specific which among other things implies a non-discoverable request.
    120 #[derive(Clone, Debug, Default)]
    121 pub struct CredentialSpecificExtension {
    122     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension).
    123     ///
    124     /// If both [`Extension::prf`] and this are [`Some`], then this take priority.
    125     pub prf: Option<PrfInputOwned>,
    126 }
    127 /// Registered credential used in
    128 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    129 #[derive(Clone, Debug)]
    130 pub struct AllowedCredential {
    131     /// The registered credential.
    132     pub credential: PublicKeyCredentialDescriptor<Vec<u8>>,
    133     /// Credential-specific extensions.
    134     pub extension: CredentialSpecificExtension,
    135 }
    136 impl From<PublicKeyCredentialDescriptor<Vec<u8>>> for AllowedCredential {
    137     #[inline]
    138     fn from(credential: PublicKeyCredentialDescriptor<Vec<u8>>) -> Self {
    139         Self {
    140             credential,
    141             extension: CredentialSpecificExtension::default(),
    142         }
    143     }
    144 }
    145 impl From<AllowedCredential> for PublicKeyCredentialDescriptor<Vec<u8>> {
    146     #[inline]
    147     fn from(credential: AllowedCredential) -> Self {
    148         credential.credential
    149     }
    150 }
    151 /// Queue of unique [`AllowedCredential`]s.
    152 #[derive(Clone, Debug, Default)]
    153 pub struct AllowedCredentials {
    154     /// Allowed credentials.
    155     creds: Vec<AllowedCredential>,
    156     /// Number of `AllowedCredential`s that have PRF inputs.
    157     ///
    158     /// Useful to help serialization.
    159     prf_count: usize,
    160 }
    161 impl Credentials for AllowedCredentials {
    162     type Credential = AllowedCredential;
    163     /// # Examples
    164     ///
    165     /// ```
    166     /// # use webauthn_rp::request::{auth::AllowedCredentials, Credentials};
    167     /// assert!(AllowedCredentials::with_capacity(1).as_ref().is_empty());
    168     /// ```
    169     #[inline]
    170     fn with_capacity(capacity: usize) -> Self {
    171         Self {
    172             creds: Vec::with_capacity(capacity),
    173             prf_count: 0,
    174         }
    175     }
    176     /// # Examples
    177     ///
    178     /// ```
    179     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    180     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    181     /// # use webauthn_rp::{
    182     /// #     request::{auth::AllowedCredentials, PublicKeyCredentialDescriptor, Credentials},
    183     /// #     response::{AuthTransports, CredentialId},
    184     /// # };
    185     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    186     /// /// from the database.
    187     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    188     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    189     ///     // ⋮
    190     /// #     AuthTransports::decode(32)
    191     /// }
    192     /// let mut creds = AllowedCredentials::with_capacity(1);
    193     /// assert!(creds.as_ref().is_empty());
    194     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    195     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    196     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    197     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    198     /// let id = CredentialId::try_from(vec![0; 16])?;
    199     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    200     /// let transports = get_transports((&id).into())?;
    201     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    202     /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into()));
    203     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    204     /// let id_copy = CredentialId::try_from(vec![0; 16])?;
    205     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    206     /// let transports_2 = AuthTransports::NONE;
    207     /// // Duplicate `CredentialId`s don't get added.
    208     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    209     /// assert!(!creds.push(
    210     ///     PublicKeyCredentialDescriptor {
    211     ///         id: id_copy,
    212     ///         transports: transports_2
    213     ///     }
    214     ///     .into()
    215     /// ));
    216     /// # Ok::<_, webauthn_rp::AggErr>(())
    217     /// ```
    218     #[expect(
    219         clippy::arithmetic_side_effects,
    220         reason = "comment explains how overflow is not possible"
    221     )]
    222     #[inline]
    223     fn push(&mut self, cred: Self::Credential) -> bool {
    224         self.creds
    225             .iter()
    226             .try_fold((), |(), c| {
    227                 if c.credential.id == cred.credential.id {
    228                     Err(())
    229                 } else {
    230                     Ok(())
    231                 }
    232             })
    233             .is_ok_and(|()| {
    234                 // This can't overflow since `self.creds.push` would `panic` since
    235                 // `self.prf_count <= self.creds.len()`.
    236                 self.prf_count += usize::from(cred.extension.prf.is_some());
    237                 self.creds.push(cred);
    238                 true
    239             })
    240     }
    241     #[inline]
    242     fn len(&self) -> usize {
    243         self.creds.len()
    244     }
    245 }
    246 impl AsRef<[AllowedCredential]> for AllowedCredentials {
    247     #[inline]
    248     fn as_ref(&self) -> &[AllowedCredential] {
    249         self.creds.as_slice()
    250     }
    251 }
    252 impl From<&AllowedCredentials> for Vec<CredInfo> {
    253     #[inline]
    254     fn from(value: &AllowedCredentials) -> Self {
    255         let len = value.creds.len();
    256         value
    257             .creds
    258             .iter()
    259             .fold(Self::with_capacity(len), |mut creds, cred| {
    260                 creds.push(CredInfo {
    261                     id: cred.credential.id.clone(),
    262                     ext: (&cred.extension).into(),
    263                 });
    264                 creds
    265             })
    266     }
    267 }
    268 impl From<AllowedCredentials> for Vec<PublicKeyCredentialDescriptor<Vec<u8>>> {
    269     #[inline]
    270     fn from(value: AllowedCredentials) -> Self {
    271         let mut creds = Self::with_capacity(value.creds.len());
    272         value.creds.into_iter().fold((), |(), cred| {
    273             creds.push(cred.credential);
    274         });
    275         creds
    276     }
    277 }
    278 impl From<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>> for AllowedCredentials {
    279     #[inline]
    280     fn from(value: Vec<PublicKeyCredentialDescriptor<Vec<u8>>>) -> Self {
    281         let mut creds = Self::with_capacity(value.len());
    282         value.into_iter().fold((), |(), credential| {
    283             _ = creds.push(AllowedCredential {
    284                 credential,
    285                 extension: CredentialSpecificExtension { prf: None },
    286             });
    287         });
    288         creds
    289     }
    290 }
    291 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
    292 /// to send to the client when authenticating a discoverable credentential.
    293 ///
    294 /// Upon saving the [`DiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send
    295 /// [`DiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created
    296 /// [`DiscoverableAuthentication`], it is validated using [`DiscoverableAuthenticationServerState::verify`].
    297 #[derive(Debug)]
    298 pub struct DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    299     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement).
    300     pub mediation: CredentialMediationRequirement,
    301     /// `public-key` [credential type](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry).
    302     pub public_key: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    303 }
    304 impl<'rp_id, 'prf_first, 'prf_second>
    305     DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>
    306 {
    307     /// Creates a `DiscoverableCredentialRequestOptions` containing [`CredentialMediationRequirement::default`] and
    308     /// [`PublicKeyCredentialRequestOptions::passkey`].
    309     ///
    310     /// # Examples
    311     ///
    312     /// ```
    313     /// # use webauthn_rp::request::{auth::DiscoverableCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    314     /// assert!(matches!(
    315     ///     DiscoverableCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).public_key.user_verification,
    316     ///     UserVerificationRequirement::Required
    317     /// ));
    318     /// # Ok::<_, webauthn_rp::AggErr>(())
    319     /// ```
    320     #[expect(single_use_lifetimes, reason = "false positive")]
    321     #[inline]
    322     #[must_use]
    323     pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    324         Self {
    325             mediation: CredentialMediationRequirement::default(),
    326             public_key: PublicKeyCredentialRequestOptions::passkey(rp_id),
    327         }
    328     }
    329     /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming
    330     /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so
    331     /// `DiscoverableAuthenticationClientState` MUST be sent ASAP. In order to complete authentication, the returned
    332     /// `DiscoverableAuthenticationServerState` MUST be saved so that it can later be used to verify the credential
    333     /// assertion with [`DiscoverableAuthenticationServerState::verify`].
    334     ///
    335     /// # Errors
    336     ///
    337     /// Errors iff `self` contains incompatible configuration.
    338     #[inline]
    339     pub fn start_ceremony(
    340         self,
    341     ) -> Result<
    342         (
    343             DiscoverableAuthenticationServerState,
    344             DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
    345         ),
    346         InvalidTimeout,
    347     > {
    348         #[cfg(not(feature = "serializable_server_state"))]
    349         let res = Instant::now();
    350         #[cfg(feature = "serializable_server_state")]
    351         let res = SystemTime::now();
    352         res.checked_add(Duration::from_millis(
    353             NonZeroU64::from(self.public_key.timeout).get(),
    354         ))
    355         .ok_or(InvalidTimeout)
    356         .map(|expiration| {
    357             (
    358                 DiscoverableAuthenticationServerState(AuthenticationServerState {
    359                     challenge: SentChallenge(self.public_key.challenge.0),
    360                     user_verification: self.public_key.user_verification,
    361                     extensions: self.public_key.extensions.into(),
    362                     expiration,
    363                 }),
    364                 DiscoverableAuthenticationClientState(self),
    365             )
    366         })
    367     }
    368 }
    369 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions)
    370 /// to send to the client when authenticating non-discoverable credententials.
    371 ///
    372 /// Upon saving the [`NonDiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send
    373 /// [`NonDiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created
    374 /// [`NonDiscoverableAuthentication`], it is validated using [`NonDiscoverableAuthenticationServerState::verify`].
    375 #[derive(Debug)]
    376 pub struct NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    377     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement).
    378     mediation: CredentialMediationRequirement,
    379     /// [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions).
    380     options: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    381     /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    382     allow_credentials: AllowedCredentials,
    383 }
    384 impl<'rp_id, 'prf_first, 'prf_second>
    385     NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>
    386 {
    387     /// Returns a mutable reference to the `CredentialMediationRequirement`.
    388     #[inline]
    389     pub const fn mediation(&mut self) -> &mut CredentialMediationRequirement {
    390         &mut self.mediation
    391     }
    392     /// Returns a mutable reference to the configurable options.
    393     #[inline]
    394     pub const fn options(
    395         &mut self,
    396     ) -> &mut PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    397         &mut self.options
    398     }
    399     /// Returns a reference to the [`AllowedCredential`]s.
    400     #[inline]
    401     #[must_use]
    402     pub const fn allow_credentials(&self) -> &AllowedCredentials {
    403         &self.allow_credentials
    404     }
    405     /// Creates a `NonDiscoverableCredentialRequestOptions` containing
    406     /// [`CredentialMediationRequirement::Optional`],
    407     /// [`PublicKeyCredentialRequestOptions::second_factor`], and the passed [`AllowedCredentials`].
    408     ///
    409     /// # Errors
    410     ///
    411     /// Errors iff `allow_credentials` is empty.
    412     ///
    413     /// # Examples
    414     ///
    415     /// ```
    416     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    417     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    418     /// # use webauthn_rp::{
    419     /// #     request::{
    420     /// #         auth::{AllowedCredentials, NonDiscoverableCredentialRequestOptions},
    421     /// #         AsciiDomain, RpId, PublicKeyCredentialDescriptor, Credentials
    422     /// #     },
    423     /// #     response::{AuthTransports, CredentialId},
    424     /// # };
    425     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    426     /// /// from the database.
    427     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    428     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    429     ///     // ⋮
    430     /// #     AuthTransports::decode(32)
    431     /// }
    432     /// let mut creds = AllowedCredentials::with_capacity(1);
    433     /// assert!(creds.as_ref().is_empty());
    434     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    435     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    436     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    437     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    438     /// let id = CredentialId::try_from(vec![0; 16])?;
    439     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    440     /// let transports = get_transports((&id).into())?;
    441     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    442     /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into()));
    443     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    444     /// assert_eq!(
    445     ///     NonDiscoverableCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), creds)?
    446     ///         .allow_credentials()
    447     ///         .len(),
    448     ///     1
    449     /// );
    450     /// # Ok::<_, webauthn_rp::AggErr>(())
    451     /// ```
    452     #[expect(single_use_lifetimes, reason = "false positive")]
    453     #[inline]
    454     pub fn second_factor<'a: 'rp_id>(
    455         rp_id: &'a RpId,
    456         allow_credentials: AllowedCredentials,
    457     ) -> Result<Self, SecondFactorErr> {
    458         if allow_credentials.is_empty() {
    459             Err(SecondFactorErr)
    460         } else {
    461             Ok(Self {
    462                 mediation: CredentialMediationRequirement::default(),
    463                 options: PublicKeyCredentialRequestOptions::second_factor(rp_id),
    464                 allow_credentials,
    465             })
    466         }
    467     }
    468     /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming
    469     /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `NonDiscoverableAuthenticationClientState`
    470     /// MUST be sent ASAP. In order to complete authentication, the returned `NonDiscoverableAuthenticationServerState`
    471     /// MUST be saved so that it can later be used to verify the credential assertion with
    472     /// [`NonDiscoverableAuthenticationServerState::verify`].
    473     ///
    474     /// # Errors
    475     ///
    476     /// Errors iff `self` contains incompatible configuration.
    477     #[inline]
    478     pub fn start_ceremony(
    479         self,
    480     ) -> Result<
    481         (
    482             NonDiscoverableAuthenticationServerState,
    483             NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>,
    484         ),
    485         InvalidTimeout,
    486     > {
    487         #[cfg(not(feature = "serializable_server_state"))]
    488         let res = Instant::now();
    489         #[cfg(feature = "serializable_server_state")]
    490         let res = SystemTime::now();
    491         res.checked_add(Duration::from_millis(
    492             NonZeroU64::from(self.options.timeout).get(),
    493         ))
    494         .ok_or(InvalidTimeout)
    495         .map(|expiration| {
    496             (
    497                 NonDiscoverableAuthenticationServerState {
    498                     state: AuthenticationServerState {
    499                         challenge: SentChallenge(self.options.challenge.0),
    500                         user_verification: self.options.user_verification,
    501                         extensions: self.options.extensions.into(),
    502                         expiration,
    503                     },
    504                     allow_credentials: Vec::from(&self.allow_credentials),
    505                 },
    506                 NonDiscoverableAuthenticationClientState(self),
    507             )
    508         })
    509     }
    510 }
    511 /// The [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions)
    512 /// to send to the client when authenticating a credential.
    513 ///
    514 /// This does _not_ contain [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials).
    515 #[derive(Debug)]
    516 pub struct PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    517     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge).
    518     pub challenge: Challenge,
    519     /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-timeout).
    520     ///
    521     /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives
    522     /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled
    523     /// when authenticating credentials as no timeout would make out-of-memory (OOM) conditions more likely.
    524     pub timeout: NonZeroU32,
    525     /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-rpid).
    526     ///
    527     /// This MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when the credential was registered.
    528     pub rp_id: &'rp_id RpId,
    529     /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification).
    530     pub user_verification: UserVerificationRequirement,
    531     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints).
    532     pub hints: Hint,
    533     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions).
    534     pub extensions: Extension<'prf_first, 'prf_second>,
    535 }
    536 impl<'rp_id> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> {
    537     /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to
    538     /// [`UserVerificationRequirement::Required`] and [`Self::timeout`] set to 5 minutes,
    539     ///
    540     /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the
    541     /// credential was registered.
    542     ///
    543     /// # Examples
    544     ///
    545     /// ```
    546     /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    547     /// assert!(matches!(
    548     ///     PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification,
    549     ///     UserVerificationRequirement::Required
    550     /// ));
    551     /// # Ok::<_, webauthn_rp::AggErr>(())
    552     /// ```
    553     #[expect(single_use_lifetimes, reason = "false positive")]
    554     #[inline]
    555     #[must_use]
    556     pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    557         Self {
    558             challenge: Challenge::new(),
    559             timeout: THREE_HUNDRED_THOUSAND,
    560             rp_id,
    561             user_verification: UserVerificationRequirement::Required,
    562             hints: Hint::None,
    563             extensions: Extension::default(),
    564         }
    565     }
    566     /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to
    567     /// [`UserVerificationRequirement::Discouraged`] and [`Self::timeout`] set to 5 minutes.
    568     ///
    569     /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the
    570     /// credentials were registered.
    571     ///
    572     /// # Examples
    573     ///
    574     /// ```
    575     /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement};
    576     /// assert!(matches!(
    577     ///     PublicKeyCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification,
    578     ///     UserVerificationRequirement::Discouraged
    579     /// ));
    580     /// # Ok::<_, webauthn_rp::AggErr>(())
    581     /// ```
    582     #[expect(single_use_lifetimes, reason = "false positive")]
    583     #[inline]
    584     #[must_use]
    585     pub fn second_factor<'a: 'rp_id>(rp_id: &'a RpId) -> Self {
    586         let mut opts = Self::passkey(rp_id);
    587         opts.user_verification = UserVerificationRequirement::Discouraged;
    588         opts
    589     }
    590 }
    591 /// Container of a [`DiscoverableCredentialRequestOptions`] that has been used to start the authentication ceremony.
    592 /// This gets sent to the client ASAP.
    593 #[derive(Debug)]
    594 pub struct DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>(
    595     DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    596 );
    597 impl<'rp_id, 'prf_first, 'prf_second>
    598     DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>
    599 {
    600     /// Returns the `DiscoverableCredentialRequestOptions` that was used to start an authentication ceremony.
    601     #[inline]
    602     #[must_use]
    603     pub const fn options(
    604         &self,
    605     ) -> &DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    606         &self.0
    607     }
    608 }
    609 /// Container of a [`NonDiscoverableCredentialRequestOptions`] that has been used to start the authentication
    610 /// ceremony. This gets sent to the client ASAP.
    611 #[derive(Debug)]
    612 pub struct NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>(
    613     NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>,
    614 );
    615 impl<'rp_id, 'prf_first, 'prf_second>
    616     NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>
    617 {
    618     /// Returns the `NonDiscoverableCredentialRequestOptions` that was used to start an authentication ceremony.
    619     #[inline]
    620     #[must_use]
    621     pub const fn options(
    622         &self,
    623     ) -> &NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> {
    624         &self.0
    625     }
    626 }
    627 /// The possible combinations of an [`AuthenticatedCredential`]'s [`StaticState`]'s
    628 /// `extensions.hmac_secret` and `client_extension_results.prf`.
    629 ///
    630 /// Note we ensure in `crate::verify_static_and_dynamic_state` that `hmac_secret` does not exist when
    631 /// `prf` does not exist, `hmac_secret` does not exist or is `false` when `prf` is `false`, or
    632 /// `hmac_secret` does not exist or is `true` when `prf` is `true`.
    633 #[derive(Clone, Copy)]
    634 enum CredPrf {
    635     /// No `prf` or `hmac_secret`.
    636     None,
    637     /// `prf.enabled` is `false` but there is no `hmac_secret`.
    638     FalseNoHmac,
    639     /// `prf.enabled` and `hmac_secret` are `false`.
    640     FalseFalseHmac,
    641     /// `prf.enabled` is `true` but there is no `hmac_secret`.
    642     TrueNoHmac,
    643     /// `prf.enabled` and `hmac_secret` are `true`.
    644     TrueTrueHmac,
    645 }
    646 impl CredPrf {
    647     /// Returns `true` iff `self` is allowed to have an `HmacSecret` response.
    648     ///
    649     /// Note many authenticators allow PRF to be used during authentication even when not requested during
    650     /// registration even for authenticators (e.g., CTAP-based ones) that implement PRF on top of the `hmac-secret`
    651     /// extension; thus we allow `Self::None` and `Self::TrueNoHmac`.
    652     const fn is_prf_capable(self) -> bool {
    653         matches!(self, Self::None | Self::TrueNoHmac | Self::TrueTrueHmac)
    654     }
    655 }
    656 /// `PrfInput` and `PrfInputOwned` without the actual data sent to reduce memory usage when storing
    657 /// [`DiscoverableAuthenticationServerState`] in an in-memory collection.
    658 #[derive(Clone, Copy, Debug)]
    659 enum ServerPrfInfo {
    660     /// No `PrfInput`.
    661     None,
    662     /// `PrfInput::second` was `None`.
    663     One(ExtensionReq),
    664     /// `PrfInput::second` was `Some`.
    665     Two(ExtensionReq),
    666 }
    667 impl ServerPrfInfo {
    668     /// Validates `val` based on the passed arguments.
    669     ///
    670     /// It's not possible to request the PRF extension without sending `UserVerificationRequirement::Required`;
    671     /// thus `user_verified` will always be `true` when sending PRF; otherwise ceremony validation will error.
    672     /// However when we _don't_ send the PRF extension _and_ we don't error on an unsolicited response, it's
    673     /// possible to receive an `HmacSecret` without the user having been verified; thus we only ensure
    674     /// `user_verified` is true when we don't error on unsolicted responses _and_ we didn't send the PRF extension.
    675     const fn validate(
    676         self,
    677         user_verified: bool,
    678         cred_prf: CredPrf,
    679         hmac: HmacSecret,
    680         err_unsolicited: bool,
    681     ) -> Result<(), ExtensionErr> {
    682         match hmac {
    683             HmacSecret::None => match self {
    684                 Self::None => Ok(()),
    685                 Self::One(req) | Self::Two(req) => {
    686                     if matches!(req, ExtensionReq::Allow) {
    687                         if cred_prf.is_prf_capable() {
    688                             Ok(())
    689                         } else {
    690                             Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    691                         }
    692                     } else {
    693                         match cred_prf {
    694                             CredPrf::None | CredPrf::TrueNoHmac => Ok(()),
    695                             CredPrf::FalseNoHmac | CredPrf::FalseFalseHmac => {
    696                                 Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    697                             }
    698                             CredPrf::TrueTrueHmac => Err(ExtensionErr::MissingHmacSecret),
    699                         }
    700                     }
    701                 }
    702             },
    703             HmacSecret::One => match self {
    704                 Self::None => {
    705                     if err_unsolicited {
    706                         Err(ExtensionErr::ForbiddenHmacSecret)
    707                     } else if cred_prf.is_prf_capable() {
    708                         if user_verified {
    709                             Ok(())
    710                         } else {
    711                             Err(ExtensionErr::UserNotVerifiedHmacSecret)
    712                         }
    713                     } else {
    714                         Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    715                     }
    716                 }
    717                 Self::One(_) => {
    718                     if cred_prf.is_prf_capable() {
    719                         Ok(())
    720                     } else {
    721                         Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    722                     }
    723                 }
    724                 Self::Two(_) => Err(ExtensionErr::InvalidHmacSecretValue(
    725                     OneOrTwo::Two,
    726                     OneOrTwo::One,
    727                 )),
    728             },
    729             HmacSecret::Two => match self {
    730                 Self::None => {
    731                     if err_unsolicited {
    732                         Err(ExtensionErr::ForbiddenHmacSecret)
    733                     } else if cred_prf.is_prf_capable() {
    734                         if user_verified {
    735                             Ok(())
    736                         } else {
    737                             Err(ExtensionErr::UserNotVerifiedHmacSecret)
    738                         }
    739                     } else {
    740                         Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    741                     }
    742                 }
    743                 Self::One(_) => Err(ExtensionErr::InvalidHmacSecretValue(
    744                     OneOrTwo::One,
    745                     OneOrTwo::Two,
    746                 )),
    747                 Self::Two(_) => {
    748                     if cred_prf.is_prf_capable() {
    749                         Ok(())
    750                     } else {
    751                         Err(ExtensionErr::PrfRequestedForPrfIncapableCred)
    752                     }
    753                 }
    754             },
    755         }
    756     }
    757 }
    758 #[cfg(test)]
    759 impl PartialEq for ServerPrfInfo {
    760     fn eq(&self, other: &Self) -> bool {
    761         match *self {
    762             Self::None => matches!(*other, Self::None),
    763             Self::One(req) => matches!(*other, Self::One(req2) if req == req2),
    764             Self::Two(req) => matches!(*other, Self::Two(req2) if req == req2),
    765         }
    766     }
    767 }
    768 impl From<Option<(PrfInput<'_, '_>, ExtensionReq)>> for ServerPrfInfo {
    769     fn from(value: Option<(PrfInput<'_, '_>, ExtensionReq)>) -> Self {
    770         value.map_or(Self::None, |val| {
    771             val.0
    772                 .second
    773                 .map_or_else(|| Self::One(val.1), |_| Self::Two(val.1))
    774         })
    775     }
    776 }
    777 impl From<&PrfInputOwned> for ServerPrfInfo {
    778     fn from(value: &PrfInputOwned) -> Self {
    779         value
    780             .second
    781             .as_ref()
    782             .map_or_else(|| Self::One(value.ext_req), |_| Self::Two(value.ext_req))
    783     }
    784 }
    785 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`]
    786 /// in an in-memory collection.
    787 #[derive(Clone, Copy, Debug)]
    788 struct ServerExtensionInfo {
    789     /// `Extension::prf`.
    790     prf: ServerPrfInfo,
    791 }
    792 impl From<Extension<'_, '_>> for ServerExtensionInfo {
    793     fn from(value: Extension<'_, '_>) -> Self {
    794         Self {
    795             prf: value.prf.into(),
    796         }
    797     }
    798 }
    799 #[cfg(test)]
    800 impl PartialEq for ServerExtensionInfo {
    801     fn eq(&self, other: &Self) -> bool {
    802         self.prf == other.prf
    803     }
    804 }
    805 /// `CredentialSpecificExtension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`]
    806 /// in an in-memory collection.
    807 #[derive(Clone, Copy, Debug)]
    808 struct ServerCredSpecificExtensionInfo {
    809     /// `CredentialSpecificExtension::prf`.
    810     prf: ServerPrfInfo,
    811 }
    812 #[cfg(test)]
    813 impl PartialEq for ServerCredSpecificExtensionInfo {
    814     fn eq(&self, other: &Self) -> bool {
    815         self.prf == other.prf
    816     }
    817 }
    818 impl From<&CredentialSpecificExtension> for ServerCredSpecificExtensionInfo {
    819     fn from(value: &CredentialSpecificExtension) -> Self {
    820         Self {
    821             prf: value
    822                 .prf
    823                 .as_ref()
    824                 .map_or(ServerPrfInfo::None, ServerPrfInfo::from),
    825         }
    826     }
    827 }
    828 impl ServerExtensionInfo {
    829     /// Validates the extensions.
    830     ///
    831     /// Note that this MUST only be called internally by `auth::validate_extensions`.
    832     const fn validate_extensions(
    833         self,
    834         user_verified: bool,
    835         auth_ext: AuthenticatorExtensionOutput,
    836         error_unsolicited: bool,
    837         cred_prf: CredPrf,
    838     ) -> Result<(), ExtensionErr> {
    839         ServerPrfInfo::validate(
    840             self.prf,
    841             user_verified,
    842             cred_prf,
    843             auth_ext.hmac_secret,
    844             error_unsolicited,
    845         )
    846     }
    847 }
    848 /// Validates the extensions.
    849 fn validate_extensions(
    850     ext: ServerExtensionInfo,
    851     user_verified: bool,
    852     cred_ext: Option<ServerCredSpecificExtensionInfo>,
    853     auth_ext: AuthenticatorExtensionOutput,
    854     error_unsolicited: bool,
    855     cred_prf: CredPrf,
    856 ) -> Result<(), ExtensionErr> {
    857     cred_ext.map_or_else(
    858         || {
    859             // No credental-specific extensions, so we can simply focus on `ext`.
    860             ext.validate_extensions(user_verified, auth_ext, error_unsolicited, cred_prf)
    861         },
    862         |c_ext| {
    863             // Must carefully process each extension based on overlap and which gets priority over the other.
    864             if matches!(c_ext.prf, ServerPrfInfo::None) {
    865                 ext.prf.validate(
    866                     user_verified,
    867                     cred_prf,
    868                     auth_ext.hmac_secret,
    869                     error_unsolicited,
    870                 )
    871             } else {
    872                 c_ext.prf.validate(
    873                     user_verified,
    874                     cred_prf,
    875                     auth_ext.hmac_secret,
    876                     error_unsolicited,
    877                 )
    878             }
    879         },
    880     )
    881 }
    882 /// [`AllowedCredential`] with less data to reduce memory usage when storing [`AuthenticationServerState`]
    883 /// in an in-memory collection.
    884 #[derive(Debug)]
    885 struct CredInfo {
    886     /// The Credential ID.
    887     id: CredentialId<Vec<u8>>,
    888     /// Any credential-specific extensions.
    889     ext: ServerCredSpecificExtensionInfo,
    890 }
    891 #[cfg(test)]
    892 impl PartialEq for CredInfo {
    893     fn eq(&self, other: &Self) -> bool {
    894         self.id == other.id && self.ext == other.ext
    895     }
    896 }
    897 /// Controls how to handle a change in [`DynamicState::authenticator_attachment`].
    898 ///
    899 /// Note when `DynamicState::authenticator_attachment` is [`AuthenticatorAttachment::None`], then it will
    900 /// be updated regardless. Similarly when [`Authentication::authenticator_attachment`] is
    901 /// `AuthenticatorAttachment::None`, it will never update `DynamicState::authenticator_attachment`.
    902 #[derive(Clone, Copy, Debug)]
    903 pub enum AuthenticatorAttachmentEnforcement {
    904     /// Fail the authentication ceremony if [`AuthenticatorAttachment`] is not the same.
    905     ///
    906     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    907     Fail(bool),
    908     /// Update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`].
    909     ///
    910     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    911     Update(bool),
    912     /// Do not update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`].
    913     ///
    914     /// The contained `bool` represents if `AuthenticatorAttachment` must be sent.
    915     Ignore(bool),
    916 }
    917 impl AuthenticatorAttachmentEnforcement {
    918     /// Validates `cur` based on `self` and `prev`.
    919     const fn validate(
    920         self,
    921         prev: AuthenticatorAttachment,
    922         cur: AuthenticatorAttachment,
    923     ) -> Result<AuthenticatorAttachment, AuthCeremonyErr> {
    924         match cur {
    925             AuthenticatorAttachment::None => match self {
    926                 Self::Fail(require) | Self::Update(require) | Self::Ignore(require) => {
    927                     if require {
    928                         Err(AuthCeremonyErr::MissingAuthenticatorAttachment)
    929                     } else {
    930                         // We don't overwrite the previous one with [`AuthenticatorAttachment::None`].
    931                         Ok(prev)
    932                     }
    933                 }
    934             },
    935             AuthenticatorAttachment::Platform => match self {
    936                 Self::Fail(_) => {
    937                     if matches!(prev, AuthenticatorAttachment::CrossPlatform) {
    938                         Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch)
    939                     } else {
    940                         // We don't fail when we previously had [`AuthenticatorAttachment::None`].
    941                         Ok(cur)
    942                     }
    943                 }
    944                 Self::Update(_) => Ok(cur),
    945                 Self::Ignore(_) => {
    946                     if matches!(prev, AuthenticatorAttachment::None) {
    947                         // We overwrite the previous one when it is [`AuthenticatorAttachment::None`].
    948                         Ok(cur)
    949                     } else {
    950                         Ok(prev)
    951                     }
    952                 }
    953             },
    954             AuthenticatorAttachment::CrossPlatform => match self {
    955                 Self::Fail(_) => {
    956                     if matches!(prev, AuthenticatorAttachment::Platform) {
    957                         Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch)
    958                     } else {
    959                         // We don't fail when we previously had [`AuthenticatorAttachment::None`].
    960                         Ok(cur)
    961                     }
    962                 }
    963                 Self::Update(_) => Ok(cur),
    964                 Self::Ignore(_) => {
    965                     if matches!(prev, AuthenticatorAttachment::None) {
    966                         // We overwrite the previous one when it is [`AuthenticatorAttachment::None`].
    967                         Ok(cur)
    968                     } else {
    969                         Ok(prev)
    970                     }
    971                 }
    972             },
    973         }
    974     }
    975 }
    976 impl Default for AuthenticatorAttachmentEnforcement {
    977     /// Returns [`Self::Ignore`] containing `false`.
    978     #[inline]
    979     fn default() -> Self {
    980         Self::Ignore(false)
    981     }
    982 }
    983 /// Additional verification options to perform in [`DiscoverableAuthenticationServerState::verify`] and
    984 /// [`NonDiscoverableAuthenticationServerState::verify`].
    985 #[derive(Clone, Copy, Debug)]
    986 pub struct AuthenticationVerificationOptions<'origins, 'top_origins, O, T> {
    987     /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
    988     ///
    989     /// When this is empty, the origin that will be used will be based on the [`RpId`] passed to
    990     /// [`DiscoverableAuthenticationServerState::verify`] or [`NonDiscoverableAuthenticationServerState::verify`]. If
    991     /// [`RpId::Domain`], then the [`DomainOrigin`] returned from passing [`AsciiDomain::as_ref`] to
    992     /// [`DomainOrigin::new`] will be used; otherwise the [`Url`] in [`RpId::Url`] will be used.
    993     pub allowed_origins: &'origins [O],
    994     /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin)
    995     /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
    996     ///
    997     /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained
    998     /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`,
    999     /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`.
   1000     pub allowed_top_origins: Option<&'top_origins [T]>,
   1001     /// The required [`Backup`] state of the credential.
   1002     ///
   1003     /// Note that `None` is _not_ the same as `Some(BackupReq::None)` as the latter indicates that any [`Backup`]
   1004     /// is allowed. This is rarely what you want; instead, `None` indicates that [`BackupReq::from`] applied to
   1005     /// [`DynamicState::backup`] will be used in [`DiscoverableAuthenticationServerState::verify`] and
   1006     /// [`NonDiscoverableAuthenticationServerState::verify`] and
   1007     pub backup_requirement: Option<BackupReq>,
   1008     /// Error when unsolicited extensions are sent back iff `true`.
   1009     pub error_on_unsolicited_extensions: bool,
   1010     /// Dictates what happens when [`Authentication::authenticator_attachment`] is not the same as
   1011     /// [`DynamicState::authenticator_attachment`].
   1012     pub auth_attachment_enforcement: AuthenticatorAttachmentEnforcement,
   1013     /// [`DynamicState::user_verified`] will be set to `true` iff [`Flag::user_verified`] when `true`.
   1014     pub update_uv: bool,
   1015     /// Dictates what happens when [`AuthenticatorData::sign_count`] is not updated to a strictly greater value.
   1016     pub sig_counter_enforcement: SignatureCounterEnforcement,
   1017     /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`.
   1018     #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))]
   1019     #[cfg(feature = "serde_relaxed")]
   1020     pub client_data_json_relaxed: bool,
   1021 }
   1022 impl<O, T> Default for AuthenticationVerificationOptions<'_, '_, O, T> {
   1023     /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`,
   1024     /// [`Self::backup_requirement`] is `None`, [`Self::error_on_unsolicited_extensions`] is `true`,
   1025     /// [`Self::auth_attachment_enforcement`] is [`AuthenticatorAttachmentEnforcement::default`],
   1026     /// [`Self::update_uv`] is `false`, [`Self::sig_counter_enforcement`] is
   1027     /// [`SignatureCounterEnforcement::default`], and [`Self::client_data_json_relaxed`] is `true`.
   1028     #[inline]
   1029     fn default() -> Self {
   1030         Self {
   1031             allowed_origins: &[],
   1032             allowed_top_origins: None,
   1033             backup_requirement: None,
   1034             error_on_unsolicited_extensions: true,
   1035             auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::default(),
   1036             update_uv: false,
   1037             sig_counter_enforcement: SignatureCounterEnforcement::default(),
   1038             #[cfg(feature = "serde_relaxed")]
   1039             client_data_json_relaxed: true,
   1040         }
   1041     }
   1042 }
   1043 // This is essentially the `DiscoverableCredentialRequestOptions` used to create it; however to reduce
   1044 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size
   1045 // `x86_64-unknown-linux-gnu` platforms.
   1046 //
   1047 /// State needed to be saved when beginning the authentication ceremony.
   1048 ///
   1049 /// Saves the necessary information associated with the [`DiscoverableCredentialRequestOptions`] used to create it
   1050 /// via [`DiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be
   1051 /// performed with [`Self::verify`].
   1052 ///
   1053 /// `DiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct
   1054 /// `DiscoverableAuthenticationServerState` associated with a [`DiscoverableAuthentication`], one should use its
   1055 /// corresponding [`DiscoverableAuthentication::challenge`].
   1056 #[derive(Debug)]
   1057 pub struct DiscoverableAuthenticationServerState(AuthenticationServerState);
   1058 impl DiscoverableAuthenticationServerState {
   1059     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1060     /// iff `cred` was mutated.
   1061     ///
   1062     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1063     /// ceremony.
   1064     ///
   1065     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1066     /// is returned.
   1067     ///
   1068     /// # Errors
   1069     ///
   1070     /// Errors iff `response` is not valid according to the
   1071     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1072     /// of the settings in `options`.
   1073     #[inline]
   1074     pub fn verify<
   1075         'a,
   1076         const USER_LEN: usize,
   1077         O: PartialEq<Origin<'a>>,
   1078         T: PartialEq<Origin<'a>>,
   1079         EdKey: AsRef<[u8]>,
   1080         P256Key: AsRef<[u8]>,
   1081         P384Key: AsRef<[u8]>,
   1082         RsaKey: AsRef<[u8]>,
   1083     >(
   1084         self,
   1085         rp_id: &RpId,
   1086         response: &'a DiscoverableAuthentication<USER_LEN>,
   1087         cred: &mut AuthenticatedCredential<
   1088             '_,
   1089             '_,
   1090             USER_LEN,
   1091             CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>,
   1092         >,
   1093         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1094     ) -> Result<bool, AuthCeremonyErr> {
   1095         // Step 6 item 2.
   1096         if cred.user_id == response.response.user_handle() {
   1097             // Step 6 item 2.
   1098             if cred.id == response.raw_id {
   1099                 self.0.verify(true, rp_id, response, cred, options, None)
   1100             } else {
   1101                 Err(AuthCeremonyErr::CredentialIdMismatch)
   1102             }
   1103         } else {
   1104             Err(AuthCeremonyErr::UserHandleMismatch)
   1105         }
   1106     }
   1107     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1108     fn is_eq(&self, other: &Self) -> bool {
   1109         self.0.is_eq(&other.0)
   1110     }
   1111 }
   1112 // This is essentially the `NonDiscoverableCredentialRequestOptions` used to create it; however to reduce
   1113 // memory usage, we remove all unnecessary data making an instance of this as small as 80 bytes in size on
   1114 // `x86_64-unknown-linux-gnu` platforms. This does not include the size of each `CredInfo` which should exist
   1115 // elsewhere on the heap but obviously contributes memory overall.
   1116 /// State needed to be saved when beginning the authentication ceremony.
   1117 ///
   1118 /// Saves the necessary information associated with the [`NonDiscoverableCredentialRequestOptions`] used to create
   1119 /// it via [`NonDiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be
   1120 /// performed with [`Self::verify`].
   1121 ///
   1122 /// `NonDiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the
   1123 /// correct `NonDiscoverableAuthenticationServerState` associated with a [`NonDiscoverableAuthentication`], one
   1124 /// should use its corresponding [`NonDiscoverableAuthentication::challenge`].
   1125 #[derive(Debug)]
   1126 pub struct NonDiscoverableAuthenticationServerState {
   1127     /// Most server state.
   1128     state: AuthenticationServerState,
   1129     /// The set of credentials that are allowed.
   1130     allow_credentials: Vec<CredInfo>,
   1131 }
   1132 impl NonDiscoverableAuthenticationServerState {
   1133     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1134     /// iff `cred` was mutated.
   1135     ///
   1136     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1137     /// ceremony.
   1138     ///
   1139     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1140     /// is returned.
   1141     ///
   1142     /// # Errors
   1143     ///
   1144     /// Errors iff `response` is not valid according to the
   1145     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1146     /// of the settings in `options`.
   1147     #[inline]
   1148     pub fn verify<
   1149         'a,
   1150         const USER_LEN: usize,
   1151         O: PartialEq<Origin<'a>>,
   1152         T: PartialEq<Origin<'a>>,
   1153         EdKey: AsRef<[u8]>,
   1154         P256Key: AsRef<[u8]>,
   1155         P384Key: AsRef<[u8]>,
   1156         RsaKey: AsRef<[u8]>,
   1157     >(
   1158         self,
   1159         rp_id: &RpId,
   1160         response: &'a NonDiscoverableAuthentication<USER_LEN>,
   1161         cred: &mut AuthenticatedCredential<
   1162             '_,
   1163             '_,
   1164             USER_LEN,
   1165             CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>,
   1166         >,
   1167         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1168     ) -> Result<bool, AuthCeremonyErr> {
   1169         response
   1170             .response
   1171             .user_handle()
   1172             .as_ref()
   1173             .map_or(Ok(()), |user| {
   1174                 // Step 6 item 1.
   1175                 if *user == cred.user_id() {
   1176                     Ok(())
   1177                 } else {
   1178                     Err(AuthCeremonyErr::UserHandleMismatch)
   1179                 }
   1180             })
   1181             .and_then(|()| {
   1182                 self.allow_credentials
   1183                     .iter()
   1184                     // Step 5.
   1185                     .find(|c| c.id == response.raw_id)
   1186                     .ok_or(AuthCeremonyErr::NoMatchingAllowedCredential)
   1187                     .and_then(|c| {
   1188                         // Step 6 item 1.
   1189                         if c.id == cred.id {
   1190                             self.state
   1191                                 .verify(false, rp_id, response, cred, options, Some(c.ext))
   1192                         } else {
   1193                             Err(AuthCeremonyErr::CredentialIdMismatch)
   1194                         }
   1195                     })
   1196             })
   1197     }
   1198     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1199     fn is_eq(&self, other: &Self) -> bool {
   1200         self.state.is_eq(&other.state) && self.allow_credentials == other.allow_credentials
   1201     }
   1202 }
   1203 // This is essentially the `PublicKeyCredentialRequestOptions` used to create it; however to reduce
   1204 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size on
   1205 // `x86_64-unknown-linux-gnu` platforms.
   1206 /// Shared state used by [`DiscoverableAuthenticationServerState`] and [`NonDiscoverableAuthenticationServerState`].
   1207 #[derive(Debug)]
   1208 struct AuthenticationServerState {
   1209     // This is a `SentChallenge` since we need `AuthenticationServerState` to be fetchable after receiving the
   1210     // response from the client. This response must obviously be constructable; thus its challenge is a
   1211     // `SentChallenge`.
   1212     //
   1213     // This must never be mutated since we want to ensure it is actually a `Challenge` (which
   1214     // can only be constructed via `Challenge::new`). This is guaranteed to be true iff
   1215     // `serializable_server_state` is not enabled.
   1216     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge).
   1217     challenge: SentChallenge,
   1218     /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification).
   1219     user_verification: UserVerificationRequirement,
   1220     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions).
   1221     extensions: ServerExtensionInfo,
   1222     /// `Instant` the ceremony expires.
   1223     #[cfg(not(feature = "serializable_server_state"))]
   1224     expiration: Instant,
   1225     /// `SystemTime` the ceremony expires.
   1226     #[cfg(feature = "serializable_server_state")]
   1227     expiration: SystemTime,
   1228 }
   1229 impl AuthenticationServerState {
   1230     /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true`
   1231     /// iff `cred` was mutated.
   1232     ///
   1233     /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the
   1234     /// ceremony.
   1235     ///
   1236     /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)`
   1237     /// is returned.
   1238     ///
   1239     /// # Errors
   1240     ///
   1241     /// Errors iff `response` is not valid according to the
   1242     /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any
   1243     /// of the settings in `options`.
   1244     fn verify<
   1245         'a,
   1246         const USER_LEN: usize,
   1247         const DISCOVERABLE: bool,
   1248         O: PartialEq<Origin<'a>>,
   1249         T: PartialEq<Origin<'a>>,
   1250         EdKey: AsRef<[u8]>,
   1251         P256Key: AsRef<[u8]>,
   1252         P384Key: AsRef<[u8]>,
   1253         RsaKey: AsRef<[u8]>,
   1254     >(
   1255         self,
   1256         discoverable: bool,
   1257         rp_id: &RpId,
   1258         response: &'a Authentication<USER_LEN, DISCOVERABLE>,
   1259         cred: &mut AuthenticatedCredential<
   1260             '_,
   1261             '_,
   1262             USER_LEN,
   1263             CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>,
   1264         >,
   1265         options: &AuthenticationVerificationOptions<'_, '_, O, T>,
   1266         cred_ext: Option<ServerCredSpecificExtensionInfo>,
   1267     ) -> Result<bool, AuthCeremonyErr> {
   1268         // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion)
   1269         // is handled by:
   1270         //
   1271         // 1. Calling code.
   1272         // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]).
   1273         // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]).
   1274         // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]).
   1275         // 5. [`NonDiscoverableAuthenticationServerState::verify`].
   1276         // 6. [`DiscoverableAuthenticationServerState::verify`] and [`NonDiscoverableAuthenticationServerState::verify`].
   1277         // 7. Informative only in that it defines variables.
   1278         // 8. [`Self::partial_validate`].
   1279         // 9. [`Self::partial_validate`].
   1280         // 10. [`Self::partial_validate`].
   1281         // 11. [`Self::partial_validate`].
   1282         // 12. [`Self::partial_validate`].
   1283         // 13. [`Self::partial_validate`].
   1284         // 14. [`Self::partial_validate`].
   1285         // 15. [`Self::partial_validate`].
   1286         // 16. [`Self::partial_validate`].
   1287         // 17. [`Self::partial_validate`].
   1288         // 18. [`Self::partial_validate`].
   1289         // 19. [`Self::partial_validate`].
   1290         // 20. [`Self::partial_validate`].
   1291         // 21. [`Self::partial_validate`].
   1292         // 22. Below.
   1293         // 23. Below.
   1294         // 24. Below.
   1295         // 25. Below.
   1296 
   1297         // Steps 8–21.
   1298         self.partial_validate(
   1299             rp_id,
   1300             response,
   1301             (&cred.static_state.credential_public_key).into(),
   1302             &CeremonyOptions {
   1303                 allowed_origins: options.allowed_origins,
   1304                 allowed_top_origins: options.allowed_top_origins,
   1305                 backup_requirement: options
   1306                     .backup_requirement
   1307                     .unwrap_or_else(|| BackupReq::from(cred.dynamic_state.backup)),
   1308                 #[cfg(feature = "serde_relaxed")]
   1309                 client_data_json_relaxed: options.client_data_json_relaxed,
   1310             },
   1311         )
   1312         .map_err(AuthCeremonyErr::from)
   1313         .and_then(|auth_data| {
   1314             options
   1315                 .auth_attachment_enforcement
   1316                 .validate(
   1317                     cred.dynamic_state.authenticator_attachment,
   1318                     response.authenticator_attachment,
   1319                 )
   1320                 .and_then(|auth_attachment| {
   1321                     let flags = auth_data.flags();
   1322                     // Step 23.
   1323                     validate_extensions(
   1324                         self.extensions,
   1325                         flags.user_verified,
   1326                         cred_ext,
   1327                         auth_data.extensions(),
   1328                         options.error_on_unsolicited_extensions,
   1329                         cred.static_state.client_extension_results.prf.map_or(
   1330                             CredPrf::None,
   1331                             |prf| {
   1332                                 if prf.enabled {
   1333                                     cred.static_state
   1334                                         .extensions
   1335                                         .hmac_secret
   1336                                         .map_or(CredPrf::TrueNoHmac, |_| CredPrf::TrueTrueHmac)
   1337                                 } else {
   1338                                     cred.static_state
   1339                                         .extensions
   1340                                         .hmac_secret
   1341                                         .map_or(CredPrf::FalseNoHmac, |_| CredPrf::FalseFalseHmac)
   1342                                 }
   1343                             },
   1344                         ),
   1345                     )
   1346                     .map_err(AuthCeremonyErr::Extension)
   1347                     .and_then(|()| {
   1348                         // Step 22.
   1349                         options
   1350                             .sig_counter_enforcement
   1351                             .validate(cred.dynamic_state.sign_count, auth_data.sign_count())
   1352                             .and_then(|sig_counter| {
   1353                                 let prev_dyn_state = cred.dynamic_state;
   1354                                 // Step 24 item 2.
   1355                                 cred.dynamic_state.backup = flags.backup;
   1356                                 if options.update_uv && flags.user_verified {
   1357                                     // Step 24 item 3.
   1358                                     cred.dynamic_state.user_verified = true;
   1359                                 }
   1360                                 // Step 24 item 1.
   1361                                 cred.dynamic_state.sign_count = sig_counter;
   1362                                 cred.dynamic_state.authenticator_attachment = auth_attachment;
   1363                                 // Step 25.
   1364                                 if flags.user_verified {
   1365                                     Ok(())
   1366                                 } else {
   1367                                     match cred.static_state.extensions.cred_protect {
   1368                                         CredentialProtectionPolicy::None | CredentialProtectionPolicy::UserVerificationOptional => Ok(()),
   1369                                         CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
   1370                                             if discoverable {
   1371                                                 Err(AuthCeremonyErr::DiscoverableCredProtectCredentialIdList)
   1372                                             } else {
   1373                                                 Ok(())
   1374                                             }
   1375                                         }
   1376                                         CredentialProtectionPolicy::UserVerificationRequired => {
   1377                                             Err(AuthCeremonyErr::UserNotVerifiedCredProtectRequired)
   1378                                         }
   1379                                     }
   1380                                 }.inspect_err(|_| {
   1381                                     cred.dynamic_state = prev_dyn_state;
   1382                                 }).map(|()| {
   1383                                     prev_dyn_state != cred.dynamic_state
   1384                                 })
   1385                             })
   1386                     })
   1387                 })
   1388         })
   1389     }
   1390     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   1391     fn is_eq(&self, other: &Self) -> bool {
   1392         self.challenge == other.challenge
   1393             && self.user_verification == other.user_verification
   1394             && self.extensions == other.extensions
   1395             && self.expiration == other.expiration
   1396     }
   1397 }
   1398 impl TimedCeremony for AuthenticationServerState {
   1399     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1400     #[inline]
   1401     fn expiration(&self) -> Instant {
   1402         self.expiration
   1403     }
   1404     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1405     #[inline]
   1406     fn expiration(&self) -> SystemTime {
   1407         self.expiration
   1408     }
   1409 }
   1410 impl TimedCeremony for DiscoverableAuthenticationServerState {
   1411     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1412     #[inline]
   1413     fn expiration(&self) -> Instant {
   1414         self.0.expiration()
   1415     }
   1416     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1417     #[inline]
   1418     fn expiration(&self) -> SystemTime {
   1419         self.0.expiration()
   1420     }
   1421 }
   1422 impl TimedCeremony for NonDiscoverableAuthenticationServerState {
   1423     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1424     #[inline]
   1425     fn expiration(&self) -> Instant {
   1426         self.state.expiration()
   1427     }
   1428     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1429     #[inline]
   1430     fn expiration(&self) -> SystemTime {
   1431         self.state.expiration()
   1432     }
   1433 }
   1434 impl<const USER_LEN: usize, const DISCOVERABLE: bool> Ceremony<USER_LEN, DISCOVERABLE>
   1435     for AuthenticationServerState
   1436 {
   1437     type R = Authentication<USER_LEN, DISCOVERABLE>;
   1438     fn rand_challenge(&self) -> SentChallenge {
   1439         self.challenge
   1440     }
   1441     #[cfg(not(feature = "serializable_server_state"))]
   1442     fn expiry(&self) -> Instant {
   1443         self.expiration
   1444     }
   1445     #[cfg(feature = "serializable_server_state")]
   1446     fn expiry(&self) -> SystemTime {
   1447         self.expiration
   1448     }
   1449     fn user_verification(&self) -> UserVerificationRequirement {
   1450         self.user_verification
   1451     }
   1452 }
   1453 impl Borrow<SentChallenge> for AuthenticationServerState {
   1454     #[inline]
   1455     fn borrow(&self) -> &SentChallenge {
   1456         &self.challenge
   1457     }
   1458 }
   1459 impl PartialEq for AuthenticationServerState {
   1460     #[inline]
   1461     fn eq(&self, other: &Self) -> bool {
   1462         self.challenge == other.challenge
   1463     }
   1464 }
   1465 impl PartialEq<&Self> for AuthenticationServerState {
   1466     #[inline]
   1467     fn eq(&self, other: &&Self) -> bool {
   1468         *self == **other
   1469     }
   1470 }
   1471 impl PartialEq<AuthenticationServerState> for &AuthenticationServerState {
   1472     #[inline]
   1473     fn eq(&self, other: &AuthenticationServerState) -> bool {
   1474         **self == *other
   1475     }
   1476 }
   1477 impl Eq for AuthenticationServerState {}
   1478 impl Hash for AuthenticationServerState {
   1479     #[inline]
   1480     fn hash<H: Hasher>(&self, state: &mut H) {
   1481         self.challenge.hash(state);
   1482     }
   1483 }
   1484 impl PartialOrd for AuthenticationServerState {
   1485     #[inline]
   1486     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1487         Some(self.cmp(other))
   1488     }
   1489 }
   1490 impl Ord for AuthenticationServerState {
   1491     #[inline]
   1492     fn cmp(&self, other: &Self) -> Ordering {
   1493         self.challenge.cmp(&other.challenge)
   1494     }
   1495 }
   1496 impl Borrow<SentChallenge> for DiscoverableAuthenticationServerState {
   1497     #[inline]
   1498     fn borrow(&self) -> &SentChallenge {
   1499         self.0.borrow()
   1500     }
   1501 }
   1502 impl PartialEq for DiscoverableAuthenticationServerState {
   1503     #[inline]
   1504     fn eq(&self, other: &Self) -> bool {
   1505         self.0 == other.0
   1506     }
   1507 }
   1508 impl PartialEq<&Self> for DiscoverableAuthenticationServerState {
   1509     #[inline]
   1510     fn eq(&self, other: &&Self) -> bool {
   1511         self.0 == other.0
   1512     }
   1513 }
   1514 impl PartialEq<DiscoverableAuthenticationServerState> for &DiscoverableAuthenticationServerState {
   1515     #[inline]
   1516     fn eq(&self, other: &DiscoverableAuthenticationServerState) -> bool {
   1517         self.0 == other.0
   1518     }
   1519 }
   1520 impl Eq for DiscoverableAuthenticationServerState {}
   1521 impl Hash for DiscoverableAuthenticationServerState {
   1522     #[inline]
   1523     fn hash<H: Hasher>(&self, state: &mut H) {
   1524         self.0.hash(state);
   1525     }
   1526 }
   1527 impl PartialOrd for DiscoverableAuthenticationServerState {
   1528     #[inline]
   1529     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1530         Some(self.cmp(other))
   1531     }
   1532 }
   1533 impl Ord for DiscoverableAuthenticationServerState {
   1534     #[inline]
   1535     fn cmp(&self, other: &Self) -> Ordering {
   1536         self.0.cmp(&other.0)
   1537     }
   1538 }
   1539 impl Borrow<SentChallenge> for NonDiscoverableAuthenticationServerState {
   1540     #[inline]
   1541     fn borrow(&self) -> &SentChallenge {
   1542         self.state.borrow()
   1543     }
   1544 }
   1545 impl PartialEq for NonDiscoverableAuthenticationServerState {
   1546     #[inline]
   1547     fn eq(&self, other: &Self) -> bool {
   1548         self.state == other.state
   1549     }
   1550 }
   1551 impl PartialEq<&Self> for NonDiscoverableAuthenticationServerState {
   1552     #[inline]
   1553     fn eq(&self, other: &&Self) -> bool {
   1554         self.state == other.state
   1555     }
   1556 }
   1557 impl PartialEq<NonDiscoverableAuthenticationServerState>
   1558     for &NonDiscoverableAuthenticationServerState
   1559 {
   1560     #[inline]
   1561     fn eq(&self, other: &NonDiscoverableAuthenticationServerState) -> bool {
   1562         self.state == other.state
   1563     }
   1564 }
   1565 impl Eq for NonDiscoverableAuthenticationServerState {}
   1566 impl Hash for NonDiscoverableAuthenticationServerState {
   1567     #[inline]
   1568     fn hash<H: Hasher>(&self, state: &mut H) {
   1569         self.state.hash(state);
   1570     }
   1571 }
   1572 impl PartialOrd for NonDiscoverableAuthenticationServerState {
   1573     #[inline]
   1574     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   1575         Some(self.cmp(other))
   1576     }
   1577 }
   1578 impl Ord for NonDiscoverableAuthenticationServerState {
   1579     #[inline]
   1580     fn cmp(&self, other: &Self) -> Ordering {
   1581         self.state.cmp(&other.state)
   1582     }
   1583 }
   1584 #[cfg(test)]
   1585 mod tests {
   1586     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1587     use super::{
   1588         super::super::{
   1589             AuthenticatedCredential, DynamicState, StaticState, UserHandle,
   1590             response::{
   1591                 Backup,
   1592                 auth::{DiscoverableAuthenticatorAssertion, HmacSecret},
   1593                 register::{
   1594                     AuthenticationExtensionsPrfOutputs, AuthenticatorExtensionOutputStaticState,
   1595                     ClientExtensionsOutputsStaticState, CredentialProtectionPolicy, Ed25519PubKey,
   1596                 },
   1597             },
   1598         },
   1599         AuthCeremonyErr, AuthenticationVerificationOptions, AuthenticatorAttachment,
   1600         AuthenticatorAttachmentEnforcement, CompressedPubKey, DiscoverableAuthentication,
   1601         ExtensionErr, OneOrTwo, PrfInput, SignatureCounterEnforcement,
   1602     };
   1603     #[cfg(all(
   1604         feature = "custom",
   1605         any(
   1606             feature = "serializable_server_state",
   1607             not(any(feature = "bin", feature = "serde"))
   1608         )
   1609     ))]
   1610     use super::{
   1611         super::{super::AggErr, Challenge, CredentialId, RpId, UserVerificationRequirement},
   1612         DiscoverableCredentialRequestOptions, ExtensionReq,
   1613     };
   1614     #[cfg(all(feature = "custom", feature = "serializable_server_state"))]
   1615     use super::{
   1616         super::{
   1617             super::bin::{Decode as _, Encode as _},
   1618             AsciiDomain, AuthTransports,
   1619         },
   1620         AllowedCredential, AllowedCredentials, CredentialSpecificExtension, Credentials as _,
   1621         DiscoverableAuthenticationServerState, Extension, NonDiscoverableAuthenticationServerState,
   1622         NonDiscoverableCredentialRequestOptions, PrfInputOwned, PublicKeyCredentialDescriptor,
   1623     };
   1624     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1625     use ed25519_dalek::{Signer as _, SigningKey};
   1626     #[cfg(all(
   1627         feature = "custom",
   1628         any(
   1629             feature = "serializable_server_state",
   1630             not(any(feature = "bin", feature = "serde"))
   1631         )
   1632     ))]
   1633     use rsa::sha2::{Digest as _, Sha256};
   1634     #[cfg(all(
   1635         feature = "custom",
   1636         any(
   1637             feature = "serializable_server_state",
   1638             not(any(feature = "bin", feature = "serde"))
   1639         )
   1640     ))]
   1641     const CBOR_BYTES: u8 = 0b010_00000;
   1642     #[cfg(all(
   1643         feature = "custom",
   1644         any(
   1645             feature = "serializable_server_state",
   1646             not(any(feature = "bin", feature = "serde"))
   1647         )
   1648     ))]
   1649     const CBOR_TEXT: u8 = 0b011_00000;
   1650     #[cfg(all(
   1651         feature = "custom",
   1652         any(
   1653             feature = "serializable_server_state",
   1654             not(any(feature = "bin", feature = "serde"))
   1655         )
   1656     ))]
   1657     const CBOR_MAP: u8 = 0b101_00000;
   1658     #[test]
   1659     #[cfg(all(feature = "custom", feature = "serializable_server_state"))]
   1660     fn eddsa_auth_ser() -> Result<(), AggErr> {
   1661         let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?);
   1662         let mut creds = AllowedCredentials::with_capacity(1);
   1663         _ = creds.push(AllowedCredential {
   1664             credential: PublicKeyCredentialDescriptor {
   1665                 id: CredentialId::try_from(vec![0; 16])?,
   1666                 transports: AuthTransports::NONE,
   1667             },
   1668             extension: CredentialSpecificExtension {
   1669                 prf: Some(PrfInputOwned {
   1670                     first: Vec::new(),
   1671                     second: Some(Vec::new()),
   1672                     ext_req: ExtensionReq::Require,
   1673                 }),
   1674             },
   1675         });
   1676         let mut opts = NonDiscoverableCredentialRequestOptions::second_factor(&rp_id, creds)?;
   1677         opts.options.user_verification = UserVerificationRequirement::Required;
   1678         opts.options.challenge = Challenge(0);
   1679         opts.options.extensions = Extension { prf: None };
   1680         let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   1681         // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information.
   1682         let mut authenticator_data = Vec::with_capacity(164);
   1683         authenticator_data.extend_from_slice(
   1684             [
   1685                 // rpIdHash.
   1686                 // This will be overwritten later.
   1687                 0,
   1688                 0,
   1689                 0,
   1690                 0,
   1691                 0,
   1692                 0,
   1693                 0,
   1694                 0,
   1695                 0,
   1696                 0,
   1697                 0,
   1698                 0,
   1699                 0,
   1700                 0,
   1701                 0,
   1702                 0,
   1703                 0,
   1704                 0,
   1705                 0,
   1706                 0,
   1707                 0,
   1708                 0,
   1709                 0,
   1710                 0,
   1711                 0,
   1712                 0,
   1713                 0,
   1714                 0,
   1715                 0,
   1716                 0,
   1717                 0,
   1718                 0,
   1719                 // flags.
   1720                 // UP, UV, and ED (right-to-left).
   1721                 0b1000_0101,
   1722                 // signCount.
   1723                 // 0 as 32-bit big endian.
   1724                 0,
   1725                 0,
   1726                 0,
   1727                 0,
   1728                 CBOR_MAP | 1,
   1729                 CBOR_TEXT | 11,
   1730                 b'h',
   1731                 b'm',
   1732                 b'a',
   1733                 b'c',
   1734                 b'-',
   1735                 b's',
   1736                 b'e',
   1737                 b'c',
   1738                 b'r',
   1739                 b'e',
   1740                 b't',
   1741                 CBOR_BYTES | 24,
   1742                 // Length is 80.
   1743                 80,
   1744                 // Two HMAC outputs concatenated and encrypted.
   1745                 0,
   1746                 0,
   1747                 0,
   1748                 0,
   1749                 0,
   1750                 0,
   1751                 0,
   1752                 0,
   1753                 0,
   1754                 0,
   1755                 0,
   1756                 0,
   1757                 0,
   1758                 0,
   1759                 0,
   1760                 0,
   1761                 0,
   1762                 0,
   1763                 0,
   1764                 0,
   1765                 0,
   1766                 0,
   1767                 0,
   1768                 0,
   1769                 0,
   1770                 0,
   1771                 0,
   1772                 0,
   1773                 0,
   1774                 0,
   1775                 0,
   1776                 0,
   1777                 0,
   1778                 0,
   1779                 0,
   1780                 0,
   1781                 0,
   1782                 0,
   1783                 0,
   1784                 0,
   1785                 0,
   1786                 0,
   1787                 0,
   1788                 0,
   1789                 0,
   1790                 0,
   1791                 0,
   1792                 0,
   1793                 0,
   1794                 0,
   1795                 0,
   1796                 0,
   1797                 0,
   1798                 0,
   1799                 0,
   1800                 0,
   1801                 0,
   1802                 0,
   1803                 0,
   1804                 0,
   1805                 0,
   1806                 0,
   1807                 0,
   1808                 0,
   1809                 0,
   1810                 0,
   1811                 0,
   1812                 0,
   1813                 0,
   1814                 0,
   1815                 0,
   1816                 0,
   1817                 0,
   1818                 0,
   1819                 0,
   1820                 0,
   1821                 0,
   1822                 0,
   1823                 0,
   1824                 0,
   1825             ]
   1826             .as_slice(),
   1827         );
   1828         authenticator_data[..32]
   1829             .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice());
   1830         authenticator_data
   1831             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   1832         authenticator_data.truncate(132);
   1833         let server = opts.start_ceremony()?.0;
   1834         assert!(
   1835             server.is_eq(&NonDiscoverableAuthenticationServerState::decode(
   1836                 server.encode()?.as_slice()
   1837             )?)
   1838         );
   1839         let mut opts_2 = DiscoverableCredentialRequestOptions::passkey(&rp_id);
   1840         opts_2.public_key.challenge = Challenge(0);
   1841         opts_2.public_key.extensions = Extension { prf: None };
   1842         let server_2 = opts_2.start_ceremony()?.0;
   1843         assert!(
   1844             server_2.is_eq(&DiscoverableAuthenticationServerState::decode(
   1845                 server_2
   1846                     .encode()
   1847                     .map_err(AggErr::EncodeDiscoverableAuthenticationServerState)?
   1848                     .as_slice()
   1849             )?)
   1850         );
   1851         Ok(())
   1852     }
   1853     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1854     #[derive(Clone, Copy)]
   1855     struct TestResponseOptions {
   1856         user_verified: bool,
   1857         hmac: HmacSecret,
   1858     }
   1859     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1860     #[derive(Clone, Copy)]
   1861     enum PrfCredOptions {
   1862         None,
   1863         FalseNoHmac,
   1864         FalseHmacFalse,
   1865         TrueNoHmac,
   1866         TrueHmacTrue,
   1867     }
   1868     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1869     #[derive(Clone, Copy)]
   1870     struct TestCredOptions {
   1871         cred_protect: CredentialProtectionPolicy,
   1872         prf: PrfCredOptions,
   1873     }
   1874     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1875     #[derive(Clone, Copy)]
   1876     enum PrfUvOptions {
   1877         /// `true` iff `UserVerificationRequirement::Required` should be used; otherwise
   1878         /// `UserVerificationRequirement::Preferred` is used.
   1879         None(bool),
   1880         Prf((PrfInput<'static, 'static>, ExtensionReq)),
   1881     }
   1882     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1883     #[derive(Clone, Copy)]
   1884     struct TestRequestOptions {
   1885         error_unsolicited: bool,
   1886         prf_uv: PrfUvOptions,
   1887     }
   1888     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1889     #[derive(Clone, Copy)]
   1890     struct TestOptions {
   1891         request: TestRequestOptions,
   1892         response: TestResponseOptions,
   1893         cred: TestCredOptions,
   1894     }
   1895     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1896     fn generate_client_data_json() -> Vec<u8> {
   1897         let mut json = Vec::with_capacity(256);
   1898         json.extend_from_slice(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice());
   1899         json
   1900     }
   1901     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   1902     fn generate_authenticator_data_public_key_sig(
   1903         opts: TestResponseOptions,
   1904     ) -> (
   1905         Vec<u8>,
   1906         CompressedPubKey<[u8; 32], [u8; 32], [u8; 48], Vec<u8>>,
   1907         Vec<u8>,
   1908     ) {
   1909         let mut authenticator_data = Vec::with_capacity(256);
   1910         authenticator_data.extend_from_slice(
   1911             [
   1912                 // RP ID HASH.
   1913                 // This will be overwritten later.
   1914                 0,
   1915                 0,
   1916                 0,
   1917                 0,
   1918                 0,
   1919                 0,
   1920                 0,
   1921                 0,
   1922                 0,
   1923                 0,
   1924                 0,
   1925                 0,
   1926                 0,
   1927                 0,
   1928                 0,
   1929                 0,
   1930                 0,
   1931                 0,
   1932                 0,
   1933                 0,
   1934                 0,
   1935                 0,
   1936                 0,
   1937                 0,
   1938                 0,
   1939                 0,
   1940                 0,
   1941                 0,
   1942                 0,
   1943                 0,
   1944                 0,
   1945                 0,
   1946                 // FLAGS.
   1947                 // UP, UV, AT, and ED (right-to-left).
   1948                 0b0000_0001
   1949                     | if opts.user_verified {
   1950                         0b0000_0100
   1951                     } else {
   1952                         0b0000_0000
   1953                     }
   1954                     | if matches!(opts.hmac, HmacSecret::None) {
   1955                         0
   1956                     } else {
   1957                         0b1000_0000
   1958                     },
   1959                 // COUNTER.
   1960                 // 0 as 32-bit big endian.
   1961                 0,
   1962                 0,
   1963                 0,
   1964                 0,
   1965             ]
   1966             .as_slice(),
   1967         );
   1968         authenticator_data[..32]
   1969             .copy_from_slice(Sha256::digest("example.com".as_bytes()).as_slice());
   1970         match opts.hmac {
   1971             HmacSecret::None => {}
   1972             HmacSecret::One => {
   1973                 authenticator_data.extend_from_slice(
   1974                     [
   1975                         CBOR_MAP | 1,
   1976                         // CBOR text of length 11.
   1977                         CBOR_TEXT | 11,
   1978                         b'h',
   1979                         b'm',
   1980                         b'a',
   1981                         b'c',
   1982                         b'-',
   1983                         b's',
   1984                         b'e',
   1985                         b'c',
   1986                         b'r',
   1987                         b'e',
   1988                         b't',
   1989                         CBOR_BYTES | 24,
   1990                         48,
   1991                     ]
   1992                     .as_slice(),
   1993                 );
   1994                 authenticator_data.extend_from_slice([0; 48].as_slice());
   1995             }
   1996             HmacSecret::Two => {
   1997                 authenticator_data.extend_from_slice(
   1998                     [
   1999                         CBOR_MAP | 1,
   2000                         // CBOR text of length 11.
   2001                         CBOR_TEXT | 11,
   2002                         b'h',
   2003                         b'm',
   2004                         b'a',
   2005                         b'c',
   2006                         b'-',
   2007                         b's',
   2008                         b'e',
   2009                         b'c',
   2010                         b'r',
   2011                         b'e',
   2012                         b't',
   2013                         CBOR_BYTES | 24,
   2014                         80,
   2015                     ]
   2016                     .as_slice(),
   2017                 );
   2018                 authenticator_data.extend_from_slice([0; 80].as_slice());
   2019             }
   2020         }
   2021         let len = authenticator_data.len();
   2022         authenticator_data
   2023             .extend_from_slice(Sha256::digest(generate_client_data_json().as_slice()).as_slice());
   2024         let sig_key = SigningKey::from_bytes(&[0; 32]);
   2025         let sig = sig_key.sign(authenticator_data.as_slice()).to_vec();
   2026         authenticator_data.truncate(len);
   2027         (
   2028             authenticator_data,
   2029             CompressedPubKey::Ed25519(Ed25519PubKey::from(sig_key.verifying_key().to_bytes())),
   2030             sig,
   2031         )
   2032     }
   2033     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2034     fn validate(options: TestOptions) -> Result<(), AggErr> {
   2035         let rp_id = RpId::Domain("example.com".to_owned().try_into()?);
   2036         let user = UserHandle::from([0; 1]);
   2037         let (authenticator_data, credential_public_key, signature) =
   2038             generate_authenticator_data_public_key_sig(options.response);
   2039         let credential_id = CredentialId::try_from(vec![0; 16])?;
   2040         let authentication = DiscoverableAuthentication::new(
   2041             credential_id.clone(),
   2042             DiscoverableAuthenticatorAssertion::new(
   2043                 generate_client_data_json(),
   2044                 authenticator_data,
   2045                 signature,
   2046                 user,
   2047             ),
   2048             AuthenticatorAttachment::None,
   2049         );
   2050         let auth_opts = AuthenticationVerificationOptions::<'static, 'static, &str, &str> {
   2051             allowed_origins: [].as_slice(),
   2052             allowed_top_origins: None,
   2053             auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::Update(false),
   2054             backup_requirement: None,
   2055             error_on_unsolicited_extensions: options.request.error_unsolicited,
   2056             sig_counter_enforcement: SignatureCounterEnforcement::Fail,
   2057             update_uv: false,
   2058             #[cfg(feature = "serde_relaxed")]
   2059             client_data_json_relaxed: false,
   2060         };
   2061         let mut opts = DiscoverableCredentialRequestOptions::passkey(&rp_id);
   2062         opts.public_key.challenge = Challenge(0);
   2063         opts.public_key.user_verification = UserVerificationRequirement::Preferred;
   2064         match options.request.prf_uv {
   2065             PrfUvOptions::None(required) => {
   2066                 if required {
   2067                     opts.public_key.user_verification = UserVerificationRequirement::Required;
   2068                 };
   2069             }
   2070             PrfUvOptions::Prf(input) => {
   2071                 opts.public_key.user_verification = UserVerificationRequirement::Required;
   2072                 opts.public_key.extensions.prf = Some(input);
   2073             }
   2074         }
   2075         let mut cred = AuthenticatedCredential::new(
   2076             (&credential_id).into(),
   2077             &user,
   2078             StaticState {
   2079                 credential_public_key,
   2080                 extensions: AuthenticatorExtensionOutputStaticState {
   2081                     cred_protect: options.cred.cred_protect,
   2082                     hmac_secret: match options.cred.prf {
   2083                         PrfCredOptions::None
   2084                         | PrfCredOptions::FalseNoHmac
   2085                         | PrfCredOptions::TrueNoHmac => None,
   2086                         PrfCredOptions::FalseHmacFalse => Some(false),
   2087                         PrfCredOptions::TrueHmacTrue => Some(true),
   2088                     },
   2089                 },
   2090                 client_extension_results: ClientExtensionsOutputsStaticState {
   2091                     prf: match options.cred.prf {
   2092                         PrfCredOptions::None => None,
   2093                         PrfCredOptions::FalseNoHmac | PrfCredOptions::FalseHmacFalse => {
   2094                             Some(AuthenticationExtensionsPrfOutputs { enabled: false })
   2095                         }
   2096                         PrfCredOptions::TrueNoHmac | PrfCredOptions::TrueHmacTrue => {
   2097                             Some(AuthenticationExtensionsPrfOutputs { enabled: true })
   2098                         }
   2099                     },
   2100                 },
   2101             },
   2102             DynamicState {
   2103                 user_verified: true,
   2104                 backup: Backup::NotEligible,
   2105                 sign_count: 0,
   2106                 authenticator_attachment: AuthenticatorAttachment::None,
   2107             },
   2108         )?;
   2109         opts.start_ceremony()?
   2110             .0
   2111             .verify(&rp_id, &authentication, &mut cred, &auth_opts)
   2112             .map_err(AggErr::AuthCeremony)
   2113             .map(|_| ())
   2114     }
   2115     /// Test all, and only, possible `UserNotVerified` errors.
   2116     /// 4 * 5 * 3 * 2 * 5 = 600 tests.
   2117     /// We ignore this due to how long it takes (around 4 seconds or so).
   2118     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2119     #[ignore]
   2120     #[test]
   2121     fn test_uv_required_err() {
   2122         const ALL_CRED_PROTECT_OPTIONS: [CredentialProtectionPolicy; 4] = [
   2123             CredentialProtectionPolicy::None,
   2124             CredentialProtectionPolicy::UserVerificationOptional,
   2125             CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
   2126             CredentialProtectionPolicy::UserVerificationRequired,
   2127         ];
   2128         const ALL_PRF_CRED_OPTIONS: [PrfCredOptions; 5] = [
   2129             PrfCredOptions::None,
   2130             PrfCredOptions::FalseNoHmac,
   2131             PrfCredOptions::FalseHmacFalse,
   2132             PrfCredOptions::TrueNoHmac,
   2133             PrfCredOptions::TrueHmacTrue,
   2134         ];
   2135         const ALL_HMAC_OPTIONS: [HmacSecret; 3] =
   2136             [HmacSecret::None, HmacSecret::One, HmacSecret::Two];
   2137         const ALL_UNSOLICIT_OPTIONS: [bool; 2] = [false, true];
   2138         const ALL_NOT_FALSE_PRF_UV_OPTIONS: [PrfUvOptions; 5] = [
   2139             PrfUvOptions::None(true),
   2140             PrfUvOptions::Prf((
   2141                 PrfInput {
   2142                     first: [].as_slice(),
   2143                     second: None,
   2144                 },
   2145                 ExtensionReq::Require,
   2146             )),
   2147             PrfUvOptions::Prf((
   2148                 PrfInput {
   2149                     first: [].as_slice(),
   2150                     second: None,
   2151                 },
   2152                 ExtensionReq::Allow,
   2153             )),
   2154             PrfUvOptions::Prf((
   2155                 PrfInput {
   2156                     first: [].as_slice(),
   2157                     second: Some([].as_slice()),
   2158                 },
   2159                 ExtensionReq::Require,
   2160             )),
   2161             PrfUvOptions::Prf((
   2162                 PrfInput {
   2163                     first: [].as_slice(),
   2164                     second: Some([].as_slice()),
   2165                 },
   2166                 ExtensionReq::Allow,
   2167             )),
   2168         ];
   2169         for cred_protect in ALL_CRED_PROTECT_OPTIONS {
   2170             for prf in ALL_PRF_CRED_OPTIONS {
   2171                 for hmac in ALL_HMAC_OPTIONS {
   2172                     for error_unsolicited in ALL_UNSOLICIT_OPTIONS {
   2173                         for prf_uv in ALL_NOT_FALSE_PRF_UV_OPTIONS {
   2174                             assert!(validate(TestOptions {
   2175                                 request: TestRequestOptions {
   2176                                     error_unsolicited,
   2177                                     prf_uv,
   2178                                 },
   2179                                 response: TestResponseOptions {
   2180                                     user_verified: false,
   2181                                     hmac,
   2182                                 },
   2183                                 cred: TestCredOptions { cred_protect, prf, },
   2184                             }).map_or_else(|err| matches!(err, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::UserNotVerified)), |_| false));
   2185                         }
   2186                     }
   2187                 }
   2188             }
   2189         }
   2190     }
   2191     /// Test all, and only, possible `UserNotVerified` errors.
   2192     /// 4 * 5 * 2 * 2 = 80 tests.
   2193     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2194     #[test]
   2195     fn test_forbidden_hmac() {
   2196         const ALL_CRED_PROTECT_OPTIONS: [CredentialProtectionPolicy; 4] = [
   2197             CredentialProtectionPolicy::None,
   2198             CredentialProtectionPolicy::UserVerificationOptional,
   2199             CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
   2200             CredentialProtectionPolicy::UserVerificationRequired,
   2201         ];
   2202         const ALL_PRF_CRED_OPTIONS: [PrfCredOptions; 5] = [
   2203             PrfCredOptions::None,
   2204             PrfCredOptions::FalseNoHmac,
   2205             PrfCredOptions::FalseHmacFalse,
   2206             PrfCredOptions::TrueNoHmac,
   2207             PrfCredOptions::TrueHmacTrue,
   2208         ];
   2209         const ALL_HMAC_OPTIONS: [HmacSecret; 2] = [HmacSecret::One, HmacSecret::Two];
   2210         const ALL_UV_OPTIONS: [bool; 2] = [false, true];
   2211         for cred_protect in ALL_CRED_PROTECT_OPTIONS {
   2212             for prf in ALL_PRF_CRED_OPTIONS {
   2213                 for hmac in ALL_HMAC_OPTIONS {
   2214                     for user_verified in ALL_UV_OPTIONS {
   2215                         assert!(validate(TestOptions {
   2216                             request: TestRequestOptions {
   2217                                 error_unsolicited: true,
   2218                                 prf_uv: PrfUvOptions::None(false),
   2219                             },
   2220                             response: TestResponseOptions {
   2221                                 user_verified,
   2222                                 hmac,
   2223                             },
   2224                             cred: TestCredOptions { cred_protect, prf, },
   2225                         }).map_or_else(|err| matches!(err, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::ForbiddenHmacSecret))), |_| false));
   2226                     }
   2227                 }
   2228             }
   2229         }
   2230     }
   2231     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2232     #[test]
   2233     fn test_prf() -> Result<(), AggErr> {
   2234         let mut opts = TestOptions {
   2235             request: TestRequestOptions {
   2236                 error_unsolicited: false,
   2237                 prf_uv: PrfUvOptions::Prf((
   2238                     PrfInput {
   2239                         first: [].as_slice(),
   2240                         second: None,
   2241                     },
   2242                     ExtensionReq::Allow,
   2243                 )),
   2244             },
   2245             response: TestResponseOptions {
   2246                 user_verified: true,
   2247                 hmac: HmacSecret::None,
   2248             },
   2249             cred: TestCredOptions {
   2250                 cred_protect: CredentialProtectionPolicy::None,
   2251                 prf: PrfCredOptions::None,
   2252             },
   2253         };
   2254         validate(opts)?;
   2255         opts.request.prf_uv = PrfUvOptions::Prf((
   2256             PrfInput {
   2257                 first: [].as_slice(),
   2258                 second: None,
   2259             },
   2260             ExtensionReq::Require,
   2261         ));
   2262         opts.cred.prf = PrfCredOptions::TrueHmacTrue;
   2263         assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::MissingHmacSecret))), |_| false));
   2264         opts.response.hmac = HmacSecret::One;
   2265         opts.request.prf_uv = PrfUvOptions::Prf((
   2266             PrfInput {
   2267                 first: [].as_slice(),
   2268                 second: None,
   2269             },
   2270             ExtensionReq::Allow,
   2271         ));
   2272         opts.cred.prf = PrfCredOptions::TrueNoHmac;
   2273         validate(opts)?;
   2274         opts.response.hmac = HmacSecret::Two;
   2275         assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::InvalidHmacSecretValue(OneOrTwo::One, OneOrTwo::Two)))), |_| false));
   2276         opts.response.hmac = HmacSecret::One;
   2277         opts.cred.prf = PrfCredOptions::FalseNoHmac;
   2278         assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::PrfRequestedForPrfIncapableCred))), |_| false));
   2279         opts.response.user_verified = false;
   2280         opts.request.prf_uv = PrfUvOptions::None(false);
   2281         opts.cred.prf = PrfCredOptions::TrueHmacTrue;
   2282         assert!(validate(opts).map_or_else(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::UserNotVerifiedHmacSecret))), |_| false));
   2283         Ok(())
   2284     }
   2285 }