webauthn_rp

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

auth.rs (90674B)


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