webauthn_rp

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

register.rs (143125B)


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