webauthn_rp

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

register.rs (143983B)


      1 extern crate alloc;
      2 use super::{
      3     super::{
      4         DynamicState, Metadata, RegisteredCredential, StaticState,
      5         response::{
      6             AuthenticatorAttachment,
      7             register::{
      8                 Attestation, AttestationFormat, AuthenticatorExtensionOutput,
      9                 ClientExtensionsOutputs, CredentialProtectionPolicy, HmacSecret, Registration,
     10                 UncompressedPubKey,
     11                 error::{ExtensionErr, RegCeremonyErr},
     12             },
     13         },
     14     },
     15     BackupReq, Ceremony, Challenge, CredentialMediationRequirement, ExtensionInfo, ExtensionReq,
     16     FIVE_MINUTES, Hint, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId, SentChallenge,
     17     TimedCeremony, UserVerificationRequirement,
     18     register::error::{CreationOptionsErr, NicknameErr, UsernameErr},
     19 };
     20 #[cfg(doc)]
     21 use crate::{
     22     request::{
     23         AsciiDomain, AsciiDomainStatic, DomainOrigin, Url,
     24         auth::{AuthenticationVerificationOptions, PublicKeyCredentialRequestOptions},
     25     },
     26     response::{AuthTransports, AuthenticatorTransport, Backup, CollectedClientData, Flag},
     27 };
     28 use alloc::borrow::Cow;
     29 use core::{
     30     borrow::Borrow,
     31     cmp::Ordering,
     32     fmt::{self, Display, Formatter},
     33     hash::{Hash, Hasher},
     34     mem,
     35     num::{NonZeroU32, NonZeroU64},
     36     time::Duration,
     37 };
     38 use precis_profiles::{UsernameCasePreserved, precis_core::profile::Profile as _};
     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 functionality to (de)serialize data to a data store.
     44 #[cfg(feature = "bin")]
     45 pub mod bin;
     46 /// Contains functionality that needs to be accessible when `bin` or `serde` are not enabled.
     47 #[cfg(feature = "custom")]
     48 mod custom;
     49 /// Contains error types.
     50 pub mod error;
     51 /// Contains functionality to (de)serialize data to a client.
     52 #[cfg(feature = "serde")]
     53 pub mod ser;
     54 /// Contains functionality to (de)serialize [`RegistrationServerState`] to a data store.
     55 #[cfg(feature = "serializable_server_state")]
     56 pub mod ser_server_state;
     57 /// Used by [`Extension::cred_protect`] to enforce the [`CredentialProtectionPolicy`] sent by the client via
     58 /// [`Registration`].
     59 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
     60 pub enum CredProtect {
     61     /// No `credProtect` request.
     62     #[default]
     63     None,
     64     /// Request
     65     /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional)
     66     /// but allow any.
     67     ///
     68     /// The `bool` corresponds to
     69     /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy).
     70     UserVerificationOptional(bool, ExtensionInfo),
     71     /// Request
     72     /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist);
     73     /// and when enforcing the value, disallow [`CredentialProtectionPolicy::UserVerificationOptional`].
     74     ///
     75     /// The `bool` corresponds to
     76     /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy).
     77     UserVerificationOptionalWithCredentialIdList(bool, ExtensionInfo),
     78     /// Request
     79     /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired);
     80     /// and when enforcing the value, only allow [`CredentialProtectionPolicy::UserVerificationRequired`].
     81     ///
     82     /// The `bool` corresponds to
     83     /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy).
     84     UserVerificationRequired(bool, ExtensionInfo),
     85 }
     86 impl CredProtect {
     87     /// Validates `other` is allowed based on `self`.
     88     ///
     89     /// # Errors
     90     ///
     91     /// Errors iff other is a less "secure" policy than `self` when enforcing the value or other does not exist
     92     /// despite requiring a value to be sent back.
     93     ///
     94     /// Note a missing response is OK when enforcing a value.
     95     const fn validate(self, other: CredentialProtectionPolicy) -> Result<(), ExtensionErr> {
     96         match self {
     97             Self::None => Ok(()),
     98             Self::UserVerificationOptional(_, info) => {
     99                 if matches!(other, CredentialProtectionPolicy::None) {
    100                     if matches!(
    101                         info,
    102                         ExtensionInfo::RequireEnforceValue | ExtensionInfo::RequireDontEnforceValue
    103                     ) {
    104                         Err(ExtensionErr::MissingCredProtect)
    105                     } else {
    106                         Ok(())
    107                     }
    108                 } else {
    109                     Ok(())
    110                 }
    111             }
    112             Self::UserVerificationOptionalWithCredentialIdList(_, info) => match info {
    113                 ExtensionInfo::RequireEnforceValue => match other {
    114                     CredentialProtectionPolicy::None => Err(ExtensionErr::MissingCredProtect),
    115                     CredentialProtectionPolicy::UserVerificationOptional => {
    116                         Err(ExtensionErr::InvalidCredProtectValue(self, other))
    117                     }
    118                     CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
    119                     | CredentialProtectionPolicy::UserVerificationRequired => Ok(()),
    120                 },
    121                 ExtensionInfo::RequireDontEnforceValue => {
    122                     if matches!(other, CredentialProtectionPolicy::None) {
    123                         Err(ExtensionErr::MissingCredProtect)
    124                     } else {
    125                         Ok(())
    126                     }
    127                 }
    128                 ExtensionInfo::AllowEnforceValue => {
    129                     if matches!(other, CredentialProtectionPolicy::UserVerificationOptional) {
    130                         Err(ExtensionErr::InvalidCredProtectValue(self, other))
    131                     } else {
    132                         Ok(())
    133                     }
    134                 }
    135                 ExtensionInfo::AllowDontEnforceValue => Ok(()),
    136             },
    137             Self::UserVerificationRequired(_, info) => match info {
    138                 ExtensionInfo::RequireEnforceValue => match other {
    139                     CredentialProtectionPolicy::None => Err(ExtensionErr::MissingCredProtect),
    140                     CredentialProtectionPolicy::UserVerificationOptional
    141                     | CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
    142                         Err(ExtensionErr::InvalidCredProtectValue(self, other))
    143                     }
    144                     CredentialProtectionPolicy::UserVerificationRequired => Ok(()),
    145                 },
    146                 ExtensionInfo::RequireDontEnforceValue => {
    147                     if matches!(other, CredentialProtectionPolicy::None) {
    148                         Err(ExtensionErr::MissingCredProtect)
    149                     } else {
    150                         Ok(())
    151                     }
    152                 }
    153                 ExtensionInfo::AllowEnforceValue => {
    154                     if matches!(
    155                         other,
    156                         CredentialProtectionPolicy::None
    157                             | CredentialProtectionPolicy::UserVerificationRequired
    158                     ) {
    159                         Ok(())
    160                     } else {
    161                         Err(ExtensionErr::InvalidCredProtectValue(self, other))
    162                     }
    163                 }
    164                 ExtensionInfo::AllowDontEnforceValue => Ok(()),
    165             },
    166         }
    167     }
    168 }
    169 impl Display for CredProtect {
    170     #[inline]
    171     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    172         match *self {
    173             Self::None => f.write_str("do not sent a credProtect request"),
    174             Self::UserVerificationOptional(enforce, info) => {
    175                 write!(
    176                     f,
    177                     "request user verification optional with enforcement {enforce} and {info}"
    178                 )
    179             }
    180             Self::UserVerificationOptionalWithCredentialIdList(enforce, info) => write!(
    181                 f,
    182                 "user verification optional with credential ID list with enforcement {enforce} and {info}"
    183             ),
    184             Self::UserVerificationRequired(enforce, info) => {
    185                 write!(
    186                     f,
    187                     "user verification required with enforcement {enforce} and {info}"
    188                 )
    189             }
    190         }
    191     }
    192 }
    193 /// String returned from the [Nickname Enforcement rule](https://www.rfc-editor.org/rfc/rfc8266#section-2.3)
    194 /// as defined in RFC 8266.
    195 ///
    196 /// Note [string truncation](https://www.w3.org/TR/webauthn-3/#sctn-strings-truncation) is allowed, so one may
    197 /// want to enforce [`Self::RECOMMENDED_MAX_LEN`].
    198 #[derive(Clone, Debug)]
    199 pub struct Nickname<'a>(Cow<'a, str>);
    200 impl<'a> Nickname<'a> {
    201     /// The maximum allowed length.
    202     pub const MAX_LEN: usize = 1023;
    203     /// The recommended maximum length to allow.
    204     pub const RECOMMENDED_MAX_LEN: usize = 64;
    205     /// Returns a `Nickname` that consumes `self`. When `self` owns the data, the data is simply moved;
    206     /// when the data is borrowed, then it is cloned into an owned instance.
    207     #[inline]
    208     #[must_use]
    209     pub fn into_owned<'b>(self) -> Nickname<'b> {
    210         Nickname(Cow::Owned(self.0.into_owned()))
    211     }
    212     /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of
    213     /// [`Self::MAX_LEN`].
    214     ///
    215     /// # Errors
    216     ///
    217     /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3)
    218     /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`].
    219     #[expect(single_use_lifetimes, reason = "false positive")]
    220     #[inline]
    221     pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> {
    222         precis_profiles::Nickname::new()
    223             .enforce(value)
    224             .map_err(|_e| NicknameErr::Rfc8266)
    225             .and_then(|val| {
    226                 if val.len() <= Self::RECOMMENDED_MAX_LEN {
    227                     Ok(Self(val))
    228                 } else {
    229                     Err(NicknameErr::Len)
    230                 }
    231             })
    232     }
    233     /// Same as [`Self::try_from`].
    234     ///
    235     /// # Errors
    236     ///
    237     /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3)
    238     /// or the resulting length exceeds [`Self::MAX_LEN`].
    239     #[expect(single_use_lifetimes, reason = "false positive")]
    240     #[inline]
    241     pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> {
    242         Self::try_from(value)
    243     }
    244 }
    245 impl AsRef<str> for Nickname<'_> {
    246     #[inline]
    247     fn as_ref(&self) -> &str {
    248         self.0.as_ref()
    249     }
    250 }
    251 impl Borrow<str> for Nickname<'_> {
    252     #[inline]
    253     fn borrow(&self) -> &str {
    254         self.0.as_ref()
    255     }
    256 }
    257 impl<'a: 'b, 'b> From<&'a Nickname<'_>> for Nickname<'b> {
    258     #[inline]
    259     fn from(value: &'a Nickname<'_>) -> Self {
    260         match value.0 {
    261             Cow::Borrowed(val) => Self(Cow::Borrowed(val)),
    262             Cow::Owned(ref val) => Self(Cow::Borrowed(val.as_str())),
    263         }
    264     }
    265 }
    266 impl<'a: 'b, 'b> From<Nickname<'a>> for Cow<'b, str> {
    267     #[inline]
    268     fn from(value: Nickname<'a>) -> Self {
    269         value.0
    270     }
    271 }
    272 impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for Nickname<'b> {
    273     type Error = NicknameErr;
    274     /// # Examples
    275     ///
    276     /// ```
    277     /// # use std::borrow::Cow;
    278     /// # use webauthn_rp::request::register::{error::NicknameErr, Nickname};
    279     /// assert_eq!(
    280     ///     Nickname::try_from(Cow::Borrowed("Srinivasa Ramanujan"))?.as_ref(),
    281     ///     "Srinivasa Ramanujan"
    282     /// );
    283     /// assert_eq!(
    284     ///     Nickname::try_from(Cow::Borrowed("श्रीनिवास रामानुजन्"))?.as_ref(),
    285     ///     "श्रीनिवास रामानुजन्"
    286     /// );
    287     /// // Empty strings are not valid.
    288     /// assert!(Nickname::try_from(Cow::Borrowed("")).map_or_else(
    289     ///     |e| matches!(e, NicknameErr::Rfc8266),
    290     ///     |_| false
    291     /// ));
    292     /// # Ok::<_, webauthn_rp::AggErr>(())
    293     /// ```
    294     #[inline]
    295     fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
    296         precis_profiles::Nickname::new()
    297             .enforce(value)
    298             .map_err(|_e| NicknameErr::Rfc8266)
    299             .and_then(|val| {
    300                 if val.len() <= Self::MAX_LEN {
    301                     Ok(Self(val))
    302                 } else {
    303                     Err(NicknameErr::Len)
    304                 }
    305             })
    306     }
    307 }
    308 impl<'a: 'b, 'b> TryFrom<&'a str> for Nickname<'b> {
    309     type Error = NicknameErr;
    310     /// Same as [`Nickname::try_from`] except the input is a `str`.
    311     #[inline]
    312     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
    313         Self::try_from(Cow::Borrowed(value))
    314     }
    315 }
    316 impl TryFrom<String> for Nickname<'_> {
    317     type Error = NicknameErr;
    318     /// Same as [`Nickname::try_from`] except the input is a `String`.
    319     #[inline]
    320     fn try_from(value: String) -> Result<Self, Self::Error> {
    321         Self::try_from(Cow::Owned(value))
    322     }
    323 }
    324 impl PartialEq<Nickname<'_>> for Nickname<'_> {
    325     #[inline]
    326     fn eq(&self, other: &Nickname<'_>) -> bool {
    327         self.0 == other.0
    328     }
    329 }
    330 impl PartialEq<&Nickname<'_>> for Nickname<'_> {
    331     #[inline]
    332     fn eq(&self, other: &&Nickname<'_>) -> bool {
    333         *self == **other
    334     }
    335 }
    336 impl PartialEq<Nickname<'_>> for &Nickname<'_> {
    337     #[inline]
    338     fn eq(&self, other: &Nickname<'_>) -> bool {
    339         **self == *other
    340     }
    341 }
    342 impl Eq for Nickname<'_> {}
    343 impl Hash for Nickname<'_> {
    344     #[inline]
    345     fn hash<H: Hasher>(&self, state: &mut H) {
    346         self.0.hash(state);
    347     }
    348 }
    349 impl PartialOrd<Nickname<'_>> for Nickname<'_> {
    350     #[inline]
    351     fn partial_cmp(&self, other: &Nickname<'_>) -> Option<Ordering> {
    352         self.0.partial_cmp(&other.0)
    353     }
    354 }
    355 impl Ord for Nickname<'_> {
    356     #[inline]
    357     fn cmp(&self, other: &Self) -> Ordering {
    358         self.0.cmp(&other.0)
    359     }
    360 }
    361 /// Name intended to be displayed to a user.
    362 #[derive(Clone, Debug)]
    363 pub enum DisplayName<'a> {
    364     /// A blank string.
    365     Blank,
    366     /// A non-blank string conforming to RFC 8266.
    367     Nickname(Nickname<'a>),
    368 }
    369 impl<'a> DisplayName<'a> {
    370     /// The maximum allowed length.
    371     pub const MAX_LEN: usize = 1023;
    372     /// The recommended maximum length to allow.
    373     pub const RECOMMENDED_MAX_LEN: usize = 64;
    374     /// Returns a `DisplayName` that consumes `self`. When `self` owns the data, the data is simply moved;
    375     /// when the data is borrowed, then it is cloned into an owned instance.
    376     #[inline]
    377     #[must_use]
    378     pub fn into_owned<'b>(self) -> DisplayName<'b> {
    379         match self {
    380             Self::Blank => DisplayName::Blank,
    381             Self::Nickname(val) => DisplayName::Nickname(val.into_owned()),
    382         }
    383     }
    384     /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of
    385     /// [`Self::MAX_LEN`].
    386     ///
    387     /// # Errors
    388     ///
    389     /// Errors iff `value` is not empty and [`Nickname::with_recommended_len`] errors.
    390     #[expect(single_use_lifetimes, reason = "false positive")]
    391     #[inline]
    392     pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> {
    393         if value.is_empty() {
    394             Ok(Self::Blank)
    395         } else {
    396             Nickname::with_recommended_len(value).map(Self::Nickname)
    397         }
    398     }
    399     /// Same as [`Self::try_from`].
    400     ///
    401     /// # Errors
    402     ///
    403     /// Errors iff `value` is not empty and [`Nickname::with_max_len`] errors.
    404     #[expect(single_use_lifetimes, reason = "false positive")]
    405     #[inline]
    406     pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> {
    407         Self::try_from(value)
    408     }
    409 }
    410 impl AsRef<str> for DisplayName<'_> {
    411     #[inline]
    412     fn as_ref(&self) -> &str {
    413         match *self {
    414             Self::Blank => "",
    415             Self::Nickname(ref val) => val.as_ref(),
    416         }
    417     }
    418 }
    419 impl Borrow<str> for DisplayName<'_> {
    420     #[inline]
    421     fn borrow(&self) -> &str {
    422         self.as_ref()
    423     }
    424 }
    425 impl<'a: 'b, 'b> From<&'a DisplayName<'_>> for DisplayName<'b> {
    426     #[inline]
    427     fn from(value: &'a DisplayName<'_>) -> Self {
    428         match *value {
    429             DisplayName::Blank => Self::Blank,
    430             DisplayName::Nickname(ref val) => Self::Nickname(val.into()),
    431         }
    432     }
    433 }
    434 impl<'a: 'b, 'b> From<DisplayName<'a>> for Cow<'b, str> {
    435     #[inline]
    436     fn from(value: DisplayName<'a>) -> Self {
    437         match value {
    438             DisplayName::Blank => Cow::Borrowed(""),
    439             DisplayName::Nickname(val) => val.into(),
    440         }
    441     }
    442 }
    443 impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for DisplayName<'b> {
    444     type Error = NicknameErr;
    445     /// # Examples
    446     ///
    447     /// ```
    448     /// # use std::borrow::Cow;
    449     /// # use webauthn_rp::request::register::{error::NicknameErr, DisplayName};
    450     /// assert_eq!(
    451     ///     DisplayName::try_from(Cow::Borrowed(""))?.as_ref(),
    452     ///     ""
    453     /// );
    454     /// assert_eq!(
    455     ///     DisplayName::try_from(Cow::Borrowed("Sir Isaac Newton"))?.as_ref(),
    456     ///     "Sir Isaac Newton"
    457     /// );
    458     /// # Ok::<_, NicknameErr>(())
    459     /// ```
    460     #[inline]
    461     fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
    462         if value.is_empty() {
    463             Ok(Self::Blank)
    464         } else {
    465             Nickname::try_from(value).map(Self::Nickname)
    466         }
    467     }
    468 }
    469 impl<'a: 'b, 'b> TryFrom<&'a str> for DisplayName<'b> {
    470     type Error = NicknameErr;
    471     /// Same as [`DisplayName::try_from`] except the input is a `str`.
    472     #[inline]
    473     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
    474         Self::try_from(Cow::Borrowed(value))
    475     }
    476 }
    477 impl TryFrom<String> for DisplayName<'_> {
    478     type Error = NicknameErr;
    479     /// Same as [`DisplayName::try_from`] except the input is a `String`.
    480     #[inline]
    481     fn try_from(value: String) -> Result<Self, Self::Error> {
    482         Self::try_from(Cow::Owned(value))
    483     }
    484 }
    485 impl PartialEq<DisplayName<'_>> for DisplayName<'_> {
    486     #[inline]
    487     fn eq(&self, other: &DisplayName<'_>) -> bool {
    488         self.as_ref() == other.as_ref()
    489     }
    490 }
    491 impl PartialEq<&DisplayName<'_>> for DisplayName<'_> {
    492     #[inline]
    493     fn eq(&self, other: &&DisplayName<'_>) -> bool {
    494         *self == **other
    495     }
    496 }
    497 impl PartialEq<DisplayName<'_>> for &DisplayName<'_> {
    498     #[inline]
    499     fn eq(&self, other: &DisplayName<'_>) -> bool {
    500         **self == *other
    501     }
    502 }
    503 impl Eq for DisplayName<'_> {}
    504 impl Hash for DisplayName<'_> {
    505     #[inline]
    506     fn hash<H: Hasher>(&self, state: &mut H) {
    507         self.as_ref().hash(state);
    508     }
    509 }
    510 impl PartialOrd<DisplayName<'_>> for DisplayName<'_> {
    511     #[inline]
    512     fn partial_cmp(&self, other: &DisplayName<'_>) -> Option<Ordering> {
    513         Some(self.cmp(other))
    514     }
    515 }
    516 impl Ord for DisplayName<'_> {
    517     #[inline]
    518     fn cmp(&self, other: &Self) -> Ordering {
    519         self.as_ref().cmp(other.as_ref())
    520     }
    521 }
    522 /// String returned from the
    523 /// [UsernameCasePreserved Enforcement rule](https://www.rfc-editor.org/rfc/rfc8265#section-3.4.3) as defined in
    524 /// RFC 8265.
    525 ///
    526 /// Note [string truncation](https://www.w3.org/TR/webauthn-3/#sctn-strings-truncation) is allowed, so one may
    527 /// want to enforce [`Self::RECOMMENDED_MAX_LEN`].
    528 #[derive(Clone, Debug)]
    529 pub struct Username<'a>(Cow<'a, str>);
    530 impl<'a> Username<'a> {
    531     /// The maximum allowed length.
    532     pub const MAX_LEN: usize = 1023;
    533     /// The recommended maximum length to allow.
    534     pub const RECOMMENDED_MAX_LEN: usize = 64;
    535     /// Returns a `Username` that consumes `self`. When `self` owns the data, the data is simply moved;
    536     /// when the data is borrowed, then it is cloned into an owned instance.
    537     #[inline]
    538     #[must_use]
    539     pub fn into_owned<'b>(self) -> Username<'b> {
    540         Username(Cow::Owned(self.0.into_owned()))
    541     }
    542     /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of
    543     /// [`Self::MAX_LEN`].
    544     ///
    545     /// # Errors
    546     ///
    547     /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3)
    548     /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`].
    549     #[expect(single_use_lifetimes, reason = "false positive")]
    550     #[inline]
    551     pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> {
    552         UsernameCasePreserved::default()
    553             .enforce(value)
    554             .map_err(|_e| UsernameErr::Rfc8265)
    555             .and_then(|val| {
    556                 if val.len() <= Self::RECOMMENDED_MAX_LEN {
    557                     Ok(Self(val))
    558                 } else {
    559                     Err(UsernameErr::Len)
    560                 }
    561             })
    562     }
    563     /// Same as [`Self::try_from`].
    564     ///
    565     /// # Errors
    566     ///
    567     /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3)
    568     /// or the resulting length exceeds [`Self::MAX_LEN`].
    569     #[expect(single_use_lifetimes, reason = "false positive")]
    570     #[inline]
    571     pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> {
    572         Self::try_from(value)
    573     }
    574 }
    575 impl AsRef<str> for Username<'_> {
    576     #[inline]
    577     fn as_ref(&self) -> &str {
    578         self.0.as_ref()
    579     }
    580 }
    581 impl Borrow<str> for Username<'_> {
    582     #[inline]
    583     fn borrow(&self) -> &str {
    584         self.0.as_ref()
    585     }
    586 }
    587 impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for Username<'b> {
    588     type Error = UsernameErr;
    589     /// # Examples
    590     ///
    591     /// ```
    592     /// # use std::borrow::Cow;
    593     /// # use webauthn_rp::request::register::{error::UsernameErr, Username};
    594     /// assert_eq!(
    595     ///     Username::try_from(Cow::Borrowed("leonhard.euler"))?.as_ref(),
    596     ///     "leonhard.euler"
    597     /// );
    598     /// // Empty strings are not valid.
    599     /// assert!(Username::try_from(Cow::Borrowed("")).map_or_else(
    600     ///     |e| matches!(e, UsernameErr::Rfc8265),
    601     ///     |_| false
    602     /// ));
    603     /// # Ok::<_, webauthn_rp::AggErr>(())
    604     /// ```
    605     #[inline]
    606     fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
    607         UsernameCasePreserved::default()
    608             .enforce(value)
    609             .map_err(|_e| UsernameErr::Rfc8265)
    610             .and_then(|val| {
    611                 if val.len() <= Self::MAX_LEN {
    612                     Ok(Self(val))
    613                 } else {
    614                     Err(UsernameErr::Len)
    615                 }
    616             })
    617     }
    618 }
    619 impl<'a: 'b, 'b> TryFrom<&'a str> for Username<'b> {
    620     type Error = UsernameErr;
    621     /// Same as [`Username::try_from`] except the input is a `str`.
    622     #[inline]
    623     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
    624         Self::try_from(Cow::Borrowed(value))
    625     }
    626 }
    627 impl TryFrom<String> for Username<'_> {
    628     type Error = UsernameErr;
    629     /// Same as [`Username::try_from`] except the input is a `String`.
    630     #[inline]
    631     fn try_from(value: String) -> Result<Self, Self::Error> {
    632         Self::try_from(Cow::Owned(value))
    633     }
    634 }
    635 impl<'a: 'b, 'b> From<Username<'a>> for Cow<'b, str> {
    636     #[inline]
    637     fn from(value: Username<'a>) -> Self {
    638         value.0
    639     }
    640 }
    641 impl<'a: 'b, 'b> From<&'a Username<'_>> for Username<'b> {
    642     #[inline]
    643     fn from(value: &'a Username<'_>) -> Self {
    644         match value.0 {
    645             Cow::Borrowed(val) => Self(Cow::Borrowed(val)),
    646             Cow::Owned(ref val) => Self(Cow::Borrowed(val.as_str())),
    647         }
    648     }
    649 }
    650 impl PartialEq<Username<'_>> for Username<'_> {
    651     #[inline]
    652     fn eq(&self, other: &Username<'_>) -> bool {
    653         self.0 == other.0
    654     }
    655 }
    656 impl PartialEq<&Username<'_>> for Username<'_> {
    657     #[inline]
    658     fn eq(&self, other: &&Username<'_>) -> bool {
    659         *self == **other
    660     }
    661 }
    662 impl PartialEq<Username<'_>> for &Username<'_> {
    663     #[inline]
    664     fn eq(&self, other: &Username<'_>) -> bool {
    665         **self == *other
    666     }
    667 }
    668 impl Eq for Username<'_> {}
    669 impl Hash for Username<'_> {
    670     #[inline]
    671     fn hash<H: Hasher>(&self, state: &mut H) {
    672         self.0.hash(state);
    673     }
    674 }
    675 impl PartialOrd<Username<'_>> for Username<'_> {
    676     #[inline]
    677     fn partial_cmp(&self, other: &Username<'_>) -> Option<Ordering> {
    678         self.0.partial_cmp(&other.0)
    679     }
    680 }
    681 impl Ord for Username<'_> {
    682     #[inline]
    683     fn cmp(&self, other: &Self) -> Ordering {
    684         self.0.cmp(&other.0)
    685     }
    686 }
    687 /// [`COSEAlgorithmIdentifier`](https://www.w3.org/TR/webauthn-3/#typedefdef-cosealgorithmidentifier).
    688 ///
    689 /// Note the order of variants is the following:
    690 ///
    691 /// [`Self::Mldsa87`] `<` [`Self::Mldsa65`] `<` [`Self::Mldsa44`] `<` [`Self::Eddsa`] `<` [`Self::Es256`]
    692 /// `<` [`Self::Es384`] `<` [`Self::Rs256`].
    693 ///
    694 /// This is relevant for [`CoseAlgorithmIdentifiers`]. For example a `CoseAlgorithmIdentifiers`
    695 /// that contains `Self::Mldsa87` will prioritize it over all others.
    696 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
    697 pub enum CoseAlgorithmIdentifier {
    698     /// [ML-DSA-87](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    699     Mldsa87,
    700     /// [ML-DSA-65](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    701     Mldsa65,
    702     /// [ML-DSA-44](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    703     Mldsa44,
    704     /// [EdDSA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    705     ///
    706     /// Note that Ed25519 must be used for the `crv` parameter
    707     /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier).
    708     Eddsa,
    709     /// [ES256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    710     ///
    711     /// Note the uncompressed form must be used and P-256 must be used for the `crv` parameter
    712     /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier).
    713     Es256,
    714     /// [ES384](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    715     ///
    716     /// Note the uncompressed form must be used and P-384 must be used for the `crv` parameter
    717     /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier).
    718     Es384,
    719     /// [RS256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
    720     Rs256,
    721 }
    722 impl CoseAlgorithmIdentifier {
    723     /// Transforms `self` into a `u8`.
    724     const fn to_u8(self) -> u8 {
    725         match self {
    726             Self::Mldsa87 => 0x1,
    727             Self::Mldsa65 => 0x2,
    728             Self::Mldsa44 => 0x4,
    729             Self::Eddsa => 0x8,
    730             Self::Es256 => 0x10,
    731             Self::Es384 => 0x20,
    732             Self::Rs256 => 0x40,
    733         }
    734     }
    735 }
    736 impl PartialEq<&Self> for CoseAlgorithmIdentifier {
    737     #[inline]
    738     fn eq(&self, other: &&Self) -> bool {
    739         *self == **other
    740     }
    741 }
    742 impl PartialEq<CoseAlgorithmIdentifier> for &CoseAlgorithmIdentifier {
    743     #[inline]
    744     fn eq(&self, other: &CoseAlgorithmIdentifier) -> bool {
    745         **self == *other
    746     }
    747 }
    748 /// Non-empty ordered set of [`CoseAlgorithmIdentifier`]s.
    749 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    750 pub struct CoseAlgorithmIdentifiers(u8);
    751 impl CoseAlgorithmIdentifiers {
    752     /// Contains all [`CoseAlgorithmIdentifier`]s.
    753     pub const ALL: Self = Self(0)
    754         .add(CoseAlgorithmIdentifier::Mldsa87)
    755         .add(CoseAlgorithmIdentifier::Mldsa65)
    756         .add(CoseAlgorithmIdentifier::Mldsa44)
    757         .add(CoseAlgorithmIdentifier::Eddsa)
    758         .add(CoseAlgorithmIdentifier::Es256)
    759         .add(CoseAlgorithmIdentifier::Es384)
    760         .add(CoseAlgorithmIdentifier::Rs256);
    761     /// Returns a `CoseAlgorithmIdentifiers` containing all `CoseAlgorithmIdentifier`s in `self` plus `alg`.
    762     const fn add(self, alg: CoseAlgorithmIdentifier) -> Self {
    763         Self(self.0 | alg.to_u8())
    764     }
    765     /// Returns a copy of `self` with `alg` removed if there would be at least one `CoseAlgorithmIdentifier`
    766     /// remaining; otherwise returns `self`.
    767     #[inline]
    768     #[must_use]
    769     pub const fn remove(self, alg: CoseAlgorithmIdentifier) -> Self {
    770         let val = alg.to_u8();
    771         if self.0 == val {
    772             self
    773         } else {
    774             Self(self.0 & !val)
    775         }
    776     }
    777     /// Returns `true` iff `self` contains `alg`.
    778     #[inline]
    779     #[must_use]
    780     pub const fn contains(self, alg: CoseAlgorithmIdentifier) -> bool {
    781         let val = alg.to_u8();
    782         self.0 & val == val
    783     }
    784     /// Validates `other` is allowed based on `self`.
    785     const fn validate(self, other: UncompressedPubKey<'_>) -> Result<(), RegCeremonyErr> {
    786         if match other {
    787             UncompressedPubKey::MlDsa87(_) => self.contains(CoseAlgorithmIdentifier::Mldsa87),
    788             UncompressedPubKey::MlDsa65(_) => self.contains(CoseAlgorithmIdentifier::Mldsa65),
    789             UncompressedPubKey::MlDsa44(_) => self.contains(CoseAlgorithmIdentifier::Mldsa44),
    790             UncompressedPubKey::Ed25519(_) => self.contains(CoseAlgorithmIdentifier::Eddsa),
    791             UncompressedPubKey::P256(_) => self.contains(CoseAlgorithmIdentifier::Es256),
    792             UncompressedPubKey::P384(_) => self.contains(CoseAlgorithmIdentifier::Es384),
    793             UncompressedPubKey::Rsa(_) => self.contains(CoseAlgorithmIdentifier::Rs256),
    794         } {
    795             Ok(())
    796         } else {
    797             Err(RegCeremonyErr::PublicKeyAlgorithmMismatch)
    798         }
    799     }
    800 }
    801 impl Default for CoseAlgorithmIdentifiers {
    802     /// Returns [`Self::ALL`].
    803     #[inline]
    804     fn default() -> Self {
    805         Self::ALL
    806     }
    807 }
    808 /// Four to sixty-three inclusively.
    809 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    810 #[repr(u8)]
    811 pub enum FourToSixtyThree {
    812     /// 4.
    813     Four = 4,
    814     /// 5.
    815     Five,
    816     /// 6.
    817     Six,
    818     /// 7.
    819     Seven,
    820     /// 8.
    821     Eight,
    822     /// 9.
    823     Nine,
    824     /// 10.
    825     Ten,
    826     /// 11.
    827     Eleven,
    828     /// 12.
    829     Twelve,
    830     /// 13.
    831     Thirteen,
    832     /// 14.
    833     Fourteen,
    834     /// 15.
    835     Fifteen,
    836     /// 16.
    837     Sixteen,
    838     /// 17.
    839     Seventeen,
    840     /// 18.
    841     Eighteen,
    842     /// 19.
    843     Nineteen,
    844     /// 20.
    845     Twenty,
    846     /// 21.
    847     TwentyOne,
    848     /// 22.
    849     TwentyTwo,
    850     /// 23.
    851     TwentyThree,
    852     /// 24.
    853     TwentyFour,
    854     /// 25.
    855     TwentyFive,
    856     /// 26.
    857     TwentySix,
    858     /// 27.
    859     TwentySeven,
    860     /// 28.
    861     TwentyEight,
    862     /// 29.
    863     TwentyNine,
    864     /// 30.
    865     Thirty,
    866     /// 31.
    867     ThirtyOne,
    868     /// 32.
    869     ThirtyTwo,
    870     /// 33.
    871     ThirtyThree,
    872     /// 34.
    873     ThirtyFour,
    874     /// 35.
    875     ThirtyFive,
    876     /// 36.
    877     ThirtySix,
    878     /// 37.
    879     ThirtySeven,
    880     /// 38.
    881     ThirtyEight,
    882     /// 39.
    883     ThirtyNine,
    884     /// 40.
    885     Fourty,
    886     /// 41.
    887     FourtyOne,
    888     /// 42.
    889     FourtyTwo,
    890     /// 43.
    891     FourtyThree,
    892     /// 44.
    893     FourtyFour,
    894     /// 45.
    895     FourtyFive,
    896     /// 46.
    897     FourtySix,
    898     /// 47.
    899     FourtySeven,
    900     /// 48.
    901     FourtyEight,
    902     /// 49.
    903     FourtyNine,
    904     /// 50.
    905     Fifty,
    906     /// 51.
    907     FiftyOne,
    908     /// 52.
    909     FiftyTwo,
    910     /// 53.
    911     FiftyThree,
    912     /// 54.
    913     FiftyFour,
    914     /// 55.
    915     FiftyFive,
    916     /// 56.
    917     FiftySix,
    918     /// 57.
    919     FiftySeven,
    920     /// 58.
    921     FiftyEight,
    922     /// 59.
    923     FiftyNine,
    924     /// 60.
    925     Sixty,
    926     /// 61.
    927     SixtyOne,
    928     /// 62.
    929     SixtyTwo,
    930     /// 63.
    931     SixtyThree,
    932 }
    933 impl FourToSixtyThree {
    934     /// Returns the equivalent `u8`.
    935     #[expect(clippy::as_conversions, reason = "comment justifies correctness")]
    936     #[inline]
    937     #[must_use]
    938     pub const fn into_u8(self) -> u8 {
    939         // This is correct since `Self` is `repr(u8)`, and the initial discriminant has the value `4`
    940         // and subsequent discriminants are implicitly incremented by 1.
    941         self as u8
    942     }
    943     /// Returns `Some` representing `val` iff `val` is inclusively between 4 and 63.
    944     #[expect(unsafe_code, reason = "comment justifies correctness")]
    945     #[inline]
    946     #[must_use]
    947     pub const fn from_u8(val: u8) -> Option<Self> {
    948         match val {
    949             0..=3 | 64.. => None,
    950             _ => {
    951                 // SAFETY:
    952                 // `val` is inclusively between 4 and 63, and `Self` is `repr(u8)`; thus this
    953                 // is safe and correct.
    954                 Some(unsafe { mem::transmute::<u8, Self>(val) })
    955             }
    956         }
    957     }
    958 }
    959 impl Display for FourToSixtyThree {
    960     #[inline]
    961     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    962         self.into_u8().fmt(f)
    963     }
    964 }
    965 impl From<FourToSixtyThree> for u8 {
    966     #[inline]
    967     fn from(value: FourToSixtyThree) -> Self {
    968         value.into_u8()
    969     }
    970 }
    971 impl Default for FourToSixtyThree {
    972     #[inline]
    973     fn default() -> Self {
    974         Self::Four
    975     }
    976 }
    977 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client.
    978 #[derive(Clone, Copy, Debug)]
    979 pub struct Extension<'prf_first, 'prf_second> {
    980     /// [`credProps`](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension).
    981     ///
    982     /// The best one can do to ensure a server-side credential is created is by sending
    983     /// [`ResidentKeyRequirement::Discouraged`]; however authenticators are still allowed
    984     /// to create a client-side credential. To more definitively check that a server-side credential is
    985     /// created, send this extension. Note that it can be difficult to impossible for a client/user agent to
    986     /// know that a server-side credential is created; thus even when the response is
    987     /// `Some(CredentialPropertiesOutput { rk: Some(false) })`, a client-side/"resident" credential could still
    988     /// have been created. One may have better luck checking if [`AuthTransports::contains`]
    989     /// [`AuthenticatorTransport::Internal`] and using that as an indicator if a client-side credential was created.
    990     ///
    991     /// In the event [`ClientExtensionsOutputs::cred_props`] is `Some(CredentialPropertiesOutput { rk: Some(false) })`
    992     /// and [`ResidentKeyRequirement::Required`] was sent, an error will happen regardless of this value.
    993     pub cred_props: Option<ExtensionReq>,
    994     /// [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension).
    995     pub cred_protect: CredProtect,
    996     /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension).
    997     ///
    998     /// When the value is enforced, that corresponds to
    999     /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension)
   1000     /// in [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) set to a value at least as large
   1001     /// as the contained `FourToSixtyThree`.
   1002     pub min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>,
   1003     /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension).
   1004     ///
   1005     /// When the value is enforced, that corresponds to
   1006     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) set to `true`.
   1007     /// In contrast [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results)
   1008     /// must not exist, be `null`, or be an
   1009     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
   1010     /// such that [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is `null`
   1011     /// and [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) does not
   1012     /// exist or is `null`. This is to ensure the decrypted outputs stay on the client.
   1013     ///
   1014     /// Note some authenticators can only enable `prf` during registration (e.g., CTAP authenticators that only
   1015     /// support
   1016     /// [`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)
   1017     /// and not
   1018     /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension));
   1019     /// thus the value of `PrfInput` is ignored and only used as a signal to enable `prf`. For many such
   1020     /// authenticators, not using this extension during registration will not preclude them from being used during
   1021     /// authentication; however it is still encouraged to use the extension during registration since some
   1022     /// authenticators actually require it.
   1023     ///
   1024     /// When the underlying credential is expected to be used during discoverable requests, it is likely that
   1025     /// `'prf_first` will be `'static` and [`PrfInput::second`] is `None` since one will not be able to
   1026     /// realistically rotate the underlying inputs and further the same input will likely be used for all credentials.
   1027     /// For credentials intended to be used during non-discoverable requests, however, one is encouraged to rotate
   1028     /// the inputs and have unique values for each credential.
   1029     pub prf: Option<(PrfInput<'prf_first, 'prf_second>, ExtensionInfo)>,
   1030 }
   1031 impl<'prf_first, 'prf_second> Extension<'prf_first, 'prf_second> {
   1032     /// Returns an empty `Extension`.
   1033     #[inline]
   1034     #[must_use]
   1035     pub const fn none() -> Self {
   1036         Self {
   1037             cred_props: None,
   1038             cred_protect: CredProtect::None,
   1039             min_pin_length: None,
   1040             prf: None,
   1041         }
   1042     }
   1043     /// Same as [`Self::none`] except [`Self::cred_props`] is `Some` containing `req`.
   1044     #[inline]
   1045     #[must_use]
   1046     pub const fn with_cred_props(req: ExtensionReq) -> Self {
   1047         Self {
   1048             cred_props: Some(req),
   1049             ..Self::none()
   1050         }
   1051     }
   1052     /// Same as [`Self::none`] except [`Self::cred_protect`] is `cred_protect`.
   1053     #[inline]
   1054     #[must_use]
   1055     pub const fn with_cred_protect(cred_protect: CredProtect) -> Self {
   1056         Self {
   1057             cred_protect,
   1058             ..Self::none()
   1059         }
   1060     }
   1061     /// Same as [`Self::none`] except [`Self::min_pin_length`] is `Some` containing `min_len` and `info`.
   1062     #[inline]
   1063     #[must_use]
   1064     pub const fn with_min_pin_length(min_len: FourToSixtyThree, info: ExtensionInfo) -> Self {
   1065         Self {
   1066             min_pin_length: Some((min_len, info)),
   1067             ..Self::none()
   1068         }
   1069     }
   1070     /// Same as [`Self::none`] except [`Self::prf`] is `Some` containing `input` and `info`.
   1071     #[expect(single_use_lifetimes, reason = "false positive")]
   1072     #[inline]
   1073     #[must_use]
   1074     pub const fn with_prf<'a: 'prf_first, 'b: 'prf_second>(
   1075         input: PrfInput<'a, 'b>,
   1076         info: ExtensionInfo,
   1077     ) -> Self {
   1078         Self {
   1079             prf: Some((input, info)),
   1080             ..Self::none()
   1081         }
   1082     }
   1083 }
   1084 impl Default for Extension<'_, '_> {
   1085     /// Same as [`Self::none`].
   1086     #[inline]
   1087     fn default() -> Self {
   1088         Self::none()
   1089     }
   1090 }
   1091 #[cfg(test)]
   1092 impl PartialEq for Extension<'_, '_> {
   1093     #[inline]
   1094     fn eq(&self, other: &Self) -> bool {
   1095         self.cred_props == other.cred_props
   1096             && self.cred_protect == other.cred_protect
   1097             && self.min_pin_length == other.min_pin_length
   1098             && self.prf == other.prf
   1099     }
   1100 }
   1101 /// The maximum number of bytes a [`UserHandle`] can be made of per
   1102 /// [WebAuthn](https://www.w3.org/TR/webauthn-3/#user-handle).
   1103 pub const USER_HANDLE_MAX_LEN: usize = 64;
   1104 /// The minimum number of bytes a [`UserHandle`] can be made of per
   1105 /// [WebAuthn](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id).
   1106 pub const USER_HANDLE_MIN_LEN: usize = 1;
   1107 /// A [user handle](https://www.w3.org/TR/webauthn-3/#user-handle) that is made up of
   1108 /// [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] bytes.
   1109 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
   1110 pub struct UserHandle<const LEN: usize>([u8; LEN]);
   1111 impl<const LEN: usize> UserHandle<LEN> {
   1112     /// Returns the contained data as a `slice`.
   1113     #[inline]
   1114     #[must_use]
   1115     pub const fn as_slice(&self) -> &[u8] {
   1116         self.0.as_slice()
   1117     }
   1118     /// Returns the contained data as a shared reference to the array.
   1119     #[inline]
   1120     #[must_use]
   1121     pub const fn as_array(&self) -> &[u8; LEN] {
   1122         &self.0
   1123     }
   1124     /// Returns the contained data.
   1125     #[inline]
   1126     #[must_use]
   1127     pub const fn into_array(self) -> [u8; LEN] {
   1128         self.0
   1129     }
   1130 }
   1131 /// Implements [`Default`] for [`UserHandle`] of array of length of the passed `usize` literal.
   1132 ///
   1133 /// Only [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively are allowed to be passed.
   1134 macro_rules! user {
   1135     ( $( $x:literal),* ) => {
   1136         $(
   1137 impl Default for UserHandle<$x> {
   1138     #[inline]
   1139     fn default() -> Self {
   1140         let mut data = [0; $x];
   1141         rand::fill(data.as_mut_slice());
   1142         Self(data)
   1143     }
   1144 }
   1145         )*
   1146     };
   1147 }
   1148 // MUST only pass [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively.
   1149 user!(
   1150     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
   1151     27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
   1152     51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64
   1153 );
   1154 impl<const LEN: usize> UserHandle<LEN>
   1155 where
   1156     Self: Default,
   1157 {
   1158     /// Returns a new `UserHandle` based on `LEN` randomly-generated [`u8`]s.
   1159     ///
   1160     /// # Examples
   1161     ///
   1162     /// ```
   1163     /// # use webauthn_rp::request::register::{UserHandle, UserHandle64, USER_HANDLE_MIN_LEN, USER_HANDLE_MAX_LEN};
   1164     /// assert_eq!(
   1165     ///     UserHandle::<USER_HANDLE_MIN_LEN>::new()
   1166     ///         .as_ref()
   1167     ///         .len(),
   1168     ///     1
   1169     /// );
   1170     /// // The probability of an all-zero `UserHandle` being generated (assuming a good entropy
   1171     /// // source) is 2^-512 ≈ 7.5 x 10^-155.
   1172     /// assert_ne!(
   1173     ///     UserHandle64::new().as_ref(),
   1174     ///     [0; USER_HANDLE_MAX_LEN]
   1175     /// );
   1176     /// ```
   1177     #[inline]
   1178     #[must_use]
   1179     pub fn new() -> Self {
   1180         Self::default()
   1181     }
   1182 }
   1183 impl<const LEN: usize> AsRef<[u8]> for UserHandle<LEN> {
   1184     #[inline]
   1185     fn as_ref(&self) -> &[u8] {
   1186         self.as_slice()
   1187     }
   1188 }
   1189 impl<const LEN: usize> Borrow<[u8]> for UserHandle<LEN> {
   1190     #[inline]
   1191     fn borrow(&self) -> &[u8] {
   1192         self.as_slice()
   1193     }
   1194 }
   1195 impl<const LEN: usize> PartialEq<&Self> for UserHandle<LEN> {
   1196     #[inline]
   1197     fn eq(&self, other: &&Self) -> bool {
   1198         *self == **other
   1199     }
   1200 }
   1201 impl<const LEN: usize> PartialEq<UserHandle<LEN>> for &UserHandle<LEN> {
   1202     #[inline]
   1203     fn eq(&self, other: &UserHandle<LEN>) -> bool {
   1204         **self == *other
   1205     }
   1206 }
   1207 impl<const LEN: usize> From<UserHandle<LEN>> for [u8; LEN] {
   1208     #[inline]
   1209     fn from(value: UserHandle<LEN>) -> Self {
   1210         value.into_array()
   1211     }
   1212 }
   1213 impl<'a: 'b, 'b, const LEN: usize> From<&'a UserHandle<LEN>> for &'b [u8; LEN] {
   1214     #[inline]
   1215     fn from(value: &'a UserHandle<LEN>) -> Self {
   1216         value.as_array()
   1217     }
   1218 }
   1219 /// `UserHandle` that is based on the [spec recommendation](https://www.w3.org/TR/webauthn-3/#user-handle).
   1220 pub type UserHandle64 = UserHandle<USER_HANDLE_MAX_LEN>;
   1221 /// `UserHandle` that is based on 16 bytes.
   1222 ///
   1223 /// While not the recommended size like [`UserHandle64`], 16 bytes is common for many deployments since
   1224 /// it's the same size as [Universally Unique IDentifiers (UUIDs)](https://www.rfc-editor.org/rfc/rfc9562).
   1225 pub type UserHandle16 = UserHandle<16>;
   1226 impl UserHandle16 {
   1227     /// Same as [`Self::new`] except 6 bits of metadata is encoded to conform with
   1228     /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4).
   1229     ///
   1230     /// # Examples
   1231     ///
   1232     /// ```
   1233     /// # use webauthn_rp::request::register::UserHandle16;
   1234     /// assert!(UserHandle16::new_uuid_v4().is_uuid_v4());
   1235     /// ```
   1236     #[inline]
   1237     #[must_use]
   1238     pub fn new_uuid_v4() -> Self {
   1239         let mut this = Self::new();
   1240         // The first 4 bits of the 6th octet (0-based index) represents the UUID version (i.e., 4).
   1241         // We first 0-out the version bits retaining the other 4 bits, then set the version bits to 4.
   1242         this.0[6] = (this.0[6] & 0x0F) | 0x40;
   1243         // The first 2 bits of the 8th octet (0-based index) represents the UUID variant (i.e., 8,9,A,B)
   1244         // which is defined to be 2.
   1245         // We first 0-out the variant bits retaining the other 6 bits, then set the variant bits to 2.
   1246         this.0[8] = (this.0[8] & 0x3F) | 0x80;
   1247         this
   1248     }
   1249     /// Returns `true` iff `self` is a valid
   1250     /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4).
   1251     ///
   1252     /// # Examples
   1253     ///
   1254     /// ```
   1255     /// # use webauthn_rp::request::register::UserHandle16;
   1256     /// assert!(UserHandle16::new_uuid_v4().is_uuid_v4());
   1257     /// let mut user = UserHandle16::new_uuid_v4().into_array();
   1258     /// user[6] = 255;
   1259     /// # #[cfg(feature = "custom")]
   1260     /// assert!(!UserHandle16::from(user).is_uuid_v4());
   1261     /// ```
   1262     #[inline]
   1263     #[must_use]
   1264     pub const fn is_uuid_v4(&self) -> bool {
   1265         // The first 4 bits of the 6th octet (0-based index) represents the UUID version (i.e., 4).
   1266         // The first 2 bits of the 8th octet (0-based index) represents the UUID variant (i.e., 8,9,A,B) which
   1267         // is defined to be 2.
   1268         self.0[6] >> 4 == 0x4 && self.0[8] >> 6 == 0x2
   1269     }
   1270     /// Returns `Some` containing `uuid_v4` iff `uuid_v4` is a valid
   1271     /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4).
   1272     ///
   1273     /// # Examples
   1274     ///
   1275     /// ```
   1276     /// # use webauthn_rp::request::register::UserHandle16;
   1277     /// let mut user = UserHandle16::new_uuid_v4().into_array();
   1278     /// assert!(UserHandle16::from_uuid_v4(user).is_some());
   1279     /// user[8] = 255;
   1280     /// assert!(UserHandle16::from_uuid_v4(user).is_none());
   1281     /// ```
   1282     #[cfg(feature = "custom")]
   1283     #[inline]
   1284     #[must_use]
   1285     pub fn from_uuid_v4(uuid_v4: [u8; 16]) -> Option<Self> {
   1286         let this = Self(uuid_v4);
   1287         this.is_uuid_v4().then_some(this)
   1288     }
   1289 }
   1290 /// [The `PublicKeyCredentialUserEntity`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentity)
   1291 /// sent to the client.
   1292 #[derive(Clone, Debug)]
   1293 pub struct PublicKeyCredentialUserEntity<'name, 'display_name, 'id, const LEN: usize> {
   1294     /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name).
   1295     pub name: Username<'name>,
   1296     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id).
   1297     pub id: &'id UserHandle<LEN>,
   1298     /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname).
   1299     pub display_name: DisplayName<'display_name>,
   1300 }
   1301 /// `PublicKeyCredentialUserEntity` based on a [`UserHandle64`].
   1302 pub type PublicKeyCredentialUserEntity64<'name, 'display_name, 'id> =
   1303     PublicKeyCredentialUserEntity<'name, 'display_name, 'id, USER_HANDLE_MAX_LEN>;
   1304 /// `PublicKeyCredentialUserEntity` based on a [`UserHandle16`].
   1305 pub type PublicKeyCredentialUserEntity16<'name, 'display_name, 'id> =
   1306     PublicKeyCredentialUserEntity<'name, 'display_name, 'id, 16>;
   1307 /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement) sent to the client.
   1308 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1309 pub enum ResidentKeyRequirement {
   1310     /// [`required`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-required).
   1311     Required,
   1312     /// [`discouraged`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-discouraged).
   1313     Discouraged,
   1314     /// [`preferred`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-preferred).
   1315     Preferred,
   1316 }
   1317 /// [`PublicKeyCredentialHints`](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint)
   1318 /// for [`AuthenticatorAttachment::CrossPlatform`] authenticators.
   1319 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1320 pub enum CrossPlatformHint {
   1321     /// No hints.
   1322     #[default]
   1323     None,
   1324     /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-security-key).
   1325     SecurityKey,
   1326     /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-hybrid).
   1327     Hybrid,
   1328     /// [`Self::SecurityKey`] and [`Self::Hybrid`].
   1329     SecurityKeyHybrid,
   1330     /// [`Self::Hybrid`] and [`Self::SecurityKey`].
   1331     HybridSecurityKey,
   1332 }
   1333 impl From<CrossPlatformHint> for Hint {
   1334     #[inline]
   1335     fn from(value: CrossPlatformHint) -> Self {
   1336         match value {
   1337             CrossPlatformHint::None => Self::None,
   1338             CrossPlatformHint::SecurityKey => Self::SecurityKey,
   1339             CrossPlatformHint::Hybrid => Self::Hybrid,
   1340             CrossPlatformHint::SecurityKeyHybrid => Self::SecurityKeyHybrid,
   1341             CrossPlatformHint::HybridSecurityKey => Self::HybridSecurityKey,
   1342         }
   1343     }
   1344 }
   1345 /// [`PublicKeyCredentialHints`](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint)
   1346 /// for [`AuthenticatorAttachment::Platform`] authenticators.
   1347 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1348 pub enum PlatformHint {
   1349     /// No hints.
   1350     #[default]
   1351     None,
   1352     /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-client-device).
   1353     ClientDevice,
   1354 }
   1355 impl From<PlatformHint> for Hint {
   1356     #[inline]
   1357     fn from(value: PlatformHint) -> Self {
   1358         match value {
   1359             PlatformHint::None => Self::None,
   1360             PlatformHint::ClientDevice => Self::ClientDevice,
   1361         }
   1362     }
   1363 }
   1364 /// [`AuthenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatorattachment)
   1365 /// requirement with associated hints for further refinement.
   1366 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1367 pub enum AuthenticatorAttachmentReq {
   1368     /// No attachment information (i.e., any [`AuthenticatorAttachment`]).
   1369     None(Hint),
   1370     /// [`platform`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-platform) is required
   1371     /// to be used.
   1372     Platform(PlatformHint),
   1373     /// [`cross-platform`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-cross-platform) is
   1374     /// required to be used.
   1375     CrossPlatform(CrossPlatformHint),
   1376 }
   1377 impl Default for AuthenticatorAttachmentReq {
   1378     #[inline]
   1379     fn default() -> Self {
   1380         Self::None(Hint::default())
   1381     }
   1382 }
   1383 impl AuthenticatorAttachmentReq {
   1384     /// Validates `self` against `other` ignoring [`Self::immutable_attachment`].
   1385     const fn validate(
   1386         self,
   1387         require_response: bool,
   1388         other: AuthenticatorAttachment,
   1389     ) -> Result<(), RegCeremonyErr> {
   1390         match self {
   1391             Self::None(_) => {
   1392                 if require_response && matches!(other, AuthenticatorAttachment::None) {
   1393                     Err(RegCeremonyErr::MissingAuthenticatorAttachment)
   1394                 } else {
   1395                     Ok(())
   1396                 }
   1397             }
   1398             Self::Platform(_) => match other {
   1399                 AuthenticatorAttachment::None => {
   1400                     if require_response {
   1401                         Err(RegCeremonyErr::MissingAuthenticatorAttachment)
   1402                     } else {
   1403                         Ok(())
   1404                     }
   1405                 }
   1406                 AuthenticatorAttachment::Platform => Ok(()),
   1407                 AuthenticatorAttachment::CrossPlatform => {
   1408                     Err(RegCeremonyErr::AuthenticatorAttachmentMismatch)
   1409                 }
   1410             },
   1411             Self::CrossPlatform(_) => match other {
   1412                 AuthenticatorAttachment::None => {
   1413                     if require_response {
   1414                         Err(RegCeremonyErr::MissingAuthenticatorAttachment)
   1415                     } else {
   1416                         Ok(())
   1417                     }
   1418                 }
   1419                 AuthenticatorAttachment::CrossPlatform => Ok(()),
   1420                 AuthenticatorAttachment::Platform => {
   1421                     Err(RegCeremonyErr::AuthenticatorAttachmentMismatch)
   1422                 }
   1423             },
   1424         }
   1425     }
   1426 }
   1427 /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictionary-authenticatorSelection).
   1428 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1429 pub struct AuthenticatorSelectionCriteria {
   1430     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-authenticatorattachment).
   1431     pub authenticator_attachment: AuthenticatorAttachmentReq,
   1432     /// [`residentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey).
   1433     pub resident_key: ResidentKeyRequirement,
   1434     /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-userverification).
   1435     pub user_verification: UserVerificationRequirement,
   1436 }
   1437 impl AuthenticatorSelectionCriteria {
   1438     /// Returns an `AuthenticatorSelectionCriteria` useful for passkeys (i.e., [`Self::resident_key`] is set to
   1439     /// [`ResidentKeyRequirement::Required`] and [`Self::user_verification`] is set to
   1440     /// [`UserVerificationRequirement::Required`]).
   1441     ///
   1442     /// # Examples
   1443     ///
   1444     /// ```
   1445     /// # use webauthn_rp::request::{
   1446     /// #     register::{
   1447     /// #         AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria, ResidentKeyRequirement,
   1448     /// #     },
   1449     /// #     Hint, UserVerificationRequirement,
   1450     /// # };
   1451     /// let crit = AuthenticatorSelectionCriteria::passkey();
   1452     /// assert!(
   1453     ///     matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   1454     /// );
   1455     /// assert!(matches!(
   1456     ///     crit.resident_key,
   1457     ///     ResidentKeyRequirement::Required
   1458     /// ));
   1459     /// assert!(matches!(
   1460     ///     crit.user_verification,
   1461     ///     UserVerificationRequirement::Required
   1462     /// ));
   1463     /// ```
   1464     #[inline]
   1465     #[must_use]
   1466     pub fn passkey() -> Self {
   1467         Self {
   1468             authenticator_attachment: AuthenticatorAttachmentReq::default(),
   1469             resident_key: ResidentKeyRequirement::Required,
   1470             user_verification: UserVerificationRequirement::Required,
   1471         }
   1472     }
   1473     /// Returns an `AuthenticatorSelectionCriteria` useful for second-factor flows (i.e., [`Self::resident_key`]
   1474     /// is set to [`ResidentKeyRequirement::Discouraged`] and [`Self::user_verification`] is set to
   1475     /// [`UserVerificationRequirement::Discouraged`]).
   1476     ///
   1477     /// Note some authenticators require user verification during credential registration (e.g.,
   1478     /// [CTAP 2.0 authenticators](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorMakeCredential)).
   1479     /// When an authenticator supports both CTAP 2.0 and
   1480     /// [Universal 2nd Factor (U2F)](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html#registration-creating-a-key-pair)
   1481     /// protocols, user agents will sometimes fall back to U2F when `UserVerificationRequirement::Discouraged`
   1482     /// is requested since the latter allows for registration without user verification. If the user agent does not
   1483     /// do this, then users will have an inconsistent experience when authenticating an already-registered
   1484     /// credential. If this is undesirable, one can use [`UserVerificationRequirement::Required`] for this and
   1485     /// [`PublicKeyCredentialRequestOptions::user_verification`] at the expense of requiring a user to verify
   1486     /// themselves twice: once for the first factor and again here.
   1487     ///
   1488     /// # Examples
   1489     ///
   1490     /// ```
   1491     /// # use webauthn_rp::request::{
   1492     /// #     register::{
   1493     /// #         AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria, ResidentKeyRequirement,
   1494     /// #     },
   1495     /// #     Hint, UserVerificationRequirement,
   1496     /// # };
   1497     /// let crit = AuthenticatorSelectionCriteria::second_factor();
   1498     /// assert!(
   1499     ///     matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   1500     /// );
   1501     /// assert!(matches!(
   1502     ///     crit.resident_key,
   1503     ///     ResidentKeyRequirement::Discouraged
   1504     /// ));
   1505     /// assert!(matches!(
   1506     ///     crit.user_verification,
   1507     ///     UserVerificationRequirement::Discouraged
   1508     /// ));
   1509     /// ```
   1510     #[inline]
   1511     #[must_use]
   1512     pub fn second_factor() -> Self {
   1513         Self {
   1514             authenticator_attachment: AuthenticatorAttachmentReq::default(),
   1515             resident_key: ResidentKeyRequirement::Discouraged,
   1516             user_verification: UserVerificationRequirement::Discouraged,
   1517         }
   1518     }
   1519     /// Ensures a client-side credential was created when applicable. Also enforces `auth_attachment` when
   1520     /// applicable.
   1521     const fn validate(
   1522         self,
   1523         require_auth_attachment: bool,
   1524         auth_attachment: AuthenticatorAttachment,
   1525     ) -> Result<(), RegCeremonyErr> {
   1526         self.authenticator_attachment
   1527             .validate(require_auth_attachment, auth_attachment)
   1528     }
   1529 }
   1530 /// Helper that verifies the overlap of [`CredentialCreationOptions::start_ceremony`] and
   1531 /// [`RegistrationServerState::decode`].
   1532 const fn validate_options_helper(
   1533     auth_crit: AuthenticatorSelectionCriteria,
   1534     extensions: ServerExtensionInfo,
   1535 ) -> Result<(), CreationOptionsErr> {
   1536     if matches!(
   1537         auth_crit.user_verification,
   1538         UserVerificationRequirement::Required
   1539     ) {
   1540         Ok(())
   1541     } else if matches!(
   1542         extensions.cred_protect,
   1543         CredProtect::UserVerificationRequired(_, _)
   1544     ) {
   1545         Err(CreationOptionsErr::CredProtectRequiredWithoutUserVerification)
   1546     } else {
   1547         Ok(())
   1548     }
   1549 }
   1550 /// The [`CredentialCreationOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialcreationoptions)
   1551 /// to send to the client when registering a new credential.
   1552 ///
   1553 /// Upon saving the [`RegistrationServerState`] returned from [`Self::start_ceremony`], one MUST send
   1554 /// [`RegistrationClientState`] to the client ASAP. After receiving the newly created [`Registration`], it is
   1555 /// validated using [`RegistrationServerState::verify`].
   1556 #[derive(Debug)]
   1557 pub struct CredentialCreationOptions<
   1558     'rp_id,
   1559     'user_name,
   1560     'user_display_name,
   1561     'user_id,
   1562     'prf_first,
   1563     'prf_second,
   1564     const USER_LEN: usize,
   1565 > {
   1566     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-mediation).
   1567     ///
   1568     /// Note if this is [`CredentialMediationRequirement::Conditional`], one may want to ensure
   1569     /// [`AuthenticatorSelectionCriteria::user_verification`] is not [`UserVerificationRequirement::Required`]
   1570     /// since some authenticators cannot enforce user verification during registration ceremonies when conditional
   1571     /// mediation is used. Do note that in the event passkeys are to be created, one may want to set
   1572     /// [`AuthenticationVerificationOptions::update_uv`] to `true` since [`Flag::user_verified`] will
   1573     /// potentially be `false`.
   1574     pub mediation: CredentialMediationRequirement,
   1575     /// `public-key` [credential type](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry).
   1576     pub public_key: PublicKeyCredentialCreationOptions<
   1577         'rp_id,
   1578         'user_name,
   1579         'user_display_name,
   1580         'user_id,
   1581         'prf_first,
   1582         'prf_second,
   1583         USER_LEN,
   1584     >,
   1585 }
   1586 impl<
   1587     'rp_id,
   1588     'user_name,
   1589     'user_display_name,
   1590     'user_id,
   1591     'prf_first,
   1592     'prf_second,
   1593     const USER_LEN: usize,
   1594 >
   1595     CredentialCreationOptions<
   1596         'rp_id,
   1597         'user_name,
   1598         'user_display_name,
   1599         'user_id,
   1600         'prf_first,
   1601         'prf_second,
   1602         USER_LEN,
   1603     >
   1604 {
   1605     /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and
   1606     /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::passkey`].
   1607     #[expect(single_use_lifetimes, reason = "false positive")]
   1608     #[inline]
   1609     #[must_use]
   1610     pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>(
   1611         rp_id: &'a RpId,
   1612         user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>,
   1613         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   1614     ) -> Self {
   1615         Self {
   1616             mediation: CredentialMediationRequirement::default(),
   1617             public_key: PublicKeyCredentialCreationOptions::passkey(
   1618                 rp_id,
   1619                 user,
   1620                 exclude_credentials,
   1621             ),
   1622         }
   1623     }
   1624     /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and
   1625     /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::second_factor`].
   1626     #[expect(single_use_lifetimes, reason = "false positive")]
   1627     #[inline]
   1628     #[must_use]
   1629     pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>(
   1630         rp_id: &'a RpId,
   1631         user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>,
   1632         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   1633     ) -> Self {
   1634         let mut opts = Self::passkey(rp_id, user, exclude_credentials);
   1635         opts.public_key.authenticator_selection = AuthenticatorSelectionCriteria::second_factor();
   1636         opts.public_key.extensions.cred_props = Some(ExtensionReq::Allow);
   1637         opts.public_key.extensions.cred_protect =
   1638             CredProtect::UserVerificationOptionalWithCredentialIdList(
   1639                 false,
   1640                 ExtensionInfo::AllowEnforceValue,
   1641             );
   1642         opts
   1643     }
   1644     /// Begins the [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony) consuming
   1645     /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `RegistrationClientState` MUST be
   1646     /// sent ASAP. In order to complete registration, the returned `RegistrationServerState` MUST be saved so that
   1647     /// it can later be used to verify the new credential with [`RegistrationServerState::verify`].
   1648     ///
   1649     /// # Errors
   1650     ///
   1651     /// Errors iff `self` contains incompatible configuration.
   1652     ///
   1653     /// # Examples
   1654     ///
   1655     /// ```
   1656     /// # #[cfg(not(feature = "serializable_server_state"))]
   1657     /// # use std::time::Instant;
   1658     /// # #[cfg(not(feature = "serializable_server_state"))]
   1659     /// # use webauthn_rp::request::TimedCeremony as _;
   1660     /// # use webauthn_rp::request::{
   1661     /// #     register::{CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64},
   1662     /// #     AsciiDomain, RpId
   1663     /// # };
   1664     /// # #[cfg(not(feature = "serializable_server_state"))]
   1665     /// assert!(
   1666     ///     CredentialCreationOptions::passkey(
   1667     ///         &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
   1668     ///         PublicKeyCredentialUserEntity {
   1669     ///             name: "bernard.riemann".try_into()?,
   1670     ///             id: &UserHandle64::new(),
   1671     ///             display_name: "Georg Friedrich Bernhard Riemann".try_into()?,
   1672     ///         },
   1673     ///         Vec::new()
   1674     ///     ).start_ceremony()?.0.expiration() > Instant::now()
   1675     /// );
   1676     /// # Ok::<_, webauthn_rp::AggErr>(())
   1677     /// ```
   1678     #[inline]
   1679     pub fn start_ceremony(
   1680         mut self,
   1681     ) -> Result<
   1682         (
   1683             RegistrationServerState<USER_LEN>,
   1684             RegistrationClientState<
   1685                 'rp_id,
   1686                 'user_name,
   1687                 'user_display_name,
   1688                 'user_id,
   1689                 'prf_first,
   1690                 'prf_second,
   1691                 USER_LEN,
   1692             >,
   1693         ),
   1694         CreationOptionsErr,
   1695     > {
   1696         let extensions = self.public_key.extensions.into();
   1697         validate_options_helper(self.public_key.authenticator_selection, extensions).and_then(
   1698             |()| {
   1699                 #[cfg(not(feature = "serializable_server_state"))]
   1700                 let now = Instant::now();
   1701                 #[cfg(feature = "serializable_server_state")]
   1702                 let now = SystemTime::now();
   1703                 now.checked_add(Duration::from_millis(
   1704                     NonZeroU64::from(self.public_key.timeout).get(),
   1705                 ))
   1706                 .ok_or(CreationOptionsErr::InvalidTimeout)
   1707                 .map(|expiration| {
   1708                     // We remove duplicates. The order has no significance, so this is OK.
   1709                     self.public_key
   1710                         .exclude_credentials
   1711                         .sort_unstable_by(|a, b| a.id.as_ref().cmp(b.id.as_ref()));
   1712                     self.public_key
   1713                         .exclude_credentials
   1714                         .dedup_by(|a, b| a.id.as_ref() == b.id.as_ref());
   1715                     (
   1716                         RegistrationServerState {
   1717                             mediation: self.mediation,
   1718                             challenge: SentChallenge(self.public_key.challenge.0),
   1719                             pub_key_cred_params: self.public_key.pub_key_cred_params,
   1720                             authenticator_selection: self.public_key.authenticator_selection,
   1721                             extensions,
   1722                             expiration,
   1723                             user_id: *self.public_key.user.id,
   1724                         },
   1725                         RegistrationClientState(self),
   1726                     )
   1727                 })
   1728             },
   1729         )
   1730     }
   1731 }
   1732 /// `CredentialCreationOptions` based on a [`UserHandle64`].
   1733 pub type CredentialCreationOptions64<
   1734     'rp_id,
   1735     'user_name,
   1736     'user_display_name,
   1737     'user_id,
   1738     'prf_first,
   1739     'prf_second,
   1740 > = CredentialCreationOptions<
   1741     'rp_id,
   1742     'user_name,
   1743     'user_display_name,
   1744     'user_id,
   1745     'prf_first,
   1746     'prf_second,
   1747     USER_HANDLE_MAX_LEN,
   1748 >;
   1749 /// `CredentialCreationOptions` based on a [`UserHandle16`].
   1750 pub type CredentialCreationOptions16<
   1751     'rp_id,
   1752     'user_name,
   1753     'user_display_name,
   1754     'user_id,
   1755     'prf_first,
   1756     'prf_second,
   1757 > = CredentialCreationOptions<
   1758     'rp_id,
   1759     'user_name,
   1760     'user_display_name,
   1761     'user_id,
   1762     'prf_first,
   1763     'prf_second,
   1764     16,
   1765 >;
   1766 /// The [`PublicKeyCredentialCreationOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptions)
   1767 /// to send to the client when registering a new credential.
   1768 #[derive(Debug)]
   1769 pub struct PublicKeyCredentialCreationOptions<
   1770     'rp_id,
   1771     'user_name,
   1772     'user_display_name,
   1773     'user_id,
   1774     'prf_first,
   1775     'prf_second,
   1776     const USER_LEN: usize,
   1777 > {
   1778     /// [`rp`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-rp).
   1779     pub rp_id: &'rp_id RpId,
   1780     /// [`user`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-user).
   1781     pub user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>,
   1782     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-challenge).
   1783     pub challenge: Challenge,
   1784     /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams).
   1785     pub pub_key_cred_params: CoseAlgorithmIdentifiers,
   1786     /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-timeout).
   1787     ///
   1788     /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives
   1789     /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled
   1790     /// when attesting credentials as no timeout would make out-of-memory (OOM) conditions more likely.
   1791     pub timeout: NonZeroU32,
   1792     /// [`excludeCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-excludecredentials).
   1793     pub exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   1794     /// [`authenticatorSelection`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-authenticatorselection).
   1795     pub authenticator_selection: AuthenticatorSelectionCriteria,
   1796     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-extensions).
   1797     pub extensions: Extension<'prf_first, 'prf_second>,
   1798 }
   1799 impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize>
   1800     PublicKeyCredentialCreationOptions<
   1801         'rp_id,
   1802         'user_name,
   1803         'user_display_name,
   1804         'user_id,
   1805         '_,
   1806         '_,
   1807         USER_LEN,
   1808     >
   1809 {
   1810     /// Most deployments of passkeys should use this function. Specifically deployments that are both userless and
   1811     /// passwordless and desire multi-factor authentication (MFA) to be done entirely on the authenticator. It
   1812     /// is important `exclude_credentials` contains the information for _all_ [`RegisteredCredential`]s registered to
   1813     /// [`PublicKeyCredentialUserEntity::id`] to avoid accidentally overwriting existing credentials that
   1814     /// have been previously registered.
   1815     ///
   1816     /// Creates a `PublicKeyCredentialCreationOptions` that requires the authenticator to create a client-side
   1817     /// discoverable credential enforcing any form of user verification. [`Self::timeout`] is [`FIVE_MINUTES`].
   1818     /// [`Extension::cred_protect`] with [`CredProtect::UserVerificationRequired`] with `false` and
   1819     /// [`ExtensionInfo::AllowEnforceValue`] is used.
   1820     ///
   1821     /// # Examples
   1822     ///
   1823     /// ```
   1824     /// # use webauthn_rp::request::{
   1825     /// #     register::{
   1826     /// #         PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64
   1827     /// #     },
   1828     /// #     AsciiDomain, RpId, UserVerificationRequirement
   1829     /// # };
   1830     /// assert!(matches!(
   1831     ///     PublicKeyCredentialCreationOptions::passkey(
   1832     ///         &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
   1833     ///         PublicKeyCredentialUserEntity {
   1834     ///             name: "archimedes.of.syracuse".try_into()?,
   1835     ///             id: &UserHandle64::new(),
   1836     ///             display_name: "Αρχιμήδης ο Συρακούσιος".try_into()?,
   1837     ///         },
   1838     ///         Vec::new()
   1839     ///     )
   1840     ///     .authenticator_selection.user_verification, UserVerificationRequirement::Required
   1841     /// ));
   1842     /// # Ok::<_, webauthn_rp::AggErr>(())
   1843     /// ```
   1844     #[expect(single_use_lifetimes, reason = "false positive")]
   1845     #[inline]
   1846     #[must_use]
   1847     pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>(
   1848         rp_id: &'a RpId,
   1849         user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>,
   1850         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   1851     ) -> Self {
   1852         Self {
   1853             rp_id,
   1854             user,
   1855             challenge: Challenge::new(),
   1856             pub_key_cred_params: CoseAlgorithmIdentifiers::default(),
   1857             timeout: FIVE_MINUTES,
   1858             exclude_credentials,
   1859             authenticator_selection: AuthenticatorSelectionCriteria::passkey(),
   1860             extensions: Extension {
   1861                 cred_props: None,
   1862                 cred_protect: CredProtect::UserVerificationRequired(
   1863                     false,
   1864                     ExtensionInfo::AllowEnforceValue,
   1865                 ),
   1866                 min_pin_length: None,
   1867                 prf: None,
   1868             },
   1869         }
   1870     }
   1871     /// Deployments that want to incorporate a "something a user has" factor into a larger multi-factor
   1872     /// authentication (MFA) setup. Specifically deployments that are _not_ userless or passwordless. It
   1873     /// is important `exclude_credentials` contains the information for _all_ [`RegisteredCredential`]s registered
   1874     /// to [`PublicKeyCredentialUserEntity::id`] to avoid accidentally overwriting existing credentials that
   1875     /// have been previously registered.
   1876     ///
   1877     /// Creates a `PublicKeyCredentialCreationOptions` that prefers the authenticator to create a server-side
   1878     /// credential without requiring user verification. [`Self::timeout`] is [`FIVE_MINUTES`].
   1879     /// [`Extension::cred_props`] is [`ExtensionReq::Allow`]. [`Extension::cred_protect`] is
   1880     /// [`CredProtect::UserVerificationOptionalWithCredentialIdList`] with `false` and
   1881     /// [`ExtensionInfo::AllowEnforceValue`].
   1882     ///
   1883     /// Note some authenticators require user verification during credential registration (e.g.,
   1884     /// [CTAP 2.0 authenticators](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorMakeCredential)).
   1885     /// When an authenticator supports both CTAP 2.0 and
   1886     /// [Universal 2nd Factor (U2F)](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html#registration-creating-a-key-pair)
   1887     /// protocols, user agents will sometimes fall back to U2F when [`UserVerificationRequirement::Discouraged`]
   1888     /// is requested since the latter allows for registration without user verification. If the user agent does not
   1889     /// do this, then users will have an inconsistent experience when authenticating an already-registered
   1890     /// credential. If this is undesirable, one can use [`UserVerificationRequirement::Required`] for this and
   1891     /// [`PublicKeyCredentialRequestOptions::user_verification`] at the expense of requiring a user to verify
   1892     /// themselves twice: once for the first factor and again here.
   1893     ///
   1894     /// # Examples
   1895     ///
   1896     /// ```
   1897     /// # use webauthn_rp::request::{register::{
   1898     /// #     PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64
   1899     /// # }, AsciiDomain, RpId};
   1900     /// assert_eq!(
   1901     ///     PublicKeyCredentialCreationOptions::second_factor(
   1902     ///         &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
   1903     ///         PublicKeyCredentialUserEntity {
   1904     ///             name: "carl.gauss".try_into()?,
   1905     ///             id: &UserHandle64::new(),
   1906     ///             display_name: "Johann Carl Friedrich Gauß".try_into()?,
   1907     ///         },
   1908     ///         Vec::new()
   1909     ///     )
   1910     ///     .timeout
   1911     ///     .get(),
   1912     ///     300_000
   1913     /// );
   1914     /// # Ok::<_, webauthn_rp::AggErr>(())
   1915     /// ```
   1916     #[expect(single_use_lifetimes, reason = "false positive")]
   1917     #[inline]
   1918     #[must_use]
   1919     pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>(
   1920         rp_id: &'a RpId,
   1921         user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>,
   1922         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   1923     ) -> Self {
   1924         let mut opts = Self::passkey(rp_id, user, exclude_credentials);
   1925         opts.authenticator_selection = AuthenticatorSelectionCriteria::second_factor();
   1926         opts.extensions.cred_props = Some(ExtensionReq::Allow);
   1927         opts.extensions.cred_protect = CredProtect::UserVerificationOptionalWithCredentialIdList(
   1928             false,
   1929             ExtensionInfo::AllowEnforceValue,
   1930         );
   1931         opts
   1932     }
   1933 }
   1934 /// `PublicKeyCredentialCreationOptions` based on a [`UserHandle64`].
   1935 pub type PublicKeyCredentialCreationOptions64<
   1936     'rp_id,
   1937     'user_name,
   1938     'user_display_name,
   1939     'user_id,
   1940     'prf_first,
   1941     'prf_second,
   1942 > = PublicKeyCredentialCreationOptions<
   1943     'rp_id,
   1944     'user_name,
   1945     'user_display_name,
   1946     'user_id,
   1947     'prf_first,
   1948     'prf_second,
   1949     USER_HANDLE_MAX_LEN,
   1950 >;
   1951 /// `PublicKeyCredentialCreationOptions` based on a [`UserHandle16`].
   1952 pub type PublicKeyCredentialCreationOptions16<
   1953     'rp_id,
   1954     'user_name,
   1955     'user_display_name,
   1956     'user_id,
   1957     'prf_first,
   1958     'prf_second,
   1959 > = PublicKeyCredentialCreationOptions<
   1960     'rp_id,
   1961     'user_name,
   1962     'user_display_name,
   1963     'user_id,
   1964     'prf_first,
   1965     'prf_second,
   1966     16,
   1967 >;
   1968 /// Container of a [`CredentialCreationOptions`] that has been used to start the registration ceremony.
   1969 /// This gets sent to the client ASAP.
   1970 #[derive(Debug)]
   1971 pub struct RegistrationClientState<
   1972     'rp_id,
   1973     'user_name,
   1974     'user_display_name,
   1975     'user_id,
   1976     'prf_first,
   1977     'prf_second,
   1978     const USER_LEN: usize,
   1979 >(
   1980     CredentialCreationOptions<
   1981         'rp_id,
   1982         'user_name,
   1983         'user_display_name,
   1984         'user_id,
   1985         'prf_first,
   1986         'prf_second,
   1987         USER_LEN,
   1988     >,
   1989 );
   1990 impl<
   1991     'rp_id,
   1992     'user_name,
   1993     'user_display_name,
   1994     'user_id,
   1995     'prf_first,
   1996     'prf_second,
   1997     const USER_LEN: usize,
   1998 >
   1999     RegistrationClientState<
   2000         'rp_id,
   2001         'user_name,
   2002         'user_display_name,
   2003         'user_id,
   2004         'prf_first,
   2005         'prf_second,
   2006         USER_LEN,
   2007     >
   2008 {
   2009     /// Returns the `CredentialCreationOptions` that was used to start a registration ceremony.
   2010     ///
   2011     /// # Examples
   2012     ///
   2013     /// ```
   2014     /// # use webauthn_rp::request::{register::{
   2015     /// #     CoseAlgorithmIdentifiers, CredentialCreationOptions,
   2016     /// #     PublicKeyCredentialUserEntity, UserHandle64,
   2017     /// # }, AsciiDomain, RpId};
   2018     /// assert_eq!(
   2019     ///     CredentialCreationOptions::passkey(
   2020     ///         &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
   2021     ///         PublicKeyCredentialUserEntity {
   2022     ///             name: "david.hilbert".try_into()?,
   2023     ///             id: &UserHandle64::new(),
   2024     ///             display_name: "David Hilbert".try_into()?,
   2025     ///         },
   2026     ///         Vec::new()
   2027     ///     )
   2028     ///     .start_ceremony()?
   2029     ///     .1
   2030     ///     .options()
   2031     ///     .public_key
   2032     ///     .rp_id.as_ref(),
   2033     ///     "example.com"
   2034     /// );
   2035     /// # Ok::<_, webauthn_rp::AggErr>(())
   2036     /// ```
   2037     #[inline]
   2038     #[must_use]
   2039     pub const fn options(
   2040         &self,
   2041     ) -> &CredentialCreationOptions<
   2042         'rp_id,
   2043         'user_name,
   2044         'user_display_name,
   2045         'user_id,
   2046         'prf_first,
   2047         'prf_second,
   2048         USER_LEN,
   2049     > {
   2050         &self.0
   2051     }
   2052 }
   2053 /// `RegistrationClientState` based on a [`UserHandle64`].
   2054 pub type RegistrationClientState64<
   2055     'rp_id,
   2056     'user_name,
   2057     'user_display_name,
   2058     'user_id,
   2059     'prf_first,
   2060     'prf_second,
   2061 > = RegistrationClientState<
   2062     'rp_id,
   2063     'user_name,
   2064     'user_display_name,
   2065     'user_id,
   2066     'prf_first,
   2067     'prf_second,
   2068     USER_HANDLE_MAX_LEN,
   2069 >;
   2070 /// `RegistrationClientState` based on a [`UserHandle16`].
   2071 pub type RegistrationClientState16<
   2072     'rp_id,
   2073     'user_name,
   2074     'user_display_name,
   2075     'user_id,
   2076     'prf_first,
   2077     'prf_second,
   2078 > = RegistrationClientState<
   2079     'rp_id,
   2080     'user_name,
   2081     'user_display_name,
   2082     'user_id,
   2083     'prf_first,
   2084     'prf_second,
   2085     16,
   2086 >;
   2087 /// Additional verification options to perform in [`RegistrationServerState::verify`].
   2088 #[derive(Clone, Copy, Debug)]
   2089 pub struct RegistrationVerificationOptions<'origins, 'top_origins, O, T> {
   2090     /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   2091     ///
   2092     /// When this is empty, the origin that will be used will be based on
   2093     /// the [`RpId`] passed to [`RegistrationServerState::verify`]. If [`RpId::Domain`] or [`RpId::StaticDomain`],
   2094     /// then the [`DomainOrigin`] returned from passing [`AsciiDomain::as_ref`] and [`AsciiDomainStatic::as_str`]
   2095     /// to [`DomainOrigin::new`] respectively will be used; otherwise the [`Url`] in [`RpId::Url`] will be used.
   2096     pub allowed_origins: &'origins [O],
   2097     /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin)
   2098     /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   2099     ///
   2100     /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained
   2101     /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`,
   2102     /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`.
   2103     pub allowed_top_origins: Option<&'top_origins [T]>,
   2104     /// The required [`Backup`] state of the credential.
   2105     pub backup_requirement: BackupReq,
   2106     /// Error when unsolicited extensions are sent back iff `true`.
   2107     pub error_on_unsolicited_extensions: bool,
   2108     /// [`AuthenticatorAttachment`] must be sent iff `true`.
   2109     pub require_authenticator_attachment: bool,
   2110     /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`.
   2111     #[cfg(feature = "serde_relaxed")]
   2112     pub client_data_json_relaxed: bool,
   2113 }
   2114 impl<O, T> RegistrationVerificationOptions<'_, '_, O, T> {
   2115     /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`,
   2116     /// [`Self::backup_requirement`] is [`BackupReq::None`], [`Self::error_on_unsolicited_extensions`] is `true`,
   2117     /// [`Self::require_authenticator_attachment`] is `false`, and [`Self::client_data_json_relaxed`] is
   2118     /// `true`.
   2119     ///
   2120     /// Note `O` and `T` should implement `PartialEq<Origin<'_>>` (e.g., `&str`).
   2121     #[inline]
   2122     #[must_use]
   2123     pub const fn new() -> Self {
   2124         Self {
   2125             allowed_origins: [].as_slice(),
   2126             allowed_top_origins: None,
   2127             backup_requirement: BackupReq::None,
   2128             error_on_unsolicited_extensions: true,
   2129             require_authenticator_attachment: false,
   2130             #[cfg(feature = "serde_relaxed")]
   2131             client_data_json_relaxed: true,
   2132         }
   2133     }
   2134 }
   2135 impl<O, T> Default for RegistrationVerificationOptions<'_, '_, O, T> {
   2136     /// Same as [`Self::new`].
   2137     #[inline]
   2138     fn default() -> Self {
   2139         Self {
   2140             allowed_origins: &[],
   2141             allowed_top_origins: None,
   2142             backup_requirement: BackupReq::default(),
   2143             error_on_unsolicited_extensions: true,
   2144             require_authenticator_attachment: false,
   2145             #[cfg(feature = "serde_relaxed")]
   2146             client_data_json_relaxed: true,
   2147         }
   2148     }
   2149 }
   2150 /// `PrfInput` without the actual data sent to reduce memory usage when storing [`RegistrationServerState`] in an
   2151 /// in-memory collection.
   2152 #[derive(Clone, Copy, Debug)]
   2153 enum ServerPrfInfo {
   2154     /// No `PrfInput`.
   2155     None,
   2156     /// `PrfInput::second` was `None`.
   2157     One(ExtensionInfo),
   2158     /// `PrfInput::second` was `Some`.
   2159     Two(ExtensionInfo),
   2160 }
   2161 #[cfg(test)]
   2162 impl PartialEq for ServerPrfInfo {
   2163     fn eq(&self, other: &Self) -> bool {
   2164         match *self {
   2165             Self::None => matches!(*other, Self::None),
   2166             Self::One(info) => matches!(*other, Self::One(info2) if info == info2),
   2167             Self::Two(info) => matches!(*other, Self::Two(info2) if info == info2),
   2168         }
   2169     }
   2170 }
   2171 impl From<Option<(PrfInput<'_, '_>, ExtensionInfo)>> for ServerPrfInfo {
   2172     fn from(value: Option<(PrfInput<'_, '_>, ExtensionInfo)>) -> Self {
   2173         value.map_or(Self::None, |val| {
   2174             val.0
   2175                 .second
   2176                 .map_or_else(|| Self::One(val.1), |_| Self::Two(val.1))
   2177         })
   2178     }
   2179 }
   2180 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`]
   2181 /// in an in-memory collection.
   2182 #[derive(Clone, Copy, Debug)]
   2183 struct ServerExtensionInfo {
   2184     /// `Extension::cred_props`.
   2185     cred_props: Option<ExtensionReq>,
   2186     /// `Extension::cred_protect`.
   2187     cred_protect: CredProtect,
   2188     /// `Extension::min_pin_length`.
   2189     min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>,
   2190     /// `Extension::prf`.
   2191     prf: ServerPrfInfo,
   2192 }
   2193 impl ServerExtensionInfo {
   2194     /// Validates the extensions.
   2195     fn validate(
   2196         self,
   2197         client_ext: ClientExtensionsOutputs,
   2198         auth_ext: AuthenticatorExtensionOutput,
   2199         error_unsolicited: bool,
   2200     ) -> Result<(), ExtensionErr> {
   2201         if error_unsolicited {
   2202             self.validate_unsolicited(client_ext, auth_ext)
   2203         } else {
   2204             Ok(())
   2205         }
   2206         .and_then(|()| {
   2207             self.validate_required(client_ext, auth_ext)
   2208                 .and_then(|()| self.validate_value(client_ext, auth_ext))
   2209         })
   2210     }
   2211     /// Validates if there are any unsolicited extensions.
   2212     ///
   2213     /// Note no distinction is made between an extension that is empty and one that is not (i.e., we are checking
   2214     ///  purely for the existence of extension keys).
   2215     fn validate_unsolicited(
   2216         mut self,
   2217         client_ext: ClientExtensionsOutputs,
   2218         auth_ext: AuthenticatorExtensionOutput,
   2219     ) -> Result<(), ExtensionErr> {
   2220         // For simpler code, we artificially set non-requested extensions after verifying there was not an error
   2221         // and recursively call this function. There are so few extensions and the checks are fast that there
   2222         // should be no worry of stack overflow or performance overhead.
   2223         if self.cred_props.is_some() {
   2224             if !matches!(self.cred_protect, CredProtect::None) {
   2225                 if self.min_pin_length.is_some() {
   2226                     // This is the last extension, so recursion stops here.
   2227                     if !matches!(self.prf, ServerPrfInfo::None) {
   2228                         Ok(())
   2229                     } else if client_ext.prf.is_some() {
   2230                         Err(ExtensionErr::ForbiddenPrf)
   2231                     } else if !matches!(auth_ext.hmac_secret, HmacSecret::None) {
   2232                         Err(ExtensionErr::ForbiddenHmacSecret)
   2233                     } else {
   2234                         Ok(())
   2235                     }
   2236                 } else if auth_ext.min_pin_length.is_some() {
   2237                     Err(ExtensionErr::ForbiddenMinPinLength)
   2238                 } else {
   2239                     // Pretend to set `minPinLength`, so we can check `prf`.
   2240                     self.min_pin_length =
   2241                         Some((FourToSixtyThree::Four, ExtensionInfo::RequireEnforceValue));
   2242                     self.validate_unsolicited(client_ext, auth_ext)
   2243                 }
   2244             } else if !matches!(auth_ext.cred_protect, CredentialProtectionPolicy::None) {
   2245                 Err(ExtensionErr::ForbiddenCredProtect)
   2246             } else {
   2247                 // Pretend to set `credProtect`, so we can check `minPinLength` and `prf` extensions.
   2248                 self.cred_protect = CredProtect::UserVerificationOptional(
   2249                     false,
   2250                     ExtensionInfo::RequireEnforceValue,
   2251                 );
   2252                 self.validate_unsolicited(client_ext, auth_ext)
   2253             }
   2254         } else if client_ext.cred_props.is_some() {
   2255             Err(ExtensionErr::ForbiddenCredProps)
   2256         } else {
   2257             // Pretend to set `credProps`; so we can check `credProtect`, `minPinLength`, and `prf` extensions.
   2258             self.cred_props = Some(ExtensionReq::Require);
   2259             self.validate_unsolicited(client_ext, auth_ext)
   2260         }
   2261     }
   2262     /// Validates if any required extensions don't have a corresponding response.
   2263     ///
   2264     /// Note empty extensions are treated as missing. For example when requiring the `credProps` extension,
   2265     /// all of the following responses would lead to a failure:
   2266     /// `{"clientExtensionResults":{}}`: no extensions.
   2267     /// `{"clientExtensionResults":{"prf":true}}`: only the `prf` extension.
   2268     /// `{"clientExtensionResults":{"credProps":{}}}`: empty `credProps` extension.
   2269     /// `{"clientExtensionResults":{"credProps":{"foo":false}}}`: `credProps` extension doesn't contain at least one
   2270     /// expected field (i.e., still "empty").
   2271     fn validate_required(
   2272         self,
   2273         client_ext: ClientExtensionsOutputs,
   2274         auth_ext: AuthenticatorExtensionOutput,
   2275     ) -> Result<(), ExtensionErr> {
   2276         // We don't check `self.cred_protect` since `CredProtect::validate` checks for both a required response
   2277         // and value enforcement; thus it only needs to be checked once (which it is in `Self::validate_value`).
   2278         self.cred_props
   2279             .map_or(Ok(()), |info| {
   2280                 if matches!(info, ExtensionReq::Require) {
   2281                     if client_ext
   2282                         .cred_props
   2283                         .is_some_and(|props| props.rk.is_some())
   2284                     {
   2285                         Ok(())
   2286                     } else {
   2287                         Err(ExtensionErr::MissingCredProps)
   2288                     }
   2289                 } else {
   2290                     Ok(())
   2291                 }
   2292             })
   2293             .and_then(|()| {
   2294                 self.min_pin_length
   2295                     .map_or(Ok(()), |info| {
   2296                         if matches!(
   2297                             info.1,
   2298                             ExtensionInfo::RequireEnforceValue
   2299                                 | ExtensionInfo::RequireDontEnforceValue
   2300                         ) {
   2301                             auth_ext
   2302                                 .min_pin_length
   2303                                 .ok_or(ExtensionErr::MissingMinPinLength)
   2304                                 .map(|_| ())
   2305                         } else {
   2306                             Ok(())
   2307                         }
   2308                     })
   2309                     .and_then(|()| match self.prf {
   2310                         ServerPrfInfo::None => Ok(()),
   2311                         ServerPrfInfo::One(info) | ServerPrfInfo::Two(info) => {
   2312                             if matches!(
   2313                                 info,
   2314                                 ExtensionInfo::RequireEnforceValue
   2315                                     | ExtensionInfo::RequireDontEnforceValue
   2316                             ) {
   2317                                 if client_ext.prf.is_some() {
   2318                                     Ok(())
   2319                                 } else {
   2320                                     Err(ExtensionErr::MissingPrf)
   2321                                 }
   2322                             } else {
   2323                                 Ok(())
   2324                             }
   2325                         }
   2326                     })
   2327             })
   2328     }
   2329     /// Validates the value of any extensions sent from the client.
   2330     ///
   2331     /// Note missing and empty extensions are always OK.
   2332     fn validate_value(
   2333         self,
   2334         client_ext: ClientExtensionsOutputs,
   2335         auth_ext: AuthenticatorExtensionOutput,
   2336     ) -> Result<(), ExtensionErr> {
   2337         // This also checks for a missing response. Instead of duplicating that check, we only call
   2338         // `self.cred_protect.validate` once here and not also in `Self::validate_required`.
   2339         self.cred_protect
   2340             .validate(auth_ext.cred_protect)
   2341             .and_then(|()| {
   2342                 self.min_pin_length
   2343                     .map_or(Ok(()), |info| {
   2344                         if matches!(
   2345                             info.1,
   2346                             ExtensionInfo::RequireEnforceValue | ExtensionInfo::AllowEnforceValue
   2347                         ) {
   2348                             auth_ext.min_pin_length.map_or(Ok(()), |pin| {
   2349                                 if pin >= info.0 {
   2350                                     Ok(())
   2351                                 } else {
   2352                                     Err(ExtensionErr::InvalidMinPinLength(info.0, pin))
   2353                                 }
   2354                             })
   2355                         } else {
   2356                             Ok(())
   2357                         }
   2358                     })
   2359                     .and_then(|()| match self.prf {
   2360                         ServerPrfInfo::None => Ok(()),
   2361                         ServerPrfInfo::One(info) | ServerPrfInfo::Two(info) => {
   2362                             if matches!(
   2363                                 info,
   2364                                 ExtensionInfo::RequireEnforceValue
   2365                                     | ExtensionInfo::AllowEnforceValue
   2366                             ) {
   2367                                 client_ext
   2368                                     .prf
   2369                                     .map_or(Ok(()), |prf| {
   2370                                         if prf.enabled {
   2371                                             Ok(())
   2372                                         } else {
   2373                                             Err(ExtensionErr::InvalidPrfValue)
   2374                                         }
   2375                                     })
   2376                                     .and({
   2377                                         if matches!(auth_ext.hmac_secret, HmacSecret::NotEnabled) {
   2378                                             Err(ExtensionErr::InvalidHmacSecretValue)
   2379                                         } else {
   2380                                             Ok(())
   2381                                         }
   2382                                     })
   2383                             } else {
   2384                                 Ok(())
   2385                             }
   2386                         }
   2387                     })
   2388             })
   2389     }
   2390 }
   2391 impl From<Extension<'_, '_>> for ServerExtensionInfo {
   2392     fn from(value: Extension<'_, '_>) -> Self {
   2393         Self {
   2394             cred_props: value.cred_props,
   2395             cred_protect: value.cred_protect,
   2396             min_pin_length: value.min_pin_length,
   2397             prf: value.prf.into(),
   2398         }
   2399     }
   2400 }
   2401 #[cfg(test)]
   2402 impl PartialEq for ServerExtensionInfo {
   2403     fn eq(&self, other: &Self) -> bool {
   2404         self.prf == other.prf
   2405     }
   2406 }
   2407 // This is essentially the `PublicKeyCredentialCreationOptions` used to create it; however to reduce
   2408 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size on
   2409 // `x86_64-unknown-linux-gnu` platforms when `USER_LEN` is `USER_HANDLE_MIN_LEN`.
   2410 /// State needed to be saved when beginning the registration ceremony.
   2411 ///
   2412 /// Saves the necessary information associated with the [`CredentialCreationOptions`] used to create it
   2413 /// via [`CredentialCreationOptions::start_ceremony`] so that registration of a new credential can be
   2414 /// performed with [`Self::verify`].
   2415 ///
   2416 /// `RegistrationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct
   2417 /// `RegistrationServerState` associated with a [`Registration`], one should use its corresponding
   2418 /// [`Registration::challenge`].
   2419 #[derive(Debug)]
   2420 pub struct RegistrationServerState<const USER_LEN: usize> {
   2421     /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-mediation).
   2422     mediation: CredentialMediationRequirement,
   2423     // This is a `SentChallenge` since we need `RegistrationServerState` to be fetchable after receiving the
   2424     // response from the client. This response must obviously be constructable; thus its challenge is a
   2425     // `SentChallenge`.
   2426     //
   2427     // This must never be mutated since we want to ensure it is actually a `Challenge` (which
   2428     // can only be constructed via `Challenge::new`). This is guaranteed to be true iff
   2429     // `serializable_server_state` is not enabled. We avoid implementing `trait`s like `Hash` when that
   2430     // is enabled.
   2431     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-challenge).
   2432     challenge: SentChallenge,
   2433     /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams).
   2434     pub_key_cred_params: CoseAlgorithmIdentifiers,
   2435     /// [`authenticatorSelection`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-authenticatorselection).
   2436     authenticator_selection: AuthenticatorSelectionCriteria,
   2437     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-extensions).
   2438     extensions: ServerExtensionInfo,
   2439     /// `Instant` the ceremony expires.
   2440     #[cfg(not(feature = "serializable_server_state"))]
   2441     expiration: Instant,
   2442     /// `SystemTime` the ceremony expires.
   2443     #[cfg(feature = "serializable_server_state")]
   2444     expiration: SystemTime,
   2445     /// User handle.
   2446     user_id: UserHandle<USER_LEN>,
   2447 }
   2448 impl<const USER_LEN: usize> RegistrationServerState<USER_LEN> {
   2449     #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))]
   2450     fn is_eq(&self, other: &Self) -> bool {
   2451         self.mediation == other.mediation
   2452             && self.challenge == other.challenge
   2453             && self.pub_key_cred_params == other.pub_key_cred_params
   2454             && self.authenticator_selection == other.authenticator_selection
   2455             && self.extensions == other.extensions
   2456             && self.expiration == other.expiration
   2457             && self.user_id == other.user_id
   2458     }
   2459     /// Verifies `response` is valid based on `self` consuming `self` and returning a `RegisteredCredential` that
   2460     /// borrows the necessary data from `response`.
   2461     ///
   2462     /// `rp_id` MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when starting the
   2463     /// ceremony.
   2464     ///
   2465     /// It is _essential_ to ensure [`RegisteredCredential::id`] has not been previously registered; if
   2466     /// so, the ceremony SHOULD be aborted and a failure reported. When saving `RegisteredCredential`, one may
   2467     /// want to save the [`RpId`] and [`PublicKeyCredentialUserEntity`] information; however since [`RpId`] is
   2468     /// likely static, that may not be necessary. User information is also likely static for a given [`UserHandle`]
   2469     /// (which is saved in `RegisteredCredential`); so if such info is saved, one may want to save it once per
   2470     /// `UserHandle` and not per `RegisteredCredential`.
   2471     ///
   2472     /// # Errors
   2473     ///
   2474     /// Errors iff `response` is not valid according to the
   2475     /// [registration ceremony criteria](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential)
   2476     /// or violates any of the settings in `options`.
   2477     #[inline]
   2478     pub fn verify<'a, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>>(
   2479         self,
   2480         rp_id: &RpId,
   2481         response: &'a Registration,
   2482         options: &RegistrationVerificationOptions<'_, '_, O, T>,
   2483     ) -> Result<RegisteredCredential<'a, USER_LEN>, RegCeremonyErr> {
   2484         // [Registration ceremony](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential)
   2485         // is handled by:
   2486         //
   2487         // 1. Calling code.
   2488         // 2. Client code and the construction of `resp` (hopefully via [`Registration::deserialize`]).
   2489         // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAttestation::deserialize`]).
   2490         // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]).
   2491         // 5. [`Self::partial_validate`].
   2492         // 6. [`Self::partial_validate`].
   2493         // 7. [`Self::partial_validate`].
   2494         // 8. [`Self::partial_validate`].
   2495         // 9. [`Self::partial_validate`].
   2496         // 10. [`Self::partial_validate`].
   2497         // 11. [`Self::partial_validate`].
   2498         // 12. [`Self::partial_validate`].
   2499         // 13. [`Self::partial_validate`].
   2500         // 14. [`Self::partial_validate`].
   2501         // 15. Below.
   2502         // 16. [`Self::partial_validate`].
   2503         // 17. [`Self::partial_validate`].
   2504         // 18. [`Self::partial_validate`].
   2505         // 19. [`Self::partial_validate`].
   2506         // 20. Below.
   2507         // 21. [`Self::partial_validate`].
   2508         // 22. [`Self::partial_validate`].
   2509         // 23. N/A since only none and self attestations are supported.
   2510         // 24. Always satisfied since only none and self attestations are supported (Item 3 is N/A).
   2511         // 25. [`Self::partial_validate`].
   2512         // 26. Calling code.
   2513         // 27. Below.
   2514         // 28. N/A since only none and self attestations are supported.
   2515         // 29. Below.
   2516 
   2517         // Steps 5–14, 16–19, 21–22, and 25.
   2518         self.partial_validate(rp_id, response, (), &options.into())
   2519             .map_err(RegCeremonyErr::from)
   2520             .and_then(|attestation_object| {
   2521                 let auth_data = attestation_object.auth_data();
   2522                 let flags = auth_data.flags();
   2523                 // Step 15.
   2524                 if matches!(self.mediation, CredentialMediationRequirement::Conditional)
   2525                     || flags.user_present
   2526                 {
   2527                     self.authenticator_selection
   2528                         // Verify any required authenticator attachment modality.
   2529                         .validate(
   2530                             options.require_authenticator_attachment,
   2531                             response.authenticator_attachment,
   2532                         )
   2533                         .and_then(|()| {
   2534                             let attested_credential_data = auth_data.attested_credential_data();
   2535                             self.pub_key_cred_params
   2536                                 // Step 20.
   2537                                 .validate(attested_credential_data.credential_public_key)
   2538                                 .and_then(|()| {
   2539                                     let extensions = auth_data.extensions();
   2540                                     // Step 27.
   2541                                     self.extensions
   2542                                         .validate(
   2543                                             response.client_extension_results,
   2544                                             extensions,
   2545                                             options.error_on_unsolicited_extensions,
   2546                                         )
   2547                                         .map_err(RegCeremonyErr::Extension)
   2548                                         .and_then(|()| {
   2549                                             // Step 29.
   2550                                             RegisteredCredential::new(
   2551                                                 attested_credential_data.credential_id,
   2552                                                 response.response.transports(),
   2553                                                 self.user_id,
   2554                                                 StaticState {
   2555                                                     credential_public_key: attested_credential_data
   2556                                                         .credential_public_key,
   2557                                                     extensions: extensions.into(),
   2558                                                     client_extension_results: response
   2559                                                         .client_extension_results
   2560                                                         .into(),
   2561                                                 },
   2562                                                 DynamicState {
   2563                                                     user_verified: flags.user_verified,
   2564                                                     backup: flags.backup,
   2565                                                     sign_count: auth_data.sign_count(),
   2566                                                     authenticator_attachment: response
   2567                                                         .authenticator_attachment,
   2568                                                 },
   2569                                                 Metadata {
   2570                                                     attestation: match attestation_object
   2571                                                         .attestation()
   2572                                                     {
   2573                                                         AttestationFormat::None => {
   2574                                                             Attestation::None
   2575                                                         }
   2576                                                         AttestationFormat::Packed(_) => {
   2577                                                             Attestation::Surrogate
   2578                                                         }
   2579                                                     },
   2580                                                     aaguid: attested_credential_data.aaguid,
   2581                                                     extensions: extensions.into(),
   2582                                                     client_extension_results: response
   2583                                                         .client_extension_results
   2584                                                         .into(),
   2585                                                     resident_key: self
   2586                                                         .authenticator_selection
   2587                                                         .resident_key,
   2588                                                 },
   2589                                             )
   2590                                             .map_err(RegCeremonyErr::Credential)
   2591                                         })
   2592                                 })
   2593                         })
   2594                 } else {
   2595                     Err(RegCeremonyErr::UserNotPresent)
   2596                 }
   2597             })
   2598     }
   2599 }
   2600 impl<const USER_LEN: usize> TimedCeremony for RegistrationServerState<USER_LEN> {
   2601     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   2602     #[inline]
   2603     fn expiration(&self) -> Instant {
   2604         self.expiration
   2605     }
   2606     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   2607     #[inline]
   2608     fn expiration(&self) -> SystemTime {
   2609         self.expiration
   2610     }
   2611 }
   2612 impl<const USER_LEN: usize> Ceremony<USER_LEN, false> for RegistrationServerState<USER_LEN> {
   2613     type R = Registration;
   2614     fn rand_challenge(&self) -> SentChallenge {
   2615         self.challenge
   2616     }
   2617     #[cfg(not(feature = "serializable_server_state"))]
   2618     fn expiry(&self) -> Instant {
   2619         self.expiration
   2620     }
   2621     #[cfg(feature = "serializable_server_state")]
   2622     fn expiry(&self) -> SystemTime {
   2623         self.expiration
   2624     }
   2625     fn user_verification(&self) -> UserVerificationRequirement {
   2626         self.authenticator_selection.user_verification
   2627     }
   2628 }
   2629 impl<const USER_LEN: usize> Borrow<SentChallenge> for RegistrationServerState<USER_LEN> {
   2630     #[inline]
   2631     fn borrow(&self) -> &SentChallenge {
   2632         &self.challenge
   2633     }
   2634 }
   2635 impl<const USER_LEN: usize> PartialEq for RegistrationServerState<USER_LEN> {
   2636     #[inline]
   2637     fn eq(&self, other: &Self) -> bool {
   2638         self.challenge == other.challenge
   2639     }
   2640 }
   2641 impl<const USER_LEN: usize> PartialEq<&Self> for RegistrationServerState<USER_LEN> {
   2642     #[inline]
   2643     fn eq(&self, other: &&Self) -> bool {
   2644         *self == **other
   2645     }
   2646 }
   2647 impl<const USER_LEN: usize> PartialEq<RegistrationServerState<USER_LEN>>
   2648     for &RegistrationServerState<USER_LEN>
   2649 {
   2650     #[inline]
   2651     fn eq(&self, other: &RegistrationServerState<USER_LEN>) -> bool {
   2652         **self == *other
   2653     }
   2654 }
   2655 impl<const USER_LEN: usize> Eq for RegistrationServerState<USER_LEN> {}
   2656 impl<const USER_LEN: usize> Hash for RegistrationServerState<USER_LEN> {
   2657     #[inline]
   2658     fn hash<H: Hasher>(&self, state: &mut H) {
   2659         self.challenge.hash(state);
   2660     }
   2661 }
   2662 impl<const USER_LEN: usize> PartialOrd for RegistrationServerState<USER_LEN> {
   2663     #[inline]
   2664     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
   2665         Some(self.cmp(other))
   2666     }
   2667 }
   2668 impl<const USER_LEN: usize> Ord for RegistrationServerState<USER_LEN> {
   2669     #[inline]
   2670     fn cmp(&self, other: &Self) -> Ordering {
   2671         self.challenge.cmp(&other.challenge)
   2672     }
   2673 }
   2674 /// `RegistrationServerState` based on a [`UserHandle64`].
   2675 pub type RegistrationServerState64 = RegistrationServerState<USER_HANDLE_MAX_LEN>;
   2676 /// `RegistrationServerState` based on a [`UserHandle16`].
   2677 pub type RegistrationServerState16 = RegistrationServerState<16>;
   2678 #[cfg(test)]
   2679 mod tests {
   2680     #[cfg(all(
   2681         feature = "custom",
   2682         any(
   2683             feature = "serializable_server_state",
   2684             not(any(feature = "bin", feature = "serde"))
   2685         )
   2686     ))]
   2687     use super::{
   2688         super::{super::AggErr, ExtensionInfo},
   2689         Challenge, CredProtect, CredentialCreationOptions, DisplayName, FourToSixtyThree, PrfInput,
   2690         PublicKeyCredentialUserEntity, RpId, UserHandle,
   2691     };
   2692     #[cfg(all(feature = "custom", feature = "serializable_server_state"))]
   2693     use super::{
   2694         super::{
   2695             super::bin::{Decode as _, Encode as _},
   2696             AsciiDomain,
   2697         },
   2698         Extension, RegistrationServerState,
   2699     };
   2700     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2701     use super::{
   2702         super::{
   2703             super::{
   2704                 CredentialErr,
   2705                 response::register::{
   2706                     AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation,
   2707                     ClientExtensionsOutputs, CredentialPropertiesOutput,
   2708                     CredentialProtectionPolicy, HmacSecret,
   2709                 },
   2710             },
   2711             AuthTransports,
   2712         },
   2713         AuthenticatorAttachment, BackupReq, ExtensionErr, ExtensionReq, RegCeremonyErr,
   2714         Registration, RegistrationVerificationOptions, UserVerificationRequirement, Username,
   2715     };
   2716     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2717     use rsa::sha2::{Digest as _, Sha256};
   2718     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2719     const CBOR_UINT: u8 = 0b000_00000;
   2720     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2721     const CBOR_NEG: u8 = 0b001_00000;
   2722     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2723     const CBOR_BYTES: u8 = 0b010_00000;
   2724     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2725     const CBOR_TEXT: u8 = 0b011_00000;
   2726     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2727     const CBOR_MAP: u8 = 0b101_00000;
   2728     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2729     const CBOR_SIMPLE: u8 = 0b111_00000;
   2730     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2731     const CBOR_FALSE: u8 = CBOR_SIMPLE | 20;
   2732     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2733     const CBOR_TRUE: u8 = CBOR_SIMPLE | 21;
   2734     #[expect(clippy::panic_in_result_fn, reason = "OK in tests")]
   2735     #[test]
   2736     #[cfg(all(feature = "custom", feature = "serializable_server_state"))]
   2737     fn eddsa_reg_ser() -> Result<(), AggErr> {
   2738         let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?);
   2739         let id = UserHandle::from([0; 1]);
   2740         let mut opts = CredentialCreationOptions::passkey(
   2741             &rp_id,
   2742             PublicKeyCredentialUserEntity {
   2743                 name: "foo".try_into()?,
   2744                 id: &id,
   2745                 display_name: DisplayName::Blank,
   2746             },
   2747             Vec::new(),
   2748         );
   2749         opts.public_key.challenge = Challenge(0);
   2750         opts.public_key.extensions = Extension {
   2751             cred_props: None,
   2752             cred_protect: CredProtect::UserVerificationRequired(
   2753                 false,
   2754                 ExtensionInfo::RequireEnforceValue,
   2755             ),
   2756             min_pin_length: Some((FourToSixtyThree::Ten, ExtensionInfo::RequireEnforceValue)),
   2757             prf: Some((
   2758                 PrfInput {
   2759                     first: [0].as_slice(),
   2760                     second: None,
   2761                 },
   2762                 ExtensionInfo::RequireEnforceValue,
   2763             )),
   2764         };
   2765         let server = opts.start_ceremony()?.0;
   2766         let enc_data = server
   2767             .encode()
   2768             .map_err(AggErr::EncodeRegistrationServerState)?;
   2769         assert_eq!(enc_data.capacity(), enc_data.len());
   2770         assert_eq!(enc_data.len(), 1 + 16 + 1 + 4 + (1 + 3 + 3 + 2) + 12 + 1);
   2771         assert!(server.is_eq(&RegistrationServerState::decode(enc_data.as_slice())?));
   2772         Ok(())
   2773     }
   2774     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2775     #[derive(Clone, Copy)]
   2776     struct TestResponseOptions {
   2777         user_verified: bool,
   2778         cred_protect: CredentialProtectionPolicy,
   2779         prf: Option<bool>,
   2780         hmac: HmacSecret,
   2781         min_pin: Option<FourToSixtyThree>,
   2782         #[expect(clippy::option_option, reason = "fine")]
   2783         cred_props: Option<Option<bool>>,
   2784     }
   2785     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2786     #[derive(Clone, Copy)]
   2787     enum PrfUvOptions {
   2788         /// `true` iff `UserVerificationRequirement::Required` should be used; otherwise
   2789         /// `UserVerificationRequirement::Preferred` is used.
   2790         None(bool),
   2791         Prf(ExtensionInfo),
   2792     }
   2793     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2794     #[derive(Clone, Copy)]
   2795     struct TestRequestOptions {
   2796         error_unsolicited: bool,
   2797         protect: CredProtect,
   2798         prf_uv: PrfUvOptions,
   2799         props: Option<ExtensionReq>,
   2800         pin: Option<(FourToSixtyThree, ExtensionInfo)>,
   2801     }
   2802     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2803     #[derive(Clone, Copy)]
   2804     struct TestOptions {
   2805         request: TestRequestOptions,
   2806         response: TestResponseOptions,
   2807     }
   2808     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2809     fn generate_client_data_json() -> Vec<u8> {
   2810         let mut json = Vec::with_capacity(256);
   2811         json.extend_from_slice(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice());
   2812         json
   2813     }
   2814     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   2815     #[expect(
   2816         clippy::arithmetic_side_effects,
   2817         clippy::indexing_slicing,
   2818         reason = "comments justify correctness"
   2819     )]
   2820     #[expect(
   2821         clippy::cognitive_complexity,
   2822         clippy::too_many_lines,
   2823         reason = "a lot to test"
   2824     )]
   2825     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   2826     fn generate_attestation_object(options: TestResponseOptions) -> Vec<u8> {
   2827         let mut attestation_object = Vec::with_capacity(256);
   2828         attestation_object.extend_from_slice(
   2829             [
   2830                 CBOR_MAP | 3,
   2831                 CBOR_TEXT | 3,
   2832                 b'f',
   2833                 b'm',
   2834                 b't',
   2835                 CBOR_TEXT | 4,
   2836                 b'n',
   2837                 b'o',
   2838                 b'n',
   2839                 b'e',
   2840                 CBOR_TEXT | 7,
   2841                 b'a',
   2842                 b't',
   2843                 b't',
   2844                 b'S',
   2845                 b't',
   2846                 b'm',
   2847                 b't',
   2848                 CBOR_MAP,
   2849                 CBOR_TEXT | 8,
   2850                 b'a',
   2851                 b'u',
   2852                 b't',
   2853                 b'h',
   2854                 b'D',
   2855                 b'a',
   2856                 b't',
   2857                 b'a',
   2858                 CBOR_BYTES | 24,
   2859                 // Length.
   2860                 // Addition won't overflow.
   2861                 113 + if matches!(options.cred_protect, CredentialProtectionPolicy::None) {
   2862                     if matches!(options.hmac, HmacSecret::None) {
   2863                         options.min_pin.map_or(0, |_| 15)
   2864                     } else {
   2865                         14 + options.min_pin.map_or(0, |_| 14)
   2866                             + match options.hmac {
   2867                                 HmacSecret::None => unreachable!("bug"),
   2868                                 HmacSecret::NotEnabled | HmacSecret::Enabled => 0,
   2869                                 HmacSecret::One => 65,
   2870                                 HmacSecret::Two => 97,
   2871                             }
   2872                     }
   2873                 } else {
   2874                     14 + if matches!(options.hmac, HmacSecret::None) {
   2875                         0
   2876                     } else {
   2877                         13
   2878                     } + options.min_pin.map_or(0, |_| 14)
   2879                         + match options.hmac {
   2880                             HmacSecret::None | HmacSecret::NotEnabled | HmacSecret::Enabled => 0,
   2881                             HmacSecret::One => 65,
   2882                             HmacSecret::Two => 97,
   2883                         }
   2884                 },
   2885                 // RP ID HASH.
   2886                 // This will be overwritten later.
   2887                 0,
   2888                 0,
   2889                 0,
   2890                 0,
   2891                 0,
   2892                 0,
   2893                 0,
   2894                 0,
   2895                 0,
   2896                 0,
   2897                 0,
   2898                 0,
   2899                 0,
   2900                 0,
   2901                 0,
   2902                 0,
   2903                 0,
   2904                 0,
   2905                 0,
   2906                 0,
   2907                 0,
   2908                 0,
   2909                 0,
   2910                 0,
   2911                 0,
   2912                 0,
   2913                 0,
   2914                 0,
   2915                 0,
   2916                 0,
   2917                 0,
   2918                 0,
   2919                 // FLAGS.
   2920                 // UP, UV, AT, and ED (right-to-left).
   2921                 0b0100_0001
   2922                     | if options.user_verified {
   2923                         0b0000_0100
   2924                     } else {
   2925                         0b0000_0000
   2926                     }
   2927                     | if matches!(options.cred_protect, CredentialProtectionPolicy::None)
   2928                         && matches!(options.hmac, HmacSecret::None)
   2929                         && options.min_pin.is_none()
   2930                     {
   2931                         0
   2932                     } else {
   2933                         0b1000_0000
   2934                     },
   2935                 // COUNTER.
   2936                 // 0 as 32-bit big endian.
   2937                 0,
   2938                 0,
   2939                 0,
   2940                 0,
   2941                 // AAGUID.
   2942                 0,
   2943                 0,
   2944                 0,
   2945                 0,
   2946                 0,
   2947                 0,
   2948                 0,
   2949                 0,
   2950                 0,
   2951                 0,
   2952                 0,
   2953                 0,
   2954                 0,
   2955                 0,
   2956                 0,
   2957                 0,
   2958                 // L.
   2959                 // CREDENTIAL ID length is 16 as 16-bit big endian.
   2960                 0,
   2961                 16,
   2962                 // CREDENTIAL ID.
   2963                 0,
   2964                 0,
   2965                 0,
   2966                 0,
   2967                 0,
   2968                 0,
   2969                 0,
   2970                 0,
   2971                 0,
   2972                 0,
   2973                 0,
   2974                 0,
   2975                 0,
   2976                 0,
   2977                 0,
   2978                 0,
   2979                 CBOR_MAP | 4,
   2980                 // COSE kty.
   2981                 CBOR_UINT | 1,
   2982                 // COSE OKP.
   2983                 CBOR_UINT | 1,
   2984                 // COSE alg.
   2985                 CBOR_UINT | 3,
   2986                 // COSE Eddsa.
   2987                 CBOR_NEG | 7,
   2988                 // COSE OKP crv.
   2989                 CBOR_NEG,
   2990                 // COSE Ed25519.
   2991                 CBOR_UINT | 6,
   2992                 // COSE OKP x.
   2993                 CBOR_NEG | 1,
   2994                 CBOR_BYTES | 24,
   2995                 // Length is 32.
   2996                 32,
   2997                 // Compressed-y coordinate.
   2998                 59,
   2999                 106,
   3000                 39,
   3001                 188,
   3002                 206,
   3003                 182,
   3004                 164,
   3005                 45,
   3006                 98,
   3007                 163,
   3008                 168,
   3009                 208,
   3010                 42,
   3011                 111,
   3012                 13,
   3013                 115,
   3014                 101,
   3015                 50,
   3016                 21,
   3017                 119,
   3018                 29,
   3019                 226,
   3020                 67,
   3021                 166,
   3022                 58,
   3023                 192,
   3024                 72,
   3025                 161,
   3026                 139,
   3027                 89,
   3028                 218,
   3029                 41,
   3030             ]
   3031             .as_slice(),
   3032         );
   3033         attestation_object[30..62].copy_from_slice(&Sha256::digest(b"example.com"));
   3034         if matches!(options.cred_protect, CredentialProtectionPolicy::None) {
   3035             if matches!(options.hmac, HmacSecret::None) {
   3036                 if options.min_pin.is_some() {
   3037                     attestation_object.push(CBOR_MAP | 1);
   3038                 }
   3039             } else if options.min_pin.is_some() {
   3040                 attestation_object.push(
   3041                     // Addition won't overflow.
   3042                     CBOR_MAP
   3043                         | (2 + u8::from(matches!(options.hmac, HmacSecret::One | HmacSecret::Two))),
   3044                 );
   3045             } else {
   3046                 attestation_object.push(
   3047                     // Addition won't overflow.
   3048                     CBOR_MAP
   3049                         | (1 + u8::from(matches!(options.hmac, HmacSecret::One | HmacSecret::Two))),
   3050                 );
   3051             }
   3052         } else {
   3053             attestation_object.extend_from_slice(
   3054                 [
   3055                     // Addition won't overflow.
   3056                     CBOR_MAP | (1 + match options.hmac { HmacSecret::None => 0, HmacSecret::NotEnabled | HmacSecret::Enabled => 1, HmacSecret::One | HmacSecret::Two => 2, } + u8::from(options.min_pin.is_some())),
   3057                     // CBOR text of length 11.
   3058                     CBOR_TEXT | 11,
   3059                     b'c',
   3060                     b'r',
   3061                     b'e',
   3062                     b'd',
   3063                     b'P',
   3064                     b'r',
   3065                     b'o',
   3066                     b't',
   3067                     b'e',
   3068                     b'c',
   3069                     b't',
   3070                     // Addition won't overflow.
   3071                     match options.cred_protect { CredentialProtectionPolicy::None => unreachable!("bug"), CredentialProtectionPolicy::UserVerificationOptional => 1, CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => 2, CredentialProtectionPolicy::UserVerificationRequired => 3, },
   3072                 ].as_slice()
   3073             );
   3074         }
   3075         if !matches!(options.hmac, HmacSecret::None) {
   3076             attestation_object.extend_from_slice(
   3077                 [
   3078                     // CBOR text of length 11.
   3079                     CBOR_TEXT | 11,
   3080                     b'h',
   3081                     b'm',
   3082                     b'a',
   3083                     b'c',
   3084                     b'-',
   3085                     b's',
   3086                     b'e',
   3087                     b'c',
   3088                     b'r',
   3089                     b'e',
   3090                     b't',
   3091                     if matches!(options.hmac, HmacSecret::NotEnabled) {
   3092                         CBOR_FALSE
   3093                     } else {
   3094                         CBOR_TRUE
   3095                     },
   3096                 ]
   3097                 .as_slice(),
   3098             );
   3099         }
   3100         _ = options.min_pin.map(|p| {
   3101             assert!(p <= FourToSixtyThree::TwentyThree, "bug");
   3102             attestation_object.extend_from_slice(
   3103                 [
   3104                     // CBOR text of length 12.
   3105                     CBOR_TEXT | 12,
   3106                     b'm',
   3107                     b'i',
   3108                     b'n',
   3109                     b'P',
   3110                     b'i',
   3111                     b'n',
   3112                     b'L',
   3113                     b'e',
   3114                     b'n',
   3115                     b'g',
   3116                     b't',
   3117                     b'h',
   3118                     CBOR_UINT | p.into_u8(),
   3119                 ]
   3120                 .as_slice(),
   3121             );
   3122         });
   3123         if matches!(options.hmac, HmacSecret::One | HmacSecret::Two) {
   3124             attestation_object.extend_from_slice(
   3125                 [
   3126                     // CBOR text of length 14.
   3127                     CBOR_TEXT | 14,
   3128                     b'h',
   3129                     b'm',
   3130                     b'a',
   3131                     b'c',
   3132                     b'-',
   3133                     b's',
   3134                     b'e',
   3135                     b'c',
   3136                     b'r',
   3137                     b'e',
   3138                     b't',
   3139                     b'-',
   3140                     b'm',
   3141                     b'c',
   3142                     CBOR_BYTES | 24,
   3143                 ]
   3144                 .as_slice(),
   3145             );
   3146             if matches!(options.hmac, HmacSecret::One) {
   3147                 attestation_object.push(48);
   3148                 attestation_object.extend_from_slice([1; 48].as_slice());
   3149             } else {
   3150                 attestation_object.push(80);
   3151                 attestation_object.extend_from_slice([1; 80].as_slice());
   3152             }
   3153         }
   3154         attestation_object
   3155     }
   3156     #[expect(clippy::unwrap_in_result, clippy::unwrap_used, reason = "OK in tests")]
   3157     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   3158     fn validate(options: TestOptions) -> Result<(), AggErr> {
   3159         let rp_id = RpId::Domain("example.com".to_owned().try_into()?);
   3160         let registration = Registration::new(
   3161             AuthenticatorAttestation::new(
   3162                 generate_client_data_json(),
   3163                 generate_attestation_object(options.response),
   3164                 AuthTransports::NONE,
   3165             ),
   3166             AuthenticatorAttachment::None,
   3167             ClientExtensionsOutputs {
   3168                 cred_props: options
   3169                     .response
   3170                     .cred_props
   3171                     .map(|rk| CredentialPropertiesOutput { rk }),
   3172                 prf: options
   3173                     .response
   3174                     .prf
   3175                     .map(|enabled| AuthenticationExtensionsPrfOutputs { enabled }),
   3176             },
   3177         );
   3178         let reg_opts = RegistrationVerificationOptions::<'static, 'static, &str, &str> {
   3179             allowed_origins: [].as_slice(),
   3180             allowed_top_origins: None,
   3181             backup_requirement: BackupReq::None,
   3182             error_on_unsolicited_extensions: options.request.error_unsolicited,
   3183             require_authenticator_attachment: false,
   3184             #[cfg(feature = "serde_relaxed")]
   3185             client_data_json_relaxed: false,
   3186         };
   3187         let user = UserHandle::from([0; 1]);
   3188         let mut opts = CredentialCreationOptions::passkey(
   3189             &rp_id,
   3190             PublicKeyCredentialUserEntity {
   3191                 id: &user,
   3192                 name: Username::try_from("blank").unwrap(),
   3193                 display_name: DisplayName::Blank,
   3194             },
   3195             Vec::new(),
   3196         );
   3197         opts.public_key.challenge = Challenge(0);
   3198         opts.public_key.authenticator_selection.user_verification =
   3199             UserVerificationRequirement::Preferred;
   3200         match options.request.prf_uv {
   3201             PrfUvOptions::None(required) => {
   3202                 if required
   3203                     || matches!(
   3204                         options.request.protect,
   3205                         CredProtect::UserVerificationRequired(_, _)
   3206                     )
   3207                 {
   3208                     opts.public_key.authenticator_selection.user_verification =
   3209                         UserVerificationRequirement::Required;
   3210                 }
   3211             }
   3212             PrfUvOptions::Prf(info) => {
   3213                 opts.public_key.authenticator_selection.user_verification =
   3214                     UserVerificationRequirement::Required;
   3215                 opts.public_key.extensions.prf = Some((
   3216                     PrfInput {
   3217                         first: [0].as_slice(),
   3218                         second: None,
   3219                     },
   3220                     info,
   3221                 ));
   3222             }
   3223         }
   3224         opts.public_key.extensions.cred_protect = options.request.protect;
   3225         opts.public_key.extensions.cred_props = options.request.props;
   3226         opts.public_key.extensions.min_pin_length = options.request.pin;
   3227         opts.start_ceremony()?
   3228             .0
   3229             .verify(&rp_id, &registration, &reg_opts)
   3230             .map_err(AggErr::RegCeremony)
   3231             .map(|_| ())
   3232     }
   3233     /// Test all, and only, possible `UserNotVerified` errors.
   3234     /// 4 * 3 * 5 * 2 * 13 * 5 * 3 * 5 * 4 * 4 = 1,872,000 tests.
   3235     /// We ignore this due to how long it takes (around 30 seconds or so).
   3236     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   3237     #[test]
   3238     #[ignore = "slow"]
   3239     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   3240     fn uv_required_err() {
   3241         const ALL_CRED_PROTECTION_OPTIONS: [CredentialProtectionPolicy; 4] = [
   3242             CredentialProtectionPolicy::None,
   3243             CredentialProtectionPolicy::UserVerificationOptional,
   3244             CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
   3245             CredentialProtectionPolicy::UserVerificationRequired,
   3246         ];
   3247         const ALL_PRF_OPTIONS: [Option<bool>; 3] = [None, Some(false), Some(true)];
   3248         const ALL_HMAC_OPTIONS: [HmacSecret; 5] = [
   3249             HmacSecret::None,
   3250             HmacSecret::NotEnabled,
   3251             HmacSecret::Enabled,
   3252             HmacSecret::One,
   3253             HmacSecret::Two,
   3254         ];
   3255         const ALL_UNSOLICIT_OPTIONS: [bool; 2] = [false, true];
   3256         const ALL_CRED_PROTECT_OPTIONS: [CredProtect; 13] = [
   3257             CredProtect::None,
   3258             CredProtect::UserVerificationOptional(false, ExtensionInfo::RequireEnforceValue),
   3259             CredProtect::UserVerificationOptional(true, ExtensionInfo::RequireDontEnforceValue),
   3260             CredProtect::UserVerificationOptional(false, ExtensionInfo::AllowEnforceValue),
   3261             CredProtect::UserVerificationOptional(true, ExtensionInfo::AllowDontEnforceValue),
   3262             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3263                 false,
   3264                 ExtensionInfo::RequireEnforceValue,
   3265             ),
   3266             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3267                 true,
   3268                 ExtensionInfo::RequireDontEnforceValue,
   3269             ),
   3270             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3271                 false,
   3272                 ExtensionInfo::AllowEnforceValue,
   3273             ),
   3274             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3275                 true,
   3276                 ExtensionInfo::AllowDontEnforceValue,
   3277             ),
   3278             CredProtect::UserVerificationRequired(false, ExtensionInfo::RequireEnforceValue),
   3279             CredProtect::UserVerificationRequired(true, ExtensionInfo::RequireDontEnforceValue),
   3280             CredProtect::UserVerificationRequired(false, ExtensionInfo::AllowEnforceValue),
   3281             CredProtect::UserVerificationRequired(true, ExtensionInfo::AllowDontEnforceValue),
   3282         ];
   3283         const ALL_NOT_FALSE_PRF_UV_OPTIONS: [PrfUvOptions; 5] = [
   3284             PrfUvOptions::None(true),
   3285             PrfUvOptions::Prf(ExtensionInfo::RequireEnforceValue),
   3286             PrfUvOptions::Prf(ExtensionInfo::RequireDontEnforceValue),
   3287             PrfUvOptions::Prf(ExtensionInfo::AllowEnforceValue),
   3288             PrfUvOptions::Prf(ExtensionInfo::AllowDontEnforceValue),
   3289         ];
   3290         const ALL_PROPS_OPTIONS: [Option<ExtensionReq>; 3] =
   3291             [None, Some(ExtensionReq::Require), Some(ExtensionReq::Allow)];
   3292         const ALL_PIN_OPTIONS: [Option<(FourToSixtyThree, ExtensionInfo)>; 5] = [
   3293             None,
   3294             Some((FourToSixtyThree::Five, ExtensionInfo::RequireEnforceValue)),
   3295             Some((
   3296                 FourToSixtyThree::Five,
   3297                 ExtensionInfo::RequireDontEnforceValue,
   3298             )),
   3299             Some((FourToSixtyThree::Five, ExtensionInfo::AllowEnforceValue)),
   3300             Some((FourToSixtyThree::Five, ExtensionInfo::AllowDontEnforceValue)),
   3301         ];
   3302         #[expect(clippy::option_option, reason = "fine")]
   3303         const ALL_CRED_PROPS_OPTIONS: [Option<Option<bool>>; 4] =
   3304             [None, Some(None), Some(Some(false)), Some(Some(true))];
   3305         const ALL_MIN_PIN_OPTIONS: [Option<FourToSixtyThree>; 4] = [
   3306             None,
   3307             Some(FourToSixtyThree::Four),
   3308             Some(FourToSixtyThree::Five),
   3309             Some(FourToSixtyThree::Six),
   3310         ];
   3311         for cred_protect in ALL_CRED_PROTECTION_OPTIONS {
   3312             for prf in ALL_PRF_OPTIONS {
   3313                 for hmac in ALL_HMAC_OPTIONS {
   3314                     for cred_props in ALL_CRED_PROPS_OPTIONS {
   3315                         for min_pin in ALL_MIN_PIN_OPTIONS {
   3316                             for error_unsolicited in ALL_UNSOLICIT_OPTIONS {
   3317                                 for protect in ALL_CRED_PROTECT_OPTIONS {
   3318                                     for prf_uv in ALL_NOT_FALSE_PRF_UV_OPTIONS {
   3319                                         for props in ALL_PROPS_OPTIONS {
   3320                                             for pin in ALL_PIN_OPTIONS {
   3321                                                 assert!(validate(TestOptions {
   3322                                                     request: TestRequestOptions {
   3323                                                         error_unsolicited,
   3324                                                         protect,
   3325                                                         prf_uv,
   3326                                                         props,
   3327                                                         pin,
   3328                                                     },
   3329                                                     response: TestResponseOptions {
   3330                                                         user_verified: false,
   3331                                                         hmac,
   3332                                                         cred_protect,
   3333                                                         prf,
   3334                                                         min_pin,
   3335                                                         cred_props,
   3336                                                     },
   3337                                                 }).is_err_and(|err| matches!(err, AggErr::RegCeremony(reg_err) if matches!(reg_err, RegCeremonyErr::UserNotVerified))));
   3338                                             }
   3339                                         }
   3340                                     }
   3341                                 }
   3342                             }
   3343                         }
   3344                     }
   3345                 }
   3346             }
   3347         }
   3348     }
   3349     /// Test all, and only, possible `ForbiddenCredProps` errors.
   3350     /// 4 * 3 * 5 * 2 * 13 * 6 * 5 * 3 * 4 = 561,600
   3351     /// -
   3352     /// 4 * 3 * 5 * 4 * 6 * 5 * 3 * 4 = 86,400
   3353     /// -
   3354     /// 4 * 3 * 5 * 13 * 5 * 5 * 3 * 4 = 234,000
   3355     /// +
   3356     /// 4 * 3 * 5 * 4 * 5 * 5 * 3 * 4 = 72,000
   3357     /// =
   3358     /// 313,200 total tests.
   3359     /// We ignore this due to how long it takes (around 6 seconds or so).
   3360     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   3361     #[test]
   3362     #[ignore = "slow"]
   3363     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   3364     fn forbidden_cred_props() {
   3365         const ALL_CRED_PROTECTION_OPTIONS: [CredentialProtectionPolicy; 4] = [
   3366             CredentialProtectionPolicy::None,
   3367             CredentialProtectionPolicy::UserVerificationOptional,
   3368             CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
   3369             CredentialProtectionPolicy::UserVerificationRequired,
   3370         ];
   3371         const ALL_PRF_OPTIONS: [Option<bool>; 3] = [None, Some(false), Some(true)];
   3372         const ALL_HMAC_OPTIONS: [HmacSecret; 5] = [
   3373             HmacSecret::None,
   3374             HmacSecret::NotEnabled,
   3375             HmacSecret::Enabled,
   3376             HmacSecret::One,
   3377             HmacSecret::Two,
   3378         ];
   3379         const ALL_UV_OPTIONS: [bool; 2] = [false, true];
   3380         const ALL_CRED_PROTECT_OPTIONS: [CredProtect; 13] = [
   3381             CredProtect::None,
   3382             CredProtect::UserVerificationOptional(false, ExtensionInfo::RequireEnforceValue),
   3383             CredProtect::UserVerificationOptional(true, ExtensionInfo::RequireDontEnforceValue),
   3384             CredProtect::UserVerificationOptional(false, ExtensionInfo::AllowEnforceValue),
   3385             CredProtect::UserVerificationOptional(true, ExtensionInfo::AllowDontEnforceValue),
   3386             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3387                 false,
   3388                 ExtensionInfo::RequireEnforceValue,
   3389             ),
   3390             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3391                 true,
   3392                 ExtensionInfo::RequireDontEnforceValue,
   3393             ),
   3394             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3395                 false,
   3396                 ExtensionInfo::AllowEnforceValue,
   3397             ),
   3398             CredProtect::UserVerificationOptionalWithCredentialIdList(
   3399                 true,
   3400                 ExtensionInfo::AllowDontEnforceValue,
   3401             ),
   3402             CredProtect::UserVerificationRequired(false, ExtensionInfo::RequireEnforceValue),
   3403             CredProtect::UserVerificationRequired(true, ExtensionInfo::RequireDontEnforceValue),
   3404             CredProtect::UserVerificationRequired(false, ExtensionInfo::AllowEnforceValue),
   3405             CredProtect::UserVerificationRequired(true, ExtensionInfo::AllowDontEnforceValue),
   3406         ];
   3407         const ALL_PRF_UV_OPTIONS: [PrfUvOptions; 6] = [
   3408             PrfUvOptions::None(false),
   3409             PrfUvOptions::None(true),
   3410             PrfUvOptions::Prf(ExtensionInfo::RequireEnforceValue),
   3411             PrfUvOptions::Prf(ExtensionInfo::RequireDontEnforceValue),
   3412             PrfUvOptions::Prf(ExtensionInfo::AllowEnforceValue),
   3413             PrfUvOptions::Prf(ExtensionInfo::AllowDontEnforceValue),
   3414         ];
   3415         const ALL_PIN_OPTIONS: [Option<(FourToSixtyThree, ExtensionInfo)>; 5] = [
   3416             None,
   3417             Some((FourToSixtyThree::Five, ExtensionInfo::RequireEnforceValue)),
   3418             Some((
   3419                 FourToSixtyThree::Five,
   3420                 ExtensionInfo::RequireDontEnforceValue,
   3421             )),
   3422             Some((FourToSixtyThree::Five, ExtensionInfo::AllowEnforceValue)),
   3423             Some((FourToSixtyThree::Five, ExtensionInfo::AllowDontEnforceValue)),
   3424         ];
   3425         #[expect(clippy::option_option, reason = "fine")]
   3426         const ALL_NON_EMPTY_CRED_PROPS_OPTIONS: [Option<Option<bool>>; 3] =
   3427             [Some(None), Some(Some(false)), Some(Some(true))];
   3428         const ALL_MIN_PIN_OPTIONS: [Option<FourToSixtyThree>; 4] = [
   3429             None,
   3430             Some(FourToSixtyThree::Four),
   3431             Some(FourToSixtyThree::Five),
   3432             Some(FourToSixtyThree::Six),
   3433         ];
   3434         for cred_protect in ALL_CRED_PROTECTION_OPTIONS {
   3435             for prf in ALL_PRF_OPTIONS {
   3436                 for hmac in ALL_HMAC_OPTIONS {
   3437                     for cred_props in ALL_NON_EMPTY_CRED_PROPS_OPTIONS {
   3438                         for min_pin in ALL_MIN_PIN_OPTIONS {
   3439                             for user_verified in ALL_UV_OPTIONS {
   3440                                 for protect in ALL_CRED_PROTECT_OPTIONS {
   3441                                     for prf_uv in ALL_PRF_UV_OPTIONS {
   3442                                         for pin in ALL_PIN_OPTIONS {
   3443                                             if user_verified
   3444                                                 || (!matches!(
   3445                                                     protect,
   3446                                                     CredProtect::UserVerificationRequired(_, _)
   3447                                                 ) && matches!(prf_uv, PrfUvOptions::None(uv) if !uv))
   3448                                             {
   3449                                                 assert!(validate(TestOptions {
   3450                                                     request: TestRequestOptions {
   3451                                                         error_unsolicited: true,
   3452                                                         protect,
   3453                                                         prf_uv,
   3454                                                         props: None,
   3455                                                         pin,
   3456                                                     },
   3457                                                     response: TestResponseOptions {
   3458                                                         user_verified,
   3459                                                         cred_protect,
   3460                                                         prf,
   3461                                                         hmac,
   3462                                                         min_pin,
   3463                                                         cred_props,
   3464                                                     },
   3465                                                 }).is_err_and(|err| matches!(err, AggErr::RegCeremony(reg_err) if matches!(reg_err, RegCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::ForbiddenCredProps)))));
   3466                                             }
   3467                                         }
   3468                                     }
   3469                                 }
   3470                             }
   3471                         }
   3472                     }
   3473                 }
   3474             }
   3475         }
   3476     }
   3477     #[expect(clippy::panic_in_result_fn, reason = "OK in tests")]
   3478     #[test]
   3479     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   3480     fn prf() -> Result<(), AggErr> {
   3481         let mut opts = TestOptions {
   3482             request: TestRequestOptions {
   3483                 error_unsolicited: false,
   3484                 protect: CredProtect::None,
   3485                 prf_uv: PrfUvOptions::Prf(ExtensionInfo::RequireEnforceValue),
   3486                 props: None,
   3487                 pin: None,
   3488             },
   3489             response: TestResponseOptions {
   3490                 user_verified: true,
   3491                 hmac: HmacSecret::None,
   3492                 cred_protect: CredentialProtectionPolicy::None,
   3493                 prf: Some(true),
   3494                 min_pin: None,
   3495                 cred_props: None,
   3496             },
   3497         };
   3498         validate(opts)?;
   3499         opts.response.prf = Some(false);
   3500         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::InvalidPrfValue)))));
   3501         opts.response.hmac = HmacSecret::NotEnabled;
   3502         opts.response.prf = Some(true);
   3503         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::InvalidHmacSecretValue)))));
   3504         opts.request.prf_uv = PrfUvOptions::Prf(ExtensionInfo::AllowDontEnforceValue);
   3505         opts.response.hmac = HmacSecret::Enabled;
   3506         opts.response.prf = None;
   3507         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::HmacSecretWithoutPrf)))));
   3508         opts.response.hmac = HmacSecret::NotEnabled;
   3509         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::HmacSecretWithoutPrf)))));
   3510         opts.response.prf = Some(true);
   3511         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::PrfWithoutHmacSecret)))));
   3512         opts.response.prf = Some(false);
   3513         validate(opts)?;
   3514         opts.request.prf_uv = PrfUvOptions::None(false);
   3515         opts.response.user_verified = false;
   3516         opts.response.hmac = HmacSecret::Enabled;
   3517         opts.response.prf = Some(true);
   3518         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::HmacSecretWithoutUserVerified)))));
   3519         opts.response.prf = None;
   3520         opts.response.hmac = HmacSecret::None;
   3521         validate(opts)?;
   3522         Ok(())
   3523     }
   3524     #[expect(clippy::panic_in_result_fn, reason = "OK in tests")]
   3525     #[test]
   3526     #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))]
   3527     fn cred_protect() -> Result<(), AggErr> {
   3528         let mut opts = TestOptions {
   3529             request: TestRequestOptions {
   3530                 error_unsolicited: false,
   3531                 protect: CredProtect::UserVerificationRequired(
   3532                     false,
   3533                     ExtensionInfo::RequireEnforceValue,
   3534                 ),
   3535                 prf_uv: PrfUvOptions::None(false),
   3536                 props: None,
   3537                 pin: None,
   3538             },
   3539             response: TestResponseOptions {
   3540                 user_verified: true,
   3541                 hmac: HmacSecret::None,
   3542                 cred_protect: CredentialProtectionPolicy::UserVerificationRequired,
   3543                 prf: None,
   3544                 min_pin: None,
   3545                 cred_props: None,
   3546             },
   3547         };
   3548         validate(opts)?;
   3549         opts.response.cred_protect =
   3550             CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList;
   3551         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::InvalidCredProtectValue(CredProtect::UserVerificationRequired(false, ExtensionInfo::RequireEnforceValue), CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList))))));
   3552         opts.request.protect =
   3553             CredProtect::UserVerificationOptional(true, ExtensionInfo::RequireEnforceValue);
   3554         opts.response.user_verified = false;
   3555         opts.response.cred_protect = CredentialProtectionPolicy::UserVerificationRequired;
   3556         assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::RegCeremony(err) if matches!(err, RegCeremonyErr::Credential(cred_err) if matches!(cred_err, CredentialErr::CredProtectUserVerificationRequiredWithoutUserVerified)))));
   3557         Ok(())
   3558     }
   3559 }