webauthn_rp

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

auth.rs (90639B)


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