webauthn_rp

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

auth.rs (94356B)


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