webauthn_rp

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

request.rs (136870B)


      1 #[cfg(doc)]
      2 use super::{
      3     hash::hash_set::FixedCapHashSet,
      4     request::{
      5         auth::{
      6             AllowedCredential, AllowedCredentials, CredentialSpecificExtension,
      7             DiscoverableAuthenticationServerState, DiscoverableCredentialRequestOptions,
      8             NonDiscoverableAuthenticationServerState, NonDiscoverableCredentialRequestOptions,
      9             PublicKeyCredentialRequestOptions,
     10         },
     11         register::{CredentialCreationOptions, RegistrationServerState},
     12     },
     13     response::register::ClientExtensionsOutputs,
     14 };
     15 use crate::{
     16     request::{
     17         error::{
     18             AsciiDomainErr, DomainOriginParseErr, PortParseErr, RpIdErr, SchemeParseErr, UrlErr,
     19         },
     20         register::RegistrationVerificationOptions,
     21     },
     22     response::{
     23         AuthData as _, AuthDataContainer, AuthResponse, AuthTransports, Backup, CeremonyErr,
     24         CredentialId, Origin, Response, SentChallenge,
     25     },
     26 };
     27 use core::{
     28     borrow::Borrow,
     29     fmt::{self, Display, Formatter},
     30     num::NonZeroU32,
     31     str::FromStr,
     32 };
     33 use rsa::sha2::{Digest as _, Sha256};
     34 #[cfg(any(doc, not(feature = "serializable_server_state")))]
     35 use std::time::Instant;
     36 #[cfg(feature = "serializable_server_state")]
     37 use std::time::SystemTime;
     38 use url::Url as Uri;
     39 /// Contains functionality for beginning the
     40 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony).
     41 ///
     42 /// # Examples
     43 ///
     44 /// ```
     45 /// # use core::convert;
     46 /// # use webauthn_rp::{
     47 /// #     hash::hash_set::FixedCapHashSet,
     48 /// #     request::{
     49 /// #         auth::{AllowedCredentials, DiscoverableCredentialRequestOptions, NonDiscoverableCredentialRequestOptions},
     50 /// #         register::UserHandle64,
     51 /// #         Credentials, PublicKeyCredentialDescriptor, RpId,
     52 /// #     },
     53 /// #     response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN},
     54 /// #     AggErr,
     55 /// # };
     56 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap();
     57 /// let mut ceremonies = FixedCapHashSet::new(128);
     58 /// let (server, client) = DiscoverableCredentialRequestOptions::passkey(RP_ID).start_ceremony()?;
     59 /// assert!(
     60 ///     ceremonies.insert_remove_all_expired(server).map_or(false, convert::identity)
     61 /// );
     62 /// # #[cfg(feature = "custom")]
     63 /// let mut ceremonies_2 = FixedCapHashSet::new(128);
     64 /// # #[cfg(feature = "serde")]
     65 /// assert!(serde_json::to_string(&client).is_ok());
     66 /// let user_handle = get_user_handle();
     67 /// # #[cfg(feature = "custom")]
     68 /// let creds = get_registered_credentials(&user_handle)?;
     69 /// # #[cfg(feature = "custom")]
     70 /// let (server_2, client_2) =
     71 ///     NonDiscoverableCredentialRequestOptions::second_factor(RP_ID, creds).start_ceremony()?;
     72 /// # #[cfg(feature = "custom")]
     73 /// assert!(
     74 ///     ceremonies_2.insert_remove_all_expired(server_2).map_or(false, convert::identity)
     75 /// );
     76 /// # #[cfg(all(feature = "custom", feature = "serde"))]
     77 /// assert!(serde_json::to_string(&client_2).is_ok());
     78 /// /// Extract `UserHandle` from session cookie.
     79 /// fn get_user_handle() -> UserHandle64 {
     80 ///     // ⋮
     81 /// #     UserHandle64::new()
     82 /// }
     83 /// # #[cfg(feature = "custom")]
     84 /// /// Fetch the `AllowedCredentials` associated with `user`.
     85 /// fn get_registered_credentials(user: &UserHandle64) -> Result<AllowedCredentials, AggErr> {
     86 ///     // ⋮
     87 /// #     let mut creds = AllowedCredentials::new();
     88 /// #     creds.push(
     89 /// #         PublicKeyCredentialDescriptor {
     90 /// #             id: CredentialId::try_from(vec![0; CRED_ID_MIN_LEN])?,
     91 /// #             transports: AuthTransports::NONE,
     92 /// #         }
     93 /// #         .into(),
     94 /// #     );
     95 /// #     Ok(creds)
     96 /// }
     97 /// # Ok::<_, AggErr>(())
     98 /// ```
     99 pub mod auth;
    100 /// Contains error types.
    101 pub mod error;
    102 /// Contains functionality for beginning the
    103 /// [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony).
    104 ///
    105 /// # Examples
    106 ///
    107 /// ```
    108 /// # use core::convert;
    109 /// # use webauthn_rp::{
    110 /// #     hash::hash_set::FixedCapHashSet,
    111 /// #     request::{
    112 /// #         register::{
    113 /// #             CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64,
    114 /// #         },
    115 /// #         PublicKeyCredentialDescriptor, RpId
    116 /// #     },
    117 /// #     response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN},
    118 /// #     AggErr,
    119 /// # };
    120 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap();
    121 /// # #[cfg(feature = "custom")]
    122 /// let mut ceremonies = FixedCapHashSet::new(128);
    123 /// # #[cfg(feature = "custom")]
    124 /// let user_handle = get_user_handle();
    125 /// # #[cfg(feature = "custom")]
    126 /// let user = get_user_entity(&user_handle)?;
    127 /// # #[cfg(feature = "custom")]
    128 /// let creds = get_registered_credentials(&user_handle)?;
    129 /// # #[cfg(feature = "custom")]
    130 /// let (server, client) = CredentialCreationOptions::passkey(RP_ID, user.clone(), creds)
    131 ///     .start_ceremony()?;
    132 /// # #[cfg(feature = "custom")]
    133 /// assert!(
    134 ///     ceremonies.insert_remove_all_expired(server).map_or(false, convert::identity)
    135 /// );
    136 /// # #[cfg(all(feature = "serde", feature = "custom"))]
    137 /// assert!(serde_json::to_string(&client).is_ok());
    138 /// # #[cfg(feature = "custom")]
    139 /// let creds_2 = get_registered_credentials(&user_handle)?;
    140 /// # #[cfg(feature = "custom")]
    141 /// let (server_2, client_2) =
    142 ///     CredentialCreationOptions::second_factor(RP_ID, user, creds_2).start_ceremony()?;
    143 /// # #[cfg(feature = "custom")]
    144 /// assert!(
    145 ///     ceremonies.insert_remove_all_expired(server_2).map_or(false, convert::identity)
    146 /// );
    147 /// # #[cfg(all(feature = "serde", feature = "custom"))]
    148 /// assert!(serde_json::to_string(&client_2).is_ok());
    149 /// /// Extract `UserHandle` from session cookie or storage if this is not the first credential registered.
    150 /// # #[cfg(feature = "custom")]
    151 /// fn get_user_handle() -> UserHandle64 {
    152 ///     // ⋮
    153 /// #     [0; USER_HANDLE_MAX_LEN].into()
    154 /// }
    155 /// /// Fetch `PublicKeyCredentialUserEntity` info associated with `user`.
    156 /// ///
    157 /// /// If this is the first time a credential is being registered, then `PublicKeyCredentialUserEntity`
    158 /// /// will need to be constructed with `name` and `display_name` passed from the client and `UserHandle::new`
    159 /// /// used for `id`. Once created, this info can be stored such that the entity information
    160 /// /// does not need to be requested for subsequent registrations.
    161 /// # #[cfg(feature = "custom")]
    162 /// fn get_user_entity(user: &UserHandle64) -> Result<PublicKeyCredentialUserEntity<'_, '_, '_, USER_HANDLE_MAX_LEN>, AggErr> {
    163 ///     // ⋮
    164 /// #     Ok(PublicKeyCredentialUserEntity {
    165 /// #         name: "foo".try_into()?,
    166 /// #         id: user,
    167 /// #         display_name: None,
    168 /// #     })
    169 /// }
    170 /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`.
    171 /// ///
    172 /// /// This doesn't need to be called when this is the first credential registered for `user`; instead
    173 /// /// an empty `Vec` should be passed.
    174 /// fn get_registered_credentials(
    175 ///     user: &UserHandle64,
    176 /// ) -> Result<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>, AggErr> {
    177 ///     // ⋮
    178 /// #     Ok(Vec::new())
    179 /// }
    180 /// # Ok::<_, AggErr>(())
    181 /// ```
    182 pub mod register;
    183 /// Contains functionality to serialize data to a client.
    184 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
    185 #[cfg(feature = "serde")]
    186 mod ser;
    187 /// Contains functionality to (de)serialize data needed for [`RegistrationServerState`],
    188 /// [`DiscoverableAuthenticationServerState`], and [`NonDiscoverableAuthenticationServerState`] to a data store.
    189 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
    190 #[cfg(feature = "serializable_server_state")]
    191 pub(super) mod ser_server_state;
    192 // `Challenge` must _never_ be constructable directly or indirectly; thus its tuple field must always be private,
    193 // and it must never implement `trait`s (e.g., `Clone`) that would allow indirect creation. It must only ever
    194 // be constructed via `Self::new` or `Self::default`. In contrast downstream code must be able to construct
    195 // `SentChallenge` since it is used during ceremony validation; thus we must keep `Challenge` and `SentChallenge`
    196 // as separate types.
    197 /// [Cryptographic challenge](https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges).
    198 #[expect(
    199     missing_copy_implementations,
    200     reason = "want to enforce randomly-generated challenges"
    201 )]
    202 #[derive(Debug)]
    203 pub struct Challenge(u128);
    204 impl Challenge {
    205     /// The number of bytes a `Challenge` takes to encode in base64url.
    206     pub(super) const BASE64_LEN: usize = base64url_nopad::encode_len(16);
    207     /// Generates a random `Challenge`.
    208     ///
    209     /// # Examples
    210     ///
    211     /// ```
    212     /// # use webauthn_rp::request::Challenge;
    213     /// // The probability of a `Challenge` being 0 (assuming a good entropy
    214     /// // source) is 2^-128 ≈ 2.9 x 10^-39.
    215     /// assert_ne!(Challenge::new().into_data(), 0);
    216     /// ```
    217     #[inline]
    218     #[must_use]
    219     pub fn new() -> Self {
    220         Self(rand::random())
    221     }
    222     /// Returns the contained `u128` consuming `self`.
    223     #[inline]
    224     #[must_use]
    225     pub const fn into_data(self) -> u128 {
    226         self.0
    227     }
    228     /// Returns the contained `u128`.
    229     #[inline]
    230     #[must_use]
    231     pub const fn as_data(&self) -> u128 {
    232         self.0
    233     }
    234     /// Returns the contained `u128` as a little-endian `array` consuming `self`.
    235     #[inline]
    236     #[must_use]
    237     pub const fn into_array(self) -> [u8; 16] {
    238         self.as_array()
    239     }
    240     /// Returns the contained `u128` as a little-endian `array`.
    241     #[expect(
    242         clippy::little_endian_bytes,
    243         reason = "Challenge and SentChallenge need to be compatible, and we need to ensure the data is sent and received in the same order"
    244     )]
    245     #[inline]
    246     #[must_use]
    247     pub const fn as_array(&self) -> [u8; 16] {
    248         self.0.to_le_bytes()
    249     }
    250 }
    251 impl Default for Challenge {
    252     /// Same as [`Self::new`].
    253     #[inline]
    254     fn default() -> Self {
    255         Self::new()
    256     }
    257 }
    258 impl From<Challenge> for u128 {
    259     #[inline]
    260     fn from(value: Challenge) -> Self {
    261         value.0
    262     }
    263 }
    264 impl From<&Challenge> for u128 {
    265     #[inline]
    266     fn from(value: &Challenge) -> Self {
    267         value.0
    268     }
    269 }
    270 impl From<Challenge> for [u8; 16] {
    271     #[inline]
    272     fn from(value: Challenge) -> Self {
    273         value.into_array()
    274     }
    275 }
    276 impl From<&Challenge> for [u8; 16] {
    277     #[inline]
    278     fn from(value: &Challenge) -> Self {
    279         value.as_array()
    280     }
    281 }
    282 /// A [domain](https://url.spec.whatwg.org/#concept-domain) in representation format consisting of only and any
    283 /// ASCII.
    284 ///
    285 /// The only ASCII character disallowed in a label is `'.'` since it is used exclusively as a separator. Every
    286 /// label must have length inclusively between 1 and 63, and the total length of the domain must be at most 253
    287 /// when a trailing `'.'` does not exist; otherwise the max length is 254. The root domain (i.e., `'.'`) is not
    288 /// allowed.
    289 ///
    290 /// Note if the domain is a `&'static str`, then use [`AsciiDomainStatic`] instead.
    291 #[derive(Clone, Debug, Eq, PartialEq)]
    292 pub struct AsciiDomain(String);
    293 impl AsciiDomain {
    294     /// Removes a trailing `'.'` if it exists.
    295     ///
    296     /// # Examples
    297     ///
    298     /// ```
    299     /// # use webauthn_rp::request::{AsciiDomain, error::AsciiDomainErr};
    300     /// let mut dom = AsciiDomain::try_from("example.com.".to_owned())?;
    301     /// assert_eq!(dom.as_ref(), "example.com.");
    302     /// dom.remove_trailing_dot();
    303     /// assert_eq!(dom.as_ref(), "example.com");
    304     /// dom.remove_trailing_dot();
    305     /// assert_eq!(dom.as_ref(), "example.com");
    306     /// # Ok::<_, AsciiDomainErr>(())
    307     /// ```
    308     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
    309     #[inline]
    310     pub fn remove_trailing_dot(&mut self) {
    311         if *self
    312             .0
    313             .as_bytes()
    314             .last()
    315             .unwrap_or_else(|| unreachable!("there is a bug in AsciiDomain::from_slice"))
    316             == b'.'
    317         {
    318             _ = self.0.pop();
    319         }
    320     }
    321 }
    322 impl AsRef<str> for AsciiDomain {
    323     #[inline]
    324     fn as_ref(&self) -> &str {
    325         self.0.as_str()
    326     }
    327 }
    328 impl Borrow<str> for AsciiDomain {
    329     #[inline]
    330     fn borrow(&self) -> &str {
    331         self.0.as_str()
    332     }
    333 }
    334 impl From<AsciiDomain> for String {
    335     #[inline]
    336     fn from(value: AsciiDomain) -> Self {
    337         value.0
    338     }
    339 }
    340 impl PartialEq<&Self> for AsciiDomain {
    341     #[inline]
    342     fn eq(&self, other: &&Self) -> bool {
    343         *self == **other
    344     }
    345 }
    346 impl PartialEq<AsciiDomain> for &AsciiDomain {
    347     #[inline]
    348     fn eq(&self, other: &AsciiDomain) -> bool {
    349         **self == *other
    350     }
    351 }
    352 impl TryFrom<Vec<u8>> for AsciiDomain {
    353     type Error = AsciiDomainErr;
    354     /// Verifies `value` is an ASCII domain in representation format converting any uppercase ASCII into
    355     /// lowercase.
    356     ///
    357     /// Note it is _strongly_ encouraged for `value` to only contain letters, numbers, hyphens, and underscores;
    358     /// otherwise certain applications may consider it not a domain. If the original domain contains non-ASCII, then
    359     /// one must encode it in Punycode _before_ calling this function. Domains that have a trailing `'.'` will be
    360     /// considered differently than domains without it; thus one will likely want to trim it if it does exist
    361     /// (e.g., [`AsciiDomain::remove_trailing_dot`]). Because this allows any ASCII, one may want to ensure `value`
    362     /// is not an IP address.
    363     ///
    364     /// # Errors
    365     ///
    366     /// Errors iff `value` is not a valid ASCII domain.
    367     ///
    368     /// # Examples
    369     ///
    370     /// ```
    371     /// # use webauthn_rp::request::{error::AsciiDomainErr, AsciiDomain};
    372     /// // Root `'.'` is not removed if it exists.
    373     /// assert_ne!("example.com", AsciiDomain::try_from(b"example.com.".to_vec())?.as_ref());
    374     /// // Root domain (i.e., `'.'`) is not allowed.
    375     /// assert!(AsciiDomain::try_from(vec![b'.']).is_err());
    376     /// // Uppercase is transformed into lowercase.
    377     /// assert_eq!("example.com", AsciiDomain::try_from(b"ExAmPle.CoM".to_vec())?.as_ref());
    378     /// // The only ASCII character not allowed in a domain label is `'.'` as it is used exclusively to delimit
    379     /// // labels.
    380     /// assert_eq!("\x00", AsciiDomain::try_from(b"\x00".to_vec())?.as_ref());
    381     /// // Empty labels are not allowed.
    382     /// assert!(AsciiDomain::try_from(b"example..com".to_vec()).is_err());
    383     /// // Labels cannot have length greater than 63.
    384     /// let mut long_label = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned();
    385     /// assert_eq!(long_label.len(), 64);
    386     /// assert!(AsciiDomain::try_from(long_label.clone().into_bytes()).is_err());
    387     /// long_label.pop();
    388     /// assert_eq!(long_label, AsciiDomain::try_from(long_label.clone().into_bytes())?.as_ref());
    389     /// // The maximum length of a domain is 254 if a trailing `'.'` exists; otherwise the max length is 253.
    390     /// let mut long_domain = format!("{long_label}.{long_label}.{long_label}.{long_label}");
    391     /// long_domain.pop();
    392     /// long_domain.push('.');
    393     /// assert_eq!(long_domain.len(), 255);
    394     /// assert!(AsciiDomain::try_from(long_domain.clone().into_bytes()).is_err());
    395     /// long_domain.pop();
    396     /// long_domain.pop();
    397     /// long_domain.push('.');
    398     /// assert_eq!(long_domain.len(), 254);
    399     /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone().into_bytes())?.as_ref());
    400     /// long_domain.pop();
    401     /// long_domain.push('a');
    402     /// assert_eq!(long_domain.len(), 254);
    403     /// assert!(AsciiDomain::try_from(long_domain.clone().into_bytes()).is_err());
    404     /// long_domain.pop();
    405     /// assert_eq!(long_domain.len(), 253);
    406     /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone().into_bytes())?.as_ref());
    407     /// // Only ASCII is allowed; thus if a domain needs to be Punycode-encoded, then it must be _before_ calling
    408     /// // this function.
    409     /// assert!(AsciiDomain::try_from("λ.com".to_owned().into_bytes()).is_err());
    410     /// assert_eq!("xn--wxa.com", AsciiDomain::try_from(b"xn--wxa.com".to_vec())?.as_ref());
    411     /// # Ok::<_, AsciiDomainErr>(())
    412     /// ```
    413     #[expect(unsafe_code, reason = "comment justifies correctness")]
    414     #[expect(
    415         clippy::arithmetic_side_effects,
    416         reason = "comments justify correctness"
    417     )]
    418     #[inline]
    419     fn try_from(mut value: Vec<u8>) -> Result<Self, Self::Error> {
    420         /// Value to add to an uppercase ASCII `u8` to get the lowercase version.
    421         const DIFF: u8 = b'a' - b'A';
    422         let bytes = value.as_slice();
    423         bytes
    424             .as_ref()
    425             .last()
    426             .ok_or(AsciiDomainErr::Empty)
    427             .and_then(|b| {
    428                 let len = bytes.len();
    429                 if *b == b'.' {
    430                     if len == 1 {
    431                         Err(AsciiDomainErr::RootDomain)
    432                     } else if len > 254 {
    433                         Err(AsciiDomainErr::Len)
    434                     } else {
    435                         Ok(())
    436                     }
    437                 } else if len > 253 {
    438                     Err(AsciiDomainErr::Len)
    439                 } else {
    440                     Ok(())
    441                 }
    442             })
    443             .and_then(|()| {
    444                 value
    445                     .iter_mut()
    446                     .try_fold(0u8, |mut label_len, byt| {
    447                         let b = *byt;
    448                         if b == b'.' {
    449                             if label_len == 0 {
    450                                 Err(AsciiDomainErr::EmptyLabel)
    451                             } else {
    452                                 Ok(0)
    453                             }
    454                         } else if label_len == 63 {
    455                             Err(AsciiDomainErr::LabelLen)
    456                         } else {
    457                             // We know `label_len` is less than 63, thus this won't overflow.
    458                             label_len += 1;
    459                             match *byt {
    460                                 // Non-uppercase ASCII is allowed and doesn't need to be converted.
    461                                 ..b'A' | b'['..=0x7F => Ok(label_len),
    462                                 // Uppercase ASCII is allowed but needs to be transformed into lowercase.
    463                                 b'A'..=b'Z' => {
    464                                     // Lowercase ASCII is a contiguous block starting from `b'a'` as is uppercase
    465                                     // ASCII which starts from `b'A'` with uppercase ASCII coming before; thus we
    466                                     // simply need to shift by a fixed amount.
    467                                     *byt += DIFF;
    468                                     Ok(label_len)
    469                                 }
    470                                 // Non-ASCII is disallowed.
    471                                 0x80.. => Err(AsciiDomainErr::NotAscii),
    472                             }
    473                         }
    474                     })
    475                     .map(|_| {
    476                         // SAFETY:
    477                         // We just verified `value` only contains ASCII; thus this is safe.
    478                         let utf8 = unsafe { String::from_utf8_unchecked(value) };
    479                         Self(utf8)
    480                     })
    481             })
    482     }
    483 }
    484 impl TryFrom<String> for AsciiDomain {
    485     type Error = AsciiDomainErr;
    486     /// Same as [`Self::try_from`] except `value` is a `String`.
    487     #[inline]
    488     fn try_from(value: String) -> Result<Self, Self::Error> {
    489         Self::try_from(value.into_bytes())
    490     }
    491 }
    492 /// Similar to [`AsciiDomain`] except the contained data is a `&'static str`.
    493 ///
    494 /// Since [`Self::new`] and [`Option::unwrap`] are `const fn`s, one can define a global `const` or `static`
    495 /// variable that represents the RP ID.
    496 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    497 pub struct AsciiDomainStatic(&'static str);
    498 impl AsciiDomainStatic {
    499     /// Returns the contained `str`.
    500     #[inline]
    501     #[must_use]
    502     pub const fn as_str(self) -> &'static str {
    503         self.0
    504     }
    505     /// Verifies `domain` is a valid lowercase ASCII domain returning `None` when not valid or when
    506     /// uppercase ASCII exists.
    507     ///
    508     /// Read [`AsciiDomain`] for more information about what constitutes a valid domain.
    509     ///
    510     /// # Examples
    511     ///
    512     /// ```
    513     /// # use webauthn_rp::request::{AsciiDomainStatic, RpId};
    514     /// /// RP ID of our application.
    515     /// const RP_IP: &RpId = &RpId::StaticDomain(AsciiDomainStatic::new("example.com").unwrap());
    516     /// ```
    517     #[expect(
    518         clippy::arithmetic_side_effects,
    519         reason = "comment justifies correctness"
    520     )]
    521     #[expect(
    522         clippy::else_if_without_else,
    523         reason = "part of if branch and else branch are the same"
    524     )]
    525     #[inline]
    526     #[must_use]
    527     pub const fn new(domain: &'static str) -> Option<Self> {
    528         let mut utf8 = domain.as_bytes();
    529         if let Some(lst) = utf8.last() {
    530             let len = utf8.len();
    531             if *lst == b'.' {
    532                 if len == 1 || len > 254 {
    533                     return None;
    534                 }
    535             } else if len > 253 {
    536                 return None;
    537             }
    538             let mut label_len = 0;
    539             while let [first, ref rest @ ..] = *utf8 {
    540                 if first == b'.' {
    541                     if label_len == 0 {
    542                         return None;
    543                     }
    544                     label_len = 0;
    545                 } else if label_len == 63 {
    546                     return None;
    547                 } else {
    548                     match first {
    549                         // Any non-uppercase ASCII is allowed.
    550                         // We know `label_len` is less than 63, so this won't overflow.
    551                         ..b'A' | b'['..=0x7F => label_len += 1,
    552                         // Uppercase ASCII and non-ASCII are disallowed.
    553                         b'A'..=b'Z' | 0x80.. => return None,
    554                     }
    555                 }
    556                 utf8 = rest;
    557             }
    558             Some(Self(domain))
    559         } else {
    560             None
    561         }
    562     }
    563 }
    564 impl AsRef<str> for AsciiDomainStatic {
    565     #[inline]
    566     fn as_ref(&self) -> &str {
    567         self.as_str()
    568     }
    569 }
    570 impl Borrow<str> for AsciiDomainStatic {
    571     #[inline]
    572     fn borrow(&self) -> &str {
    573         self.as_str()
    574     }
    575 }
    576 impl From<AsciiDomainStatic> for &'static str {
    577     #[inline]
    578     fn from(value: AsciiDomainStatic) -> Self {
    579         value.0
    580     }
    581 }
    582 impl From<AsciiDomainStatic> for String {
    583     #[inline]
    584     fn from(value: AsciiDomainStatic) -> Self {
    585         value.0.to_owned()
    586     }
    587 }
    588 impl From<AsciiDomainStatic> for AsciiDomain {
    589     #[inline]
    590     fn from(value: AsciiDomainStatic) -> Self {
    591         Self(value.0.to_owned())
    592     }
    593 }
    594 impl PartialEq<&Self> for AsciiDomainStatic {
    595     #[inline]
    596     fn eq(&self, other: &&Self) -> bool {
    597         *self == **other
    598     }
    599 }
    600 impl PartialEq<AsciiDomainStatic> for &AsciiDomainStatic {
    601     #[inline]
    602     fn eq(&self, other: &AsciiDomainStatic) -> bool {
    603         **self == *other
    604     }
    605 }
    606 /// The output of the [URL serializer](https://url.spec.whatwg.org/#concept-url-serializer).
    607 ///
    608 /// The returned URL must consist of a [scheme](https://url.spec.whatwg.org/#concept-url-scheme) and
    609 /// optional [path](https://url.spec.whatwg.org/#url-path) but nothing else.
    610 #[derive(Clone, Debug, Eq, PartialEq)]
    611 pub struct Url(String);
    612 impl AsRef<str> for Url {
    613     #[inline]
    614     fn as_ref(&self) -> &str {
    615         self.0.as_str()
    616     }
    617 }
    618 impl Borrow<str> for Url {
    619     #[inline]
    620     fn borrow(&self) -> &str {
    621         self.0.as_str()
    622     }
    623 }
    624 impl From<Url> for String {
    625     #[inline]
    626     fn from(value: Url) -> Self {
    627         value.0
    628     }
    629 }
    630 impl PartialEq<&Self> for Url {
    631     #[inline]
    632     fn eq(&self, other: &&Self) -> bool {
    633         *self == **other
    634     }
    635 }
    636 impl PartialEq<Url> for &Url {
    637     #[inline]
    638     fn eq(&self, other: &Url) -> bool {
    639         **self == *other
    640     }
    641 }
    642 impl FromStr for Url {
    643     type Err = UrlErr;
    644     #[inline]
    645     fn from_str(s: &str) -> Result<Self, Self::Err> {
    646         Uri::from_str(s).map_err(|_e| UrlErr).and_then(|url| {
    647             if url.scheme().is_empty()
    648                 || url.has_host()
    649                 || url.query().is_some()
    650                 || url.fragment().is_some()
    651             {
    652                 Err(UrlErr)
    653             } else {
    654                 Ok(Self(url.into()))
    655             }
    656         })
    657     }
    658 }
    659 /// [RP ID](https://w3c.github.io/webauthn/#rp-id).
    660 #[derive(Clone, Debug, Eq, PartialEq)]
    661 pub enum RpId {
    662     /// An ASCII domain.
    663     ///
    664     /// Note web platforms MUST use this variant; and if possible, non-web platforms should too. Also despite
    665     /// the spec currently requiring RP IDs to be
    666     /// [valid domain strings](https://url.spec.whatwg.org/#valid-domain-string), this is unnecessarily strict
    667     /// and will likely be relaxed in a [future version](https://github.com/w3c/webauthn/issues/2206); thus
    668     /// any ASCII domain is allowed.
    669     Domain(AsciiDomain),
    670     /// Similar to [`Self::Domain`] except the ASCII domain is static.
    671     ///
    672     /// Since [`AsciiDomainStatic::new`] is a `const fn`, one can define a `const` or `static` global variable
    673     /// the contains the RP ID.
    674     StaticDomain(AsciiDomainStatic),
    675     /// A URL with only scheme and path.
    676     Url(Url),
    677 }
    678 impl RpId {
    679     /// Returns `Some` containing an [`AsciiDomainStatic`] iff [`AsciiDomainStatic::new`] does.
    680     #[inline]
    681     #[must_use]
    682     pub const fn from_static_domain(domain: &'static str) -> Option<Self> {
    683         if let Some(dom) = AsciiDomainStatic::new(domain) {
    684             Some(Self::StaticDomain(dom))
    685         } else {
    686             None
    687         }
    688     }
    689     /// Validates `hash` is the same as the SHA-256 hash of `self`.
    690     fn validate_rp_id_hash<E>(&self, hash: &[u8]) -> Result<(), CeremonyErr<E>> {
    691         if hash == Sha256::digest(self.as_ref()).as_slice() {
    692             Ok(())
    693         } else {
    694             Err(CeremonyErr::RpIdHashMismatch)
    695         }
    696     }
    697 }
    698 impl AsRef<str> for RpId {
    699     #[inline]
    700     fn as_ref(&self) -> &str {
    701         match *self {
    702             Self::Domain(ref dom) => dom.as_ref(),
    703             Self::StaticDomain(dom) => dom.as_str(),
    704             Self::Url(ref url) => url.as_ref(),
    705         }
    706     }
    707 }
    708 impl Borrow<str> for RpId {
    709     #[inline]
    710     fn borrow(&self) -> &str {
    711         match *self {
    712             Self::Domain(ref dom) => dom.borrow(),
    713             Self::StaticDomain(dom) => dom.as_str(),
    714             Self::Url(ref url) => url.borrow(),
    715         }
    716     }
    717 }
    718 impl From<RpId> for String {
    719     #[inline]
    720     fn from(value: RpId) -> Self {
    721         match value {
    722             RpId::Domain(dom) => dom.into(),
    723             RpId::StaticDomain(dom) => dom.into(),
    724             RpId::Url(url) => url.into(),
    725         }
    726     }
    727 }
    728 impl PartialEq<&Self> for RpId {
    729     #[inline]
    730     fn eq(&self, other: &&Self) -> bool {
    731         *self == **other
    732     }
    733 }
    734 impl PartialEq<RpId> for &RpId {
    735     #[inline]
    736     fn eq(&self, other: &RpId) -> bool {
    737         **self == *other
    738     }
    739 }
    740 impl From<AsciiDomain> for RpId {
    741     #[inline]
    742     fn from(value: AsciiDomain) -> Self {
    743         Self::Domain(value)
    744     }
    745 }
    746 impl From<AsciiDomainStatic> for RpId {
    747     #[inline]
    748     fn from(value: AsciiDomainStatic) -> Self {
    749         Self::StaticDomain(value)
    750     }
    751 }
    752 impl From<Url> for RpId {
    753     #[inline]
    754     fn from(value: Url) -> Self {
    755         Self::Url(value)
    756     }
    757 }
    758 impl TryFrom<String> for RpId {
    759     type Error = RpIdErr;
    760     /// Returns `Ok` iff `value` is a valid [`Url`] or [`AsciiDomain`].
    761     ///
    762     /// Note when `value` is a valid `Url` and `AsciiDomain`, it will be treated as a `Url`.
    763     #[inline]
    764     fn try_from(value: String) -> Result<Self, Self::Error> {
    765         Url::from_str(value.as_str())
    766             .map(Self::Url)
    767             .or_else(|_err| {
    768                 AsciiDomain::try_from(value)
    769                     .map(Self::Domain)
    770                     .map_err(|_e| RpIdErr)
    771             })
    772     }
    773 }
    774 /// A URI scheme. This can be used to make
    775 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient.
    776 #[derive(Clone, Copy, Debug, Default)]
    777 pub enum Scheme<'a> {
    778     /// A scheme must not exist when validating the origin.
    779     None,
    780     /// Any scheme, or no scheme at all, is allowed to exist when validating the origin.
    781     Any,
    782     /// The HTTPS scheme must exist when validating the origin.
    783     #[default]
    784     Https,
    785     /// The SSH scheme must exist when validating the origin.
    786     Ssh,
    787     /// The contained `str` scheme must exist when validating the origin.
    788     Other(&'a str),
    789     /// [`Self::None`] or [`Self::Https`].
    790     NoneHttps,
    791     /// [`Self::None`] or [`Self::Ssh`].
    792     NoneSsh,
    793     /// [`Self::None`] or [`Self::Other`].
    794     NoneOther(&'a str),
    795 }
    796 impl Scheme<'_> {
    797     /// `self` is any `Scheme`; however `other` is assumed to only be a `Scheme` from a `DomainOrigin` returned
    798     /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Scheme::None`, `Scheme::Https`,
    799     /// `Scheme::Ssh`, or `Scheme::Other`; furthermore when `Scheme::Other`, it won't contain a `str` that is
    800     /// empty or equal to "https" or "ssh".
    801     #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")]
    802     fn is_equal_to_origin_scheme(self, other: Self) -> bool {
    803         match self {
    804             Self::None => matches!(other, Self::None),
    805             Self::Any => true,
    806             Self::Https => matches!(other, Self::Https),
    807             Self::Ssh => matches!(other, Self::Ssh),
    808             Self::Other(scheme) => match other {
    809                 Self::None => false,
    810                 // We want to crash and burn since there is a bug in code.
    811                 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => {
    812                     unreachable!("there is a bug in DomainOrigin::try_from")
    813                 }
    814                 Self::Https => scheme == "https",
    815                 Self::Ssh => scheme == "ssh",
    816                 Self::Other(scheme_other) => scheme == scheme_other,
    817             },
    818             Self::NoneHttps => match other {
    819                 Self::None | Self::Https => true,
    820                 Self::Ssh | Self::Other(_) => false,
    821                 // We want to crash and burn since there is a bug in code.
    822                 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => {
    823                     unreachable!("there is a bug in DomainOrigin::try_from")
    824                 }
    825             },
    826             Self::NoneSsh => match other {
    827                 Self::None | Self::Ssh => true,
    828                 // We want to crash and burn since there is a bug in code.
    829                 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => {
    830                     unreachable!("there is a bug in DomainOrigin::try_from")
    831                 }
    832                 Self::Https | Self::Other(_) => false,
    833             },
    834             Self::NoneOther(scheme) => match other {
    835                 Self::None => true,
    836                 // We want to crash and burn since there is a bug in code.
    837                 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => {
    838                     unreachable!("there is a bug in DomainOrigin::try_from")
    839                 }
    840                 Self::Https => scheme == "https",
    841                 Self::Ssh => scheme == "ssh",
    842                 Self::Other(scheme_other) => scheme == scheme_other,
    843             },
    844         }
    845     }
    846 }
    847 impl<'a: 'b, 'b> TryFrom<&'a str> for Scheme<'b> {
    848     type Error = SchemeParseErr;
    849     /// `"https"` and `"ssh"` get mapped to [`Self::Https`] and [`Self::Ssh`] respectively. All other
    850     /// values get mapped to [`Self::Other`].
    851     ///
    852     /// # Errors
    853     ///
    854     /// Errors iff `s` is empty.
    855     ///
    856     /// # Examples
    857     ///
    858     /// ```
    859     /// # use webauthn_rp::request::Scheme;
    860     /// assert!(matches!(Scheme::try_from("https")?, Scheme::Https));
    861     /// assert!(matches!(Scheme::try_from("https ")?, Scheme::Other(scheme) if scheme == "https "));
    862     /// assert!(matches!(Scheme::try_from("ssh")?, Scheme::Ssh));
    863     /// assert!(matches!(Scheme::try_from("Ssh")?, Scheme::Other(scheme) if scheme == "Ssh"));
    864     /// // Even though one can construct an empty `Scheme` via `Scheme::Other` or `Scheme::NoneOther`,
    865     /// // one cannot parse one.
    866     /// assert!(Scheme::try_from("").is_err());
    867     /// # Ok::<_, webauthn_rp::AggErr>(())
    868     /// ```
    869     #[inline]
    870     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
    871         match value {
    872             "" => Err(SchemeParseErr),
    873             "https" => Ok(Self::Https),
    874             "ssh" => Ok(Self::Ssh),
    875             _ => Ok(Self::Other(value)),
    876         }
    877     }
    878 }
    879 /// A TCP/UDP port. This can be used to make
    880 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient.
    881 #[derive(Clone, Copy, Debug, Default)]
    882 pub enum Port {
    883     /// A port must not exist when validating the origin.
    884     #[default]
    885     None,
    886     /// Any port, or no port at all, is allowed to exist when validating the origin.
    887     Any,
    888     /// The contained `u16` port must exist when validating the origin.
    889     Val(u16),
    890     /// [`Self::None`] or [`Self::Val`].
    891     NoneVal(u16),
    892 }
    893 impl Port {
    894     /// `self` is any `Port`; however `other` is assumed to only be a `Port` from a `DomainOrigin` returned
    895     /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Port::None` or `Port::Val`.
    896     #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")]
    897     fn is_equal_to_origin_port(self, other: Self) -> bool {
    898         match self {
    899             Self::None => matches!(other, Self::None),
    900             Self::Any => true,
    901             Self::Val(port) => match other {
    902                 Self::None => false,
    903                 // There is a bug in code so we want to crash and burn.
    904                 Self::Any | Self::NoneVal(_) => {
    905                     unreachable!("there is a bug in DomainOrigin::try_from")
    906                 }
    907                 Self::Val(port_other) => port == port_other,
    908             },
    909             Self::NoneVal(port) => match other {
    910                 Self::None => true,
    911                 // There is a bug in code so we want to crash and burn.
    912                 Self::Any | Self::NoneVal(_) => {
    913                     unreachable!("there is a bug in DomainOrigin::try_from")
    914                 }
    915                 Self::Val(port_other) => port == port_other,
    916             },
    917         }
    918     }
    919 }
    920 impl FromStr for Port {
    921     type Err = PortParseErr;
    922     /// Parses `s` as a 16-bit unsigned integer without leading 0s returning [`Self::Val`] with the contained
    923     /// `u16`.
    924     ///
    925     /// # Errors
    926     ///
    927     /// Errors iff `s` is not a valid 16-bit unsigned integer in decimal notation without leading 0s.
    928     ///
    929     /// # Examples
    930     ///
    931     /// ```
    932     /// # use webauthn_rp::request::{error::PortParseErr, Port};
    933     /// assert!(matches!("443".parse()?, Port::Val(443)));
    934     /// // TCP/UDP ports have to be in canonical form:
    935     /// assert!("022"
    936     ///     .parse::<Port>()
    937     ///     .map_or_else(|err| matches!(err, PortParseErr::NotCanonical), |_| false));
    938     /// # Ok::<_, webauthn_rp::AggErr>(())
    939     /// ```
    940     #[inline]
    941     fn from_str(s: &str) -> Result<Self, Self::Err> {
    942         s.parse().map_err(PortParseErr::ParseInt).and_then(|port| {
    943             if s.len()
    944                 == match port {
    945                     ..=9 => 1,
    946                     10..=99 => 2,
    947                     100..=999 => 3,
    948                     1_000..=9_999 => 4,
    949                     10_000.. => 5,
    950                 }
    951             {
    952                 Ok(Self::Val(port))
    953             } else {
    954                 Err(PortParseErr::NotCanonical)
    955             }
    956         })
    957     }
    958 }
    959 /// A [`tuple origin`](https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-tuple).
    960 ///
    961 /// This can be used to make [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin)
    962 /// more convenient.
    963 #[derive(Clone, Copy, Debug)]
    964 pub struct DomainOrigin<'a, 'b> {
    965     /// The scheme.
    966     pub scheme: Scheme<'a>,
    967     /// The host.
    968     pub host: &'b str,
    969     /// The TCP/UDP port.
    970     pub port: Port,
    971 }
    972 impl<'b> DomainOrigin<'_, 'b> {
    973     /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and
    974     /// [`Self::port`] as [`Port::None`].
    975     ///
    976     /// # Examples
    977     ///
    978     /// ```
    979     /// # extern crate alloc;
    980     /// # use alloc::borrow::Cow;
    981     /// # use webauthn_rp::{request::DomainOrigin, response::Origin};
    982     /// assert_eq!(
    983     ///     DomainOrigin::new("www.example.com"),
    984     ///     Origin(Cow::Borrowed("https://www.example.com"))
    985     /// );
    986     /// // `DomainOrigin::new` does not allow _any_ port to exist.
    987     /// assert_ne!(
    988     ///     DomainOrigin::new("www.example.com"),
    989     ///     Origin(Cow::Borrowed("https://www.example.com:443"))
    990     /// );
    991     /// ```
    992     #[expect(single_use_lifetimes, reason = "false positive")]
    993     #[must_use]
    994     #[inline]
    995     pub const fn new<'c: 'b>(host: &'c str) -> Self {
    996         Self {
    997             scheme: Scheme::Https,
    998             host,
    999             port: Port::None,
   1000         }
   1001     }
   1002     /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and
   1003     /// [`Self::port`] as [`Port::Any`].
   1004     ///
   1005     /// # Examples
   1006     ///
   1007     /// ```
   1008     /// # extern crate alloc;
   1009     /// # use alloc::borrow::Cow;
   1010     /// # use webauthn_rp::{request::DomainOrigin, response::Origin};
   1011     /// // Any port is allowed to exist.
   1012     /// assert_eq!(
   1013     ///     DomainOrigin::new_ignore_port("www.example.com"),
   1014     ///     Origin(Cow::Borrowed("https://www.example.com:1234"))
   1015     /// );
   1016     /// // A port doesn't have to exist at all either.
   1017     /// assert_eq!(
   1018     ///     DomainOrigin::new_ignore_port("www.example.com"),
   1019     ///     Origin(Cow::Borrowed("https://www.example.com"))
   1020     /// );
   1021     /// ```
   1022     #[expect(single_use_lifetimes, reason = "false positive")]
   1023     #[must_use]
   1024     #[inline]
   1025     pub const fn new_ignore_port<'c: 'b>(host: &'c str) -> Self {
   1026         Self {
   1027             scheme: Scheme::Https,
   1028             host,
   1029             port: Port::Any,
   1030         }
   1031     }
   1032 }
   1033 impl PartialEq<Origin<'_>> for DomainOrigin<'_, '_> {
   1034     /// Returns `true` iff [`DomainOrigin::scheme`], [`DomainOrigin::host`], and [`DomainOrigin::port`] are the
   1035     /// same after calling [`DomainOrigin::try_from`] on `other.0.as_str()`.
   1036     ///
   1037     /// Note that [`Scheme`] and [`Port`] need not be the same variant. For example [`Scheme::Https`] and
   1038     /// [`Scheme::Other`] containing `"https"` will be treated the same.
   1039     #[inline]
   1040     fn eq(&self, other: &Origin<'_>) -> bool {
   1041         DomainOrigin::try_from(other.0.as_ref()).is_ok_and(|dom| {
   1042             self.scheme.is_equal_to_origin_scheme(dom.scheme)
   1043                 && self.host == dom.host
   1044                 && self.port.is_equal_to_origin_port(dom.port)
   1045         })
   1046     }
   1047 }
   1048 impl PartialEq<Origin<'_>> for &DomainOrigin<'_, '_> {
   1049     #[inline]
   1050     fn eq(&self, other: &Origin<'_>) -> bool {
   1051         **self == *other
   1052     }
   1053 }
   1054 impl PartialEq<&Origin<'_>> for DomainOrigin<'_, '_> {
   1055     #[inline]
   1056     fn eq(&self, other: &&Origin<'_>) -> bool {
   1057         *self == **other
   1058     }
   1059 }
   1060 impl PartialEq<DomainOrigin<'_, '_>> for Origin<'_> {
   1061     #[inline]
   1062     fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool {
   1063         *other == *self
   1064     }
   1065 }
   1066 impl PartialEq<DomainOrigin<'_, '_>> for &Origin<'_> {
   1067     #[inline]
   1068     fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool {
   1069         *other == **self
   1070     }
   1071 }
   1072 impl PartialEq<&DomainOrigin<'_, '_>> for Origin<'_> {
   1073     #[inline]
   1074     fn eq(&self, other: &&DomainOrigin<'_, '_>) -> bool {
   1075         **other == *self
   1076     }
   1077 }
   1078 impl<'a: 'b + 'c, 'b, 'c> TryFrom<&'a str> for DomainOrigin<'b, 'c> {
   1079     type Error = DomainOriginParseErr;
   1080     /// `value` is parsed according to the following extended regex:
   1081     ///
   1082     /// `^([^:]*:\/\/)?[^:]*(:.*)?$`
   1083     ///
   1084     /// where the `[^:]*` of the first capturing group is parsed according to [`Scheme::try_from`], and
   1085     /// the `.*` of the second capturing group is parsed according to [`Port::from_str`].
   1086     ///
   1087     /// # Errors
   1088     ///
   1089     /// Errors iff `Scheme::try_from` or `Port::from_str` fail when applicable.
   1090     ///
   1091     /// # Examples
   1092     ///
   1093     /// ```
   1094     /// # use webauthn_rp::request::{DomainOrigin, Port, Scheme};
   1095     /// assert!(
   1096     ///     DomainOrigin::try_from("https://www.example.com:443").map_or(false, |dom| matches!(
   1097     ///         dom.scheme,
   1098     ///         Scheme::Https
   1099     ///     ) && dom.host
   1100     ///         == "www.example.com"
   1101     ///         && matches!(dom.port, Port::Val(port) if port == 443))
   1102     /// );
   1103     /// // Parsing is done in a case sensitive way.
   1104     /// assert!(DomainOrigin::try_from("Https://www.EXample.com").map_or(
   1105     ///     false,
   1106     ///     |dom| matches!(dom.scheme, Scheme::Other(scheme) if scheme == "Https")
   1107     ///         && dom.host == "www.EXample.com"
   1108     ///         && matches!(dom.port, Port::None)
   1109     /// ));
   1110     /// ```
   1111     #[inline]
   1112     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
   1113         // Any string that contains `':'` is not a [valid domain](https://url.spec.whatwg.org/#valid-domain), and
   1114         // and `"//"` never exists in a `Port`; thus if `"://"` exists, it's either invalid or delimits the scheme
   1115         // from the rest of the origin.
   1116         match value.split_once("://") {
   1117             None => Ok((Scheme::None, value)),
   1118             Some((poss_scheme, rem)) => Scheme::try_from(poss_scheme)
   1119                 .map_err(DomainOriginParseErr::Scheme)
   1120                 .map(|scheme| (scheme, rem)),
   1121         }
   1122         .and_then(|(scheme, rem)| {
   1123             // `':'` never exists in a valid domain; thus if it exists, it's either invalid or
   1124             // separates the domain from the port.
   1125             rem.split_once(':')
   1126                 .map_or_else(
   1127                     || Ok((rem, Port::None)),
   1128                     |(rem2, poss_port)| {
   1129                         Port::from_str(poss_port)
   1130                             .map_err(DomainOriginParseErr::Port)
   1131                             .map(|port| (rem2, port))
   1132                     },
   1133                 )
   1134                 .map(|(host, port)| Self { scheme, host, port })
   1135         })
   1136     }
   1137 }
   1138 /// [`PublicKeyCredentialDescriptor`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptor)
   1139 /// associated with a registered credential.
   1140 #[derive(Clone, Debug)]
   1141 pub struct PublicKeyCredentialDescriptor<T> {
   1142     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-id).
   1143     pub id: CredentialId<T>,
   1144     /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports).
   1145     pub transports: AuthTransports,
   1146 }
   1147 /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement).
   1148 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1149 pub enum UserVerificationRequirement {
   1150     /// [`required`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-required).
   1151     Required,
   1152     /// [`discouraged`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-discouraged).
   1153     ///
   1154     /// Note some authenticators always require user verification when registering a credential (e.g.,
   1155     /// [CTAP 2.0](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html)
   1156     /// authenticators that have had a PIN enabled).
   1157     Discouraged,
   1158     /// [`preferred`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-preferred).
   1159     Preferred,
   1160 }
   1161 /// [`PublicKeyCredentialHints`](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint).
   1162 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1163 pub enum Hint {
   1164     /// No hints.
   1165     #[default]
   1166     None,
   1167     /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-security-key).
   1168     SecurityKey,
   1169     /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-client-device).
   1170     ClientDevice,
   1171     /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-hybrid).
   1172     Hybrid,
   1173     /// [`Self::SecurityKey`] and [`Self::ClientDevice`].
   1174     SecurityKeyClientDevice,
   1175     /// [`Self::ClientDevice`] and [`Self::SecurityKey`].
   1176     ClientDeviceSecurityKey,
   1177     /// [`Self::SecurityKey`] and [`Self::Hybrid`].
   1178     SecurityKeyHybrid,
   1179     /// [`Self::Hybrid`] and [`Self::SecurityKey`].
   1180     HybridSecurityKey,
   1181     /// [`Self::ClientDevice`] and [`Self::Hybrid`].
   1182     ClientDeviceHybrid,
   1183     /// [`Self::Hybrid`] and [`Self::ClientDevice`].
   1184     HybridClientDevice,
   1185     /// [`Self::SecurityKeyClientDevice`] and [`Self::Hybrid`].
   1186     SecurityKeyClientDeviceHybrid,
   1187     /// [`Self::SecurityKeyHybrid`] and [`Self::ClientDevice`].
   1188     SecurityKeyHybridClientDevice,
   1189     /// [`Self::ClientDeviceSecurityKey`] and [`Self::Hybrid`].
   1190     ClientDeviceSecurityKeyHybrid,
   1191     /// [`Self::ClientDeviceHybrid`] and [`Self::SecurityKey`].
   1192     ClientDeviceHybridSecurityKey,
   1193     /// [`Self::HybridSecurityKey`] and [`Self::ClientDevice`].
   1194     HybridSecurityKeyClientDevice,
   1195     /// [`Self::HybridClientDevice`] and [`Self::SecurityKey`].
   1196     HybridClientDeviceSecurityKey,
   1197 }
   1198 /// Controls if the response to a requested extension is required to be sent back.
   1199 ///
   1200 /// Note when requiring an extension, the extension must not only be sent back but also
   1201 /// contain at least one expected field (e.g., [`ClientExtensionsOutputs::cred_props`] must be
   1202 /// `Some(CredentialPropertiesOutput { rk: Some(_) })`.
   1203 ///
   1204 /// If one wants to additionally control the values of an extension, use [`ExtensionInfo`].
   1205 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1206 pub enum ExtensionReq {
   1207     /// The response to a requested extension is required to be sent back.
   1208     Require,
   1209     /// The response to a requested extension is allowed, but not required, to be sent back.
   1210     Allow,
   1211 }
   1212 /// Dictates how an extension should be processed.
   1213 ///
   1214 /// If one wants to only control if the extension should be returned, use [`ExtensionReq`].
   1215 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1216 pub enum ExtensionInfo {
   1217     /// Require the associated extension and enforce its value.
   1218     RequireEnforceValue,
   1219     /// Require the associated extension but don't enforce its value.
   1220     RequireDontEnforceValue,
   1221     /// Allow the associated extension to exist and enforce its value when it does exist.
   1222     AllowEnforceValue,
   1223     /// Allow the associated extension to exist but don't enforce its value.
   1224     AllowDontEnforceValue,
   1225 }
   1226 impl Display for ExtensionInfo {
   1227     #[inline]
   1228     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1229         f.write_str(match *self {
   1230             Self::RequireEnforceValue => "require the corresponding extension response and enforce its value",
   1231             Self::RequireDontEnforceValue => "require the corresponding extension response but don't enforce its value",
   1232             Self::AllowEnforceValue => "don't require the corresponding extension response; but if sent, enforce its value",
   1233             Self::AllowDontEnforceValue => "don't require the corresponding extension response; and if sent, don't enforce its value",
   1234         })
   1235     }
   1236 }
   1237 /// [`CredentialMediationRequirement`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement).
   1238 ///
   1239 /// Note [`silent`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-silent)
   1240 /// is not supported for WebAuthn credentials, and
   1241 /// [`optional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-optional)
   1242 /// is just an alias for [`Self::Required`].
   1243 #[expect(clippy::doc_markdown, reason = "false positive")]
   1244 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1245 pub enum CredentialMediationRequirement {
   1246     /// [`required`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-required).
   1247     ///
   1248     /// This is the default mediation for ceremonies.
   1249     #[default]
   1250     Required,
   1251     /// [`conditional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-conditional).
   1252     ///
   1253     /// Note that when registering a new credential with [`CredentialCreationOptions::mediation`] set to
   1254     /// `Self::Conditional`, [`UserVerificationRequirement::Required`] MUST NOT be used unless user verification
   1255     /// can be explicitly performed during the ceremony.
   1256     Conditional,
   1257 }
   1258 /// Backup requirements for the credential.
   1259 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
   1260 pub enum BackupReq {
   1261     /// No requirements (i.e., any [`Backup`] is allowed).
   1262     #[default]
   1263     None,
   1264     /// Credential must not be eligible for backup.
   1265     NotEligible,
   1266     /// Credential must be eligible for backup.
   1267     ///
   1268     /// Note the existence of a backup is ignored. If a backup must exist, then use [`Self::Exists`]; if a
   1269     /// backup must not exist, then use [`Self::EligibleNotExists`].
   1270     Eligible,
   1271     /// Credential must be eligible for backup, but a backup must not exist.
   1272     EligibleNotExists,
   1273     /// Credential must be backed up.
   1274     Exists,
   1275 }
   1276 impl From<Backup> for BackupReq {
   1277     /// One may want to create `BackupReq` based on the previous `Backup` such that the subsequent `Backup` is
   1278     /// essentially unchanged.
   1279     ///
   1280     /// Specifically this transforms [`Backup::NotEligible`] to [`Self::NotEligible`] and [`Backup::Eligible`] and
   1281     /// [`Backup::Exists`] to [`Self::Eligible`]. Note this means that a credential that
   1282     /// is eligible to be backed up but currently does not have a backup will be allowed to change such that it
   1283     /// is backed up. Similarly, a credential that is backed up is allowed to change such that a backup no longer
   1284     /// exists.
   1285     #[inline]
   1286     fn from(value: Backup) -> Self {
   1287         if matches!(value, Backup::NotEligible) {
   1288             Self::NotEligible
   1289         } else {
   1290             Self::Eligible
   1291         }
   1292     }
   1293 }
   1294 /// A container of "credentials".
   1295 ///
   1296 /// This is mainly a way to unify [`Vec`] of [`PublicKeyCredentialDescriptor`]
   1297 /// and [`AllowedCredentials`]. This can be useful in situations when one only
   1298 /// deals with [`AllowedCredential`]s with empty [`CredentialSpecificExtension`]s
   1299 /// essentially making them the same as [`PublicKeyCredentialDescriptor`]s.
   1300 ///
   1301 /// # Examples
   1302 ///
   1303 /// ```
   1304 /// # use webauthn_rp::{
   1305 /// #     request::{
   1306 /// #         auth::AllowedCredentials, register::UserHandle, Credentials, PublicKeyCredentialDescriptor,
   1307 /// #     },
   1308 /// #     response::{AuthTransports, CredentialId},
   1309 /// # };
   1310 /// /// Fetches all credentials under `user_handle` to be allowed during authentication for non-discoverable
   1311 /// /// requests.
   1312 /// # #[cfg(feature = "custom")]
   1313 /// fn get_allowed_credentials<const LEN: usize>(user_handle: &UserHandle<LEN>) -> AllowedCredentials {
   1314 ///     get_credentials(user_handle)
   1315 /// }
   1316 /// /// Fetches all credentials under `user_handle` to be excluded during registration.
   1317 /// # #[cfg(feature = "custom")]
   1318 /// fn get_excluded_credentials<const LEN: usize>(
   1319 ///     user_handle: &UserHandle<LEN>,
   1320 /// ) -> Vec<PublicKeyCredentialDescriptor<Vec<u8>>> {
   1321 ///     get_credentials(user_handle)
   1322 /// }
   1323 /// /// Used to fetch the excluded `PublicKeyCredentialDescriptor`s associated with `user_handle` during
   1324 /// /// registration as well as the `AllowedCredentials` containing `AllowedCredential`s with no credential-specific
   1325 /// /// extensions which is used for non-discoverable requests.
   1326 /// # #[cfg(feature = "custom")]
   1327 /// fn get_credentials<const LEN: usize, T>(user_handle: &UserHandle<LEN>) -> T
   1328 /// where
   1329 ///     T: Credentials,
   1330 ///     PublicKeyCredentialDescriptor<Vec<u8>>: Into<T::Credential>,
   1331 /// {
   1332 ///     let iter = get_cred_parts(user_handle);
   1333 ///     let len = iter.size_hint().0;
   1334 ///     iter.fold(T::with_capacity(len), |mut creds, parts| {
   1335 ///         creds.push(
   1336 ///             PublicKeyCredentialDescriptor {
   1337 ///                 id: parts.0,
   1338 ///                 transports: parts.1,
   1339 ///             }
   1340 ///             .into(),
   1341 ///         );
   1342 ///         creds
   1343 ///     })
   1344 /// }
   1345 /// /// Fetches all `CredentialId`s and associated `AuthTransports` under `user_handle`
   1346 /// /// from the database.
   1347 /// # #[cfg(feature = "custom")]
   1348 /// fn get_cred_parts<const LEN: usize>(
   1349 ///     user_handle: &UserHandle<LEN>,
   1350 /// ) -> impl Iterator<Item = (CredentialId<Vec<u8>>, AuthTransports)> {
   1351 ///     // ⋮
   1352 /// #     [(
   1353 /// #         CredentialId::try_from(vec![0; 16]).unwrap(),
   1354 /// #         AuthTransports::NONE,
   1355 /// #     )]
   1356 /// #     .into_iter()
   1357 /// }
   1358 /// ```
   1359 pub trait Credentials: Sized {
   1360     /// The "credential"s that make up `Self`.
   1361     type Credential;
   1362     /// Returns `Self`.
   1363     #[inline]
   1364     #[must_use]
   1365     fn new() -> Self {
   1366         Self::with_capacity(0)
   1367     }
   1368     /// Returns `Self` with at least `capacity` allocated.
   1369     fn with_capacity(capacity: usize) -> Self;
   1370     /// Adds `cred` to `self`.
   1371     ///
   1372     /// Returns `true` iff `cred` was added.
   1373     fn push(&mut self, cred: Self::Credential) -> bool;
   1374     /// Returns the number of [`Self::Credential`]s in `Self`.
   1375     fn len(&self) -> usize;
   1376     /// Returns `true` iff [`Self::len`] is `0`.
   1377     #[inline]
   1378     fn is_empty(&self) -> bool {
   1379         self.len() == 0
   1380     }
   1381 }
   1382 impl<T> Credentials for Vec<T> {
   1383     type Credential = T;
   1384     #[inline]
   1385     fn with_capacity(capacity: usize) -> Self {
   1386         Self::with_capacity(capacity)
   1387     }
   1388     #[inline]
   1389     fn push(&mut self, cred: Self::Credential) -> bool {
   1390         self.push(cred);
   1391         true
   1392     }
   1393     #[inline]
   1394     fn len(&self) -> usize {
   1395         self.len()
   1396     }
   1397 }
   1398 /// Additional options that control how [`Ceremony::partial_validate`] works.
   1399 struct CeremonyOptions<'origins, 'top_origins, O, T> {
   1400     /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   1401     ///
   1402     /// When this is empty, the origin that will be used will be based on
   1403     /// the [`RpId`] passed to [`RegistrationServerState::verify`]. If [`RpId::Domain`], then the [`DomainOrigin`] returned from
   1404     /// passing [`AsciiDomain::as_ref`] to [`DomainOrigin::new`] will be used; otherwise the [`Url`] in
   1405     /// [`RpId::Url`] will be used.
   1406     allowed_origins: &'origins [O],
   1407     /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin)
   1408     /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin).
   1409     ///
   1410     /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained
   1411     /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`,
   1412     /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`.
   1413     allowed_top_origins: Option<&'top_origins [T]>,
   1414     /// The required [`Backup`] state of the credential.
   1415     backup_requirement: BackupReq,
   1416     /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`.
   1417     #[cfg(feature = "serde_relaxed")]
   1418     client_data_json_relaxed: bool,
   1419 }
   1420 impl<'o, 't, O, T> From<&RegistrationVerificationOptions<'o, 't, O, T>>
   1421     for CeremonyOptions<'o, 't, O, T>
   1422 {
   1423     fn from(value: &RegistrationVerificationOptions<'o, 't, O, T>) -> Self {
   1424         Self {
   1425             allowed_origins: value.allowed_origins,
   1426             allowed_top_origins: value.allowed_top_origins,
   1427             backup_requirement: value.backup_requirement,
   1428             #[cfg(feature = "serde_relaxed")]
   1429             client_data_json_relaxed: value.client_data_json_relaxed,
   1430         }
   1431     }
   1432 }
   1433 /// Functionality common to both registration and authentication ceremonies.
   1434 ///
   1435 /// Designed to be implemented on the _request_ side.
   1436 trait Ceremony<const USER_LEN: usize, const DISCOVERABLE: bool> {
   1437     /// The type of response that is associated with the ceremony.
   1438     type R: Response;
   1439     /// Challenge.
   1440     fn rand_challenge(&self) -> SentChallenge;
   1441     /// `Instant` the ceremony was expires.
   1442     #[cfg(not(feature = "serializable_server_state"))]
   1443     fn expiry(&self) -> Instant;
   1444     /// `Instant` the ceremony was expires.
   1445     #[cfg(feature = "serializable_server_state")]
   1446     fn expiry(&self) -> SystemTime;
   1447     /// User verification requirement.
   1448     fn user_verification(&self) -> UserVerificationRequirement;
   1449     /// Performs validation of ceremony criteria common to both ceremony types.
   1450     #[expect(
   1451         clippy::type_complexity,
   1452         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
   1453     )]
   1454     #[expect(clippy::too_many_lines, reason = "102 lines is fine")]
   1455     fn partial_validate<'a, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>>(
   1456         &self,
   1457         rp_id: &RpId,
   1458         resp: &'a Self::R,
   1459         key: <<Self::R as Response>::Auth as AuthResponse>::CredKey<'_>,
   1460         options: &CeremonyOptions<'_, '_, O, T>,
   1461     ) -> Result<
   1462         <<Self::R as Response>::Auth as AuthResponse>::Auth<'a>,
   1463         CeremonyErr<
   1464             <<<Self::R as Response>::Auth as AuthResponse>::Auth<'a> as AuthDataContainer<'a>>::Err,
   1465         >,
   1466     > {
   1467         // [Registration ceremony](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential)
   1468         // is handled by:
   1469         //
   1470         // 1. Calling code.
   1471         // 2. Client code and the construction of `resp` (hopefully via [`Registration::deserialize`]).
   1472         // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAttestation::deserialize`]).
   1473         // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]).
   1474         // 5. Below via [`CollectedClientData::from_client_data_json_relaxed`].
   1475         // 6. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`].
   1476         // 7. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`].
   1477         // 8. Below.
   1478         // 9. Below.
   1479         // 10. Below.
   1480         // 11. Below.
   1481         // 12. Below via [`AuthenticatorAttestation::new`].
   1482         // 13. Below via [`AttestationObject::parse_data`].
   1483         // 14. Below.
   1484         // 15. [`RegistrationServerState::verify`].
   1485         // 16. Below.
   1486         // 17. Below via [`AuthenticatorData::from_cbor`].
   1487         // 18. Below.
   1488         // 19. Below.
   1489         // 20. [`RegistrationServerState::verify`].
   1490         // 21. Below via [`AttestationObject::parse_data`].
   1491         // 22. Below via [`AttestationObject::parse_data`].
   1492         // 23. N/A since only none and self attestations are supported.
   1493         // 24. Always satisfied since only none and self attestations are supported (Item 3 is N/A).
   1494         // 25. Below via [`AttestedCredentialData::from_cbor`].
   1495         // 26. Calling code.
   1496         // 27. [`RegistrationServerState::verify`].
   1497         // 28. N/A since only none and self attestations are supported.
   1498         // 29. [`RegistrationServerState::verify`].
   1499         //
   1500         //
   1501         // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion)
   1502         // is handled by:
   1503         //
   1504         // 1. Calling code.
   1505         // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]).
   1506         // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]).
   1507         // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]).
   1508         // 5. [`AuthenticationServerState::verify`].
   1509         // 6. [`AuthenticationServerState::verify`].
   1510         // 7. Informative only in that it defines variables.
   1511         // 8. Below via [`CollectedClientData::from_client_data_json_relaxed`].
   1512         // 9. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`].
   1513         // 10. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`].
   1514         // 11. Below.
   1515         // 12. Below.
   1516         // 13. Below.
   1517         // 14. Below.
   1518         // 15. Below.
   1519         // 16. Below via [`AuthenticatorData::from_cbor`].
   1520         // 17. Below.
   1521         // 18. Below via [`AuthenticatorData::from_cbor`].
   1522         // 19. Below.
   1523         // 20. Below via [`AuthenticatorAssertion::new`].
   1524         // 21. Below.
   1525         // 22. [`AuthenticationServerState::verify`].
   1526         // 23. [`AuthenticationServerState::verify`].
   1527         // 24. [`AuthenticationServerState::verify`].
   1528         // 25. [`AuthenticationServerState::verify`].
   1529 
   1530         // Enforce timeout.
   1531         #[cfg(not(feature = "serializable_server_state"))]
   1532         let active = self.expiry() >= Instant::now();
   1533         #[cfg(feature = "serializable_server_state")]
   1534         let active = self.expiry() >= SystemTime::now();
   1535         if active {
   1536             #[cfg(feature = "serde_relaxed")]
   1537             let relaxed = options.client_data_json_relaxed;
   1538             #[cfg(not(feature = "serde_relaxed"))]
   1539             let relaxed = false;
   1540             resp.auth()
   1541                 // Steps 5–7, 12–13, 17, 21–22, and 25 of the registration ceremony.
   1542                 // Steps 8–10, 16, 18, and 20–21 of the authentication ceremony.
   1543                 .parse_data_and_verify_sig(key, relaxed)
   1544                 .map_err(CeremonyErr::AuthResp)
   1545                 .and_then(|(client_data_json, auth_response)| {
   1546                     if options.allowed_origins.is_empty() {
   1547                         if match *rp_id {
   1548                             RpId::Domain(ref dom) => {
   1549                                 // Steps 9 and 12 of the registration and authentication ceremonies
   1550                                 // respectively.
   1551                                 DomainOrigin::new(dom.as_ref()) == client_data_json.origin
   1552                             }
   1553                             // Steps 9 and 12 of the registration and authentication ceremonies
   1554                             // respectively.
   1555                             RpId::Url(ref url) => url == client_data_json.origin,
   1556                             RpId::StaticDomain(dom) => {
   1557                                 DomainOrigin::new(dom.0) == client_data_json.origin
   1558                             }
   1559                         } {
   1560                             Ok(())
   1561                         } else {
   1562                             Err(CeremonyErr::OriginMismatch)
   1563                         }
   1564                     } else {
   1565                         options
   1566                             .allowed_origins
   1567                             .iter()
   1568                             // Steps 9 and 12 of the registration and authentication ceremonies
   1569                             // respectively.
   1570                             .find(|o| **o == client_data_json.origin)
   1571                             .ok_or(CeremonyErr::OriginMismatch)
   1572                             .map(|_| ())
   1573                     }
   1574                     .and_then(|()| {
   1575                         // Steps 10–11 of the registration ceremony.
   1576                         // Steps 13–14 of the authentication ceremony.
   1577                         match options.allowed_top_origins {
   1578                             None => {
   1579                                 if client_data_json.cross_origin {
   1580                                     Err(CeremonyErr::CrossOrigin)
   1581                                 } else if client_data_json.top_origin.is_some() {
   1582                                     Err(CeremonyErr::TopOriginMismatch)
   1583                                 } else {
   1584                                     Ok(())
   1585                                 }
   1586                             }
   1587                             Some(top_origins) => client_data_json.top_origin.map_or(Ok(()), |t| {
   1588                                 top_origins
   1589                                     .iter()
   1590                                     .find(|top| **top == t)
   1591                                     .ok_or(CeremonyErr::TopOriginMismatch)
   1592                                     .map(|_| ())
   1593                             }),
   1594                         }
   1595                         .and_then(|()| {
   1596                             // Steps 8 and 11 of the registration and authentication ceremonies
   1597                             // respectively.
   1598                             if self.rand_challenge() == client_data_json.challenge {
   1599                                 let auth_data = auth_response.authenticator_data();
   1600                                 rp_id
   1601                                     // Steps 14 and 15 of the registration and authentication ceremonies
   1602                                     // respectively.
   1603                                     .validate_rp_id_hash(auth_data.rp_hash())
   1604                                     .and_then(|()| {
   1605                                         let flag = auth_data.flag();
   1606                                         // Steps 16 and 17 of the registration and authentication ceremonies
   1607                                         // respectively.
   1608                                         if flag.user_verified
   1609                                             || !matches!(
   1610                                                 self.user_verification(),
   1611                                                 UserVerificationRequirement::Required
   1612                                             )
   1613                                         {
   1614                                             // Steps 18–19 of the registration ceremony.
   1615                                             // Step 19 of the authentication ceremony.
   1616                                             match options.backup_requirement {
   1617                                                 BackupReq::None => Ok(()),
   1618                                                 BackupReq::NotEligible => {
   1619                                                     if matches!(flag.backup, Backup::NotEligible) {
   1620                                                         Ok(())
   1621                                                     } else {
   1622                                                         Err(CeremonyErr::BackupEligible)
   1623                                                     }
   1624                                                 }
   1625                                                 BackupReq::Eligible => {
   1626                                                     if matches!(flag.backup, Backup::NotEligible) {
   1627                                                         Err(CeremonyErr::BackupNotEligible)
   1628                                                     } else {
   1629                                                         Ok(())
   1630                                                     }
   1631                                                 }
   1632                                                 BackupReq::EligibleNotExists => {
   1633                                                     if matches!(flag.backup, Backup::Eligible) {
   1634                                                         Ok(())
   1635                                                     } else {
   1636                                                         Err(CeremonyErr::BackupExists)
   1637                                                     }
   1638                                                 }
   1639                                                 BackupReq::Exists => {
   1640                                                     if matches!(flag.backup, Backup::Exists) {
   1641                                                         Ok(())
   1642                                                     } else {
   1643                                                         Err(CeremonyErr::BackupDoesNotExist)
   1644                                                     }
   1645                                                 }
   1646                                             }
   1647                                         } else {
   1648                                             Err(CeremonyErr::UserNotVerified)
   1649                                         }
   1650                                     })
   1651                                     .map(|()| auth_response)
   1652                             } else {
   1653                                 Err(CeremonyErr::ChallengeMismatch)
   1654                             }
   1655                         })
   1656                     })
   1657                 })
   1658         } else {
   1659             Err(CeremonyErr::Timeout)
   1660         }
   1661     }
   1662 }
   1663 /// "Ceremonies" stored on the server that expire after a certain duration.
   1664 ///
   1665 /// Types like [`RegistrationServerState`] and [`DiscoverableAuthenticationServerState`] are based on [`Challenge`]s
   1666 /// that expire after a certain duration.
   1667 pub trait TimedCeremony {
   1668     /// Returns the `Instant` the ceremony expires.
   1669     ///
   1670     /// Note when `serializable_server_state` is enabled, [`SystemTime`] is returned instead.
   1671     #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))]
   1672     #[cfg(any(doc, not(feature = "serializable_server_state")))]
   1673     fn expiration(&self) -> Instant;
   1674     /// Returns the `SystemTime` the ceremony expires.
   1675     #[cfg(all(not(doc), feature = "serializable_server_state"))]
   1676     fn expiration(&self) -> SystemTime;
   1677 }
   1678 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues).
   1679 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1680 pub struct PrfInput<'first, 'second> {
   1681     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first).
   1682     pub first: &'first [u8],
   1683     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second).
   1684     pub second: Option<&'second [u8]>,
   1685 }
   1686 impl<'first, 'second> PrfInput<'first, 'second> {
   1687     /// Returns a `PrfInput` with [`Self::first`] set to `first` and [`Self::second`] set to `None`.
   1688     #[expect(single_use_lifetimes, reason = "false positive")]
   1689     #[inline]
   1690     #[must_use]
   1691     pub const fn with_first<'a: 'first>(first: &'a [u8]) -> Self {
   1692         Self {
   1693             first,
   1694             second: None,
   1695         }
   1696     }
   1697     /// Same as [`Self::with_first`] except [`Self::second`] is set to `Some` containing `second`.
   1698     #[expect(single_use_lifetimes, reason = "false positive")]
   1699     #[inline]
   1700     #[must_use]
   1701     pub const fn with_two<'a: 'first, 'b: 'second>(first: &'a [u8], second: &'b [u8]) -> Self {
   1702         Self {
   1703             first,
   1704             second: Some(second),
   1705         }
   1706     }
   1707 }
   1708 /// The number of milliseconds in 5 minutes.
   1709 ///
   1710 /// This is the recommended default timeout duration for ceremonies
   1711 /// [in the spec](https://www.w3.org/TR/webauthn-3/#sctn-timeout-recommended-range).
   1712 pub const FIVE_MINUTES: NonZeroU32 = NonZeroU32::new(300_000).unwrap();
   1713 #[cfg(test)]
   1714 mod tests {
   1715     use super::AsciiDomainStatic;
   1716     #[cfg(feature = "custom")]
   1717     use super::{
   1718         super::{
   1719             AggErr, AuthenticatedCredential,
   1720             response::{
   1721                 AuthTransports, AuthenticatorAttachment, Backup, CredentialId,
   1722                 auth::{
   1723                     DiscoverableAuthentication, DiscoverableAuthenticatorAssertion,
   1724                     NonDiscoverableAuthentication, NonDiscoverableAuthenticatorAssertion,
   1725                 },
   1726                 register::{
   1727                     AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation,
   1728                     AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputs,
   1729                     ClientExtensionsOutputsStaticState, CompressedP256PubKey, CompressedP384PubKey,
   1730                     CompressedPubKey, CredentialProtectionPolicy, DynamicState, Ed25519PubKey,
   1731                     Registration, RsaPubKey, StaticState, UncompressedPubKey,
   1732                 },
   1733             },
   1734         },
   1735         Challenge, Credentials, ExtensionInfo, ExtensionReq, PrfInput,
   1736         PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement,
   1737         auth::{
   1738             AllowedCredential, AllowedCredentials, AuthenticationVerificationOptions,
   1739             CredentialSpecificExtension, DiscoverableCredentialRequestOptions,
   1740             Extension as AuthExt, NonDiscoverableCredentialRequestOptions, PrfInputOwned,
   1741         },
   1742         register::{
   1743             CredProtect, CredentialCreationOptions, Extension as RegExt, FourToSixtyThree,
   1744             PublicKeyCredentialUserEntity, RegistrationVerificationOptions, UserHandle,
   1745         },
   1746     };
   1747     #[cfg(feature = "custom")]
   1748     use ed25519_dalek::{Signer, SigningKey};
   1749     #[cfg(feature = "custom")]
   1750     use p256::{
   1751         ecdsa::{DerSignature as P256DerSig, SigningKey as P256Key},
   1752         elliptic_curve::sec1::Tag,
   1753     };
   1754     #[cfg(feature = "custom")]
   1755     use p384::ecdsa::{DerSignature as P384DerSig, SigningKey as P384Key};
   1756     #[cfg(feature = "custom")]
   1757     use rsa::{
   1758         BigUint, RsaPrivateKey,
   1759         pkcs1v15::SigningKey as RsaKey,
   1760         sha2::{Digest, Sha256},
   1761         signature::{Keypair, SignatureEncoding},
   1762         traits::PublicKeyParts,
   1763     };
   1764     use serde_json as _;
   1765     #[cfg(feature = "custom")]
   1766     const CBOR_UINT: u8 = 0b000_00000;
   1767     #[cfg(feature = "custom")]
   1768     const CBOR_NEG: u8 = 0b001_00000;
   1769     #[cfg(feature = "custom")]
   1770     const CBOR_BYTES: u8 = 0b010_00000;
   1771     #[cfg(feature = "custom")]
   1772     const CBOR_TEXT: u8 = 0b011_00000;
   1773     #[cfg(feature = "custom")]
   1774     const CBOR_MAP: u8 = 0b101_00000;
   1775     #[cfg(feature = "custom")]
   1776     const CBOR_SIMPLE: u8 = 0b111_00000;
   1777     #[cfg(feature = "custom")]
   1778     const CBOR_TRUE: u8 = CBOR_SIMPLE | 21;
   1779     #[test]
   1780     fn ascii_domain_static() {
   1781         /// No trailing dot, max label length, max domain length.
   1782         const LONG: AsciiDomainStatic = AsciiDomainStatic::new(
   1783                 "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww",
   1784             )
   1785             .unwrap();
   1786         /// Trailing dot, min label length, max domain length.
   1787         const LONG_TRAILING: AsciiDomainStatic = AsciiDomainStatic::new("w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.").unwrap();
   1788         /// Single character domain.
   1789         const SHORT: AsciiDomainStatic = AsciiDomainStatic::new("w").unwrap();
   1790         let long_label = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww";
   1791         assert_eq!(long_label.len(), 63);
   1792         let mut long = format!("{long_label}.{long_label}.{long_label}.{long_label}");
   1793         _ = long.pop();
   1794         _ = long.pop();
   1795         assert_eq!(LONG.0.len(), 253);
   1796         assert_eq!(LONG.0, long.as_str());
   1797         let trailing = "w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.";
   1798         assert_eq!(LONG_TRAILING.0.len(), 254);
   1799         assert_eq!(LONG_TRAILING.0, trailing);
   1800         assert_eq!(SHORT.0.len(), 1);
   1801         assert_eq!(SHORT.0, "w");
   1802         assert!(AsciiDomainStatic::new("www.Example.com").is_none());
   1803         assert!(AsciiDomainStatic::new("").is_none());
   1804         assert!(AsciiDomainStatic::new(".").is_none());
   1805         assert!(AsciiDomainStatic::new("www..c").is_none());
   1806         let too_long_label = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww";
   1807         assert_eq!(too_long_label.len(), 64);
   1808         assert!(AsciiDomainStatic::new(too_long_label).is_none());
   1809         let dom_254_no_trailing_dot = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww";
   1810         assert_eq!(dom_254_no_trailing_dot.len(), 254);
   1811         assert!(AsciiDomainStatic::new(dom_254_no_trailing_dot).is_none());
   1812         assert!(AsciiDomainStatic::new("λ.com").is_none());
   1813     }
   1814     #[cfg(feature = "custom")]
   1815     const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap();
   1816     #[test]
   1817     #[cfg(feature = "custom")]
   1818     fn eddsa_reg() -> Result<(), AggErr> {
   1819         let id = UserHandle::from([0]);
   1820         let mut opts = CredentialCreationOptions::passkey(
   1821             RP_ID,
   1822             PublicKeyCredentialUserEntity {
   1823                 name: "foo".try_into()?,
   1824                 id: &id,
   1825                 display_name: None,
   1826             },
   1827             Vec::new(),
   1828         );
   1829         opts.public_key.challenge = Challenge(0);
   1830         opts.public_key.extensions = RegExt {
   1831             cred_props: None,
   1832             cred_protect: CredProtect::UserVerificationRequired(
   1833                 false,
   1834                 ExtensionInfo::RequireEnforceValue,
   1835             ),
   1836             min_pin_length: Some((FourToSixtyThree::Ten, ExtensionInfo::RequireEnforceValue)),
   1837             prf: Some((
   1838                 PrfInput {
   1839                     first: [0].as_slice(),
   1840                     second: None,
   1841                 },
   1842                 ExtensionInfo::RequireEnforceValue,
   1843             )),
   1844         };
   1845         let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   1846         // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information.
   1847         let mut attestation_object = Vec::new();
   1848         attestation_object.extend_from_slice(
   1849             [
   1850                 CBOR_MAP | 3,
   1851                 CBOR_TEXT | 3,
   1852                 b'f',
   1853                 b'm',
   1854                 b't',
   1855                 CBOR_TEXT | 6,
   1856                 b'p',
   1857                 b'a',
   1858                 b'c',
   1859                 b'k',
   1860                 b'e',
   1861                 b'd',
   1862                 CBOR_TEXT | 7,
   1863                 b'a',
   1864                 b't',
   1865                 b't',
   1866                 b'S',
   1867                 b't',
   1868                 b'm',
   1869                 b't',
   1870                 CBOR_MAP | 2,
   1871                 CBOR_TEXT | 3,
   1872                 b'a',
   1873                 b'l',
   1874                 b'g',
   1875                 // COSE EdDSA.
   1876                 CBOR_NEG | 7,
   1877                 CBOR_TEXT | 3,
   1878                 b's',
   1879                 b'i',
   1880                 b'g',
   1881                 CBOR_BYTES | 24,
   1882                 64,
   1883                 0,
   1884                 0,
   1885                 0,
   1886                 0,
   1887                 0,
   1888                 0,
   1889                 0,
   1890                 0,
   1891                 0,
   1892                 0,
   1893                 0,
   1894                 0,
   1895                 0,
   1896                 0,
   1897                 0,
   1898                 0,
   1899                 0,
   1900                 0,
   1901                 0,
   1902                 0,
   1903                 0,
   1904                 0,
   1905                 0,
   1906                 0,
   1907                 0,
   1908                 0,
   1909                 0,
   1910                 0,
   1911                 0,
   1912                 0,
   1913                 0,
   1914                 0,
   1915                 0,
   1916                 0,
   1917                 0,
   1918                 0,
   1919                 0,
   1920                 0,
   1921                 0,
   1922                 0,
   1923                 0,
   1924                 0,
   1925                 0,
   1926                 0,
   1927                 0,
   1928                 0,
   1929                 0,
   1930                 0,
   1931                 0,
   1932                 0,
   1933                 0,
   1934                 0,
   1935                 0,
   1936                 0,
   1937                 0,
   1938                 0,
   1939                 0,
   1940                 0,
   1941                 0,
   1942                 0,
   1943                 0,
   1944                 0,
   1945                 0,
   1946                 0,
   1947                 CBOR_TEXT | 8,
   1948                 b'a',
   1949                 b'u',
   1950                 b't',
   1951                 b'h',
   1952                 b'D',
   1953                 b'a',
   1954                 b't',
   1955                 b'a',
   1956                 CBOR_BYTES | 24,
   1957                 // Length is 154.
   1958                 154,
   1959                 // RP ID HASH.
   1960                 // This will be overwritten later.
   1961                 0,
   1962                 0,
   1963                 0,
   1964                 0,
   1965                 0,
   1966                 0,
   1967                 0,
   1968                 0,
   1969                 0,
   1970                 0,
   1971                 0,
   1972                 0,
   1973                 0,
   1974                 0,
   1975                 0,
   1976                 0,
   1977                 0,
   1978                 0,
   1979                 0,
   1980                 0,
   1981                 0,
   1982                 0,
   1983                 0,
   1984                 0,
   1985                 0,
   1986                 0,
   1987                 0,
   1988                 0,
   1989                 0,
   1990                 0,
   1991                 0,
   1992                 0,
   1993                 // FLAGS.
   1994                 // UP, UV, AT, and ED (right-to-left).
   1995                 0b1100_0101,
   1996                 // COUNTER.
   1997                 // 0 as 32-bit big endian.
   1998                 0,
   1999                 0,
   2000                 0,
   2001                 0,
   2002                 // AAGUID.
   2003                 0,
   2004                 0,
   2005                 0,
   2006                 0,
   2007                 0,
   2008                 0,
   2009                 0,
   2010                 0,
   2011                 0,
   2012                 0,
   2013                 0,
   2014                 0,
   2015                 0,
   2016                 0,
   2017                 0,
   2018                 0,
   2019                 // L.
   2020                 // CREDENTIAL ID length is 16 as 16-bit big endian.
   2021                 0,
   2022                 16,
   2023                 // CREDENTIAL ID.
   2024                 0,
   2025                 0,
   2026                 0,
   2027                 0,
   2028                 0,
   2029                 0,
   2030                 0,
   2031                 0,
   2032                 0,
   2033                 0,
   2034                 0,
   2035                 0,
   2036                 0,
   2037                 0,
   2038                 0,
   2039                 0,
   2040                 CBOR_MAP | 4,
   2041                 // COSE kty.
   2042                 CBOR_UINT | 1,
   2043                 // COSE OKP.
   2044                 CBOR_UINT | 1,
   2045                 // COSE alg.
   2046                 CBOR_UINT | 3,
   2047                 // COSE EdDSA.
   2048                 CBOR_NEG | 7,
   2049                 // COSE OKP crv.
   2050                 CBOR_NEG,
   2051                 // COSE Ed25519.
   2052                 CBOR_UINT | 6,
   2053                 // COSE OKP x.
   2054                 CBOR_NEG | 1,
   2055                 CBOR_BYTES | 24,
   2056                 // Length is 32.
   2057                 32,
   2058                 // Compressed-y coordinate.
   2059                 // This will be overwritten later.
   2060                 0,
   2061                 0,
   2062                 0,
   2063                 0,
   2064                 0,
   2065                 0,
   2066                 0,
   2067                 0,
   2068                 0,
   2069                 0,
   2070                 0,
   2071                 0,
   2072                 0,
   2073                 0,
   2074                 0,
   2075                 0,
   2076                 0,
   2077                 0,
   2078                 0,
   2079                 0,
   2080                 0,
   2081                 0,
   2082                 0,
   2083                 0,
   2084                 0,
   2085                 0,
   2086                 0,
   2087                 0,
   2088                 0,
   2089                 0,
   2090                 0,
   2091                 0,
   2092                 CBOR_MAP | 3,
   2093                 CBOR_TEXT | 11,
   2094                 b'c',
   2095                 b'r',
   2096                 b'e',
   2097                 b'd',
   2098                 b'P',
   2099                 b'r',
   2100                 b'o',
   2101                 b't',
   2102                 b'e',
   2103                 b'c',
   2104                 b't',
   2105                 // userVerificationRequired.
   2106                 CBOR_UINT | 3,
   2107                 // CBOR text of length 11.
   2108                 CBOR_TEXT | 11,
   2109                 b'h',
   2110                 b'm',
   2111                 b'a',
   2112                 b'c',
   2113                 b'-',
   2114                 b's',
   2115                 b'e',
   2116                 b'c',
   2117                 b'r',
   2118                 b'e',
   2119                 b't',
   2120                 CBOR_TRUE,
   2121                 CBOR_TEXT | 12,
   2122                 b'm',
   2123                 b'i',
   2124                 b'n',
   2125                 b'P',
   2126                 b'i',
   2127                 b'n',
   2128                 b'L',
   2129                 b'e',
   2130                 b'n',
   2131                 b'g',
   2132                 b't',
   2133                 b'h',
   2134                 CBOR_UINT | 16,
   2135             ]
   2136             .as_slice(),
   2137         );
   2138         attestation_object
   2139             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   2140         let sig_key = SigningKey::from_bytes(&[0; 32]);
   2141         let ver_key = sig_key.verifying_key();
   2142         let pub_key = ver_key.as_bytes();
   2143         attestation_object[107..139]
   2144             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   2145         attestation_object[188..220].copy_from_slice(pub_key);
   2146         let sig = sig_key.sign(&attestation_object[107..]);
   2147         attestation_object[32..96].copy_from_slice(sig.to_bytes().as_slice());
   2148         attestation_object.truncate(261);
   2149         assert!(matches!(opts.start_ceremony()?.0.verify(
   2150             RP_ID,
   2151             &Registration {
   2152                 response: AuthenticatorAttestation::new(
   2153                     client_data_json,
   2154                     attestation_object,
   2155                     AuthTransports::NONE,
   2156                 ),
   2157                 authenticator_attachment: AuthenticatorAttachment::None,
   2158                 client_extension_results: ClientExtensionsOutputs {
   2159                     cred_props: None,
   2160                     prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true, }),
   2161                 },
   2162             },
   2163             &RegistrationVerificationOptions::<&str, &str>::default(),
   2164         )?.static_state.credential_public_key, UncompressedPubKey::Ed25519(k) if k.into_inner() == pub_key));
   2165         Ok(())
   2166     }
   2167     #[test]
   2168     #[cfg(feature = "custom")]
   2169     fn eddsa_auth() -> Result<(), AggErr> {
   2170         let mut creds = AllowedCredentials::with_capacity(1);
   2171         _ = creds.push(AllowedCredential {
   2172             credential: PublicKeyCredentialDescriptor {
   2173                 id: CredentialId::try_from(vec![0; 16])?,
   2174                 transports: AuthTransports::NONE,
   2175             },
   2176             extension: CredentialSpecificExtension {
   2177                 prf: Some(PrfInputOwned {
   2178                     first: Vec::new(),
   2179                     second: Some(Vec::new()),
   2180                     ext_req: ExtensionReq::Require,
   2181                 }),
   2182             },
   2183         });
   2184         let mut opts = NonDiscoverableCredentialRequestOptions::second_factor(RP_ID, creds);
   2185         opts.options.user_verification = UserVerificationRequirement::Required;
   2186         opts.options.challenge = Challenge(0);
   2187         opts.options.extensions = AuthExt { prf: None };
   2188         let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   2189         // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information.
   2190         let mut authenticator_data = Vec::with_capacity(164);
   2191         authenticator_data.extend_from_slice(
   2192             [
   2193                 // rpIdHash.
   2194                 // This will be overwritten later.
   2195                 0,
   2196                 0,
   2197                 0,
   2198                 0,
   2199                 0,
   2200                 0,
   2201                 0,
   2202                 0,
   2203                 0,
   2204                 0,
   2205                 0,
   2206                 0,
   2207                 0,
   2208                 0,
   2209                 0,
   2210                 0,
   2211                 0,
   2212                 0,
   2213                 0,
   2214                 0,
   2215                 0,
   2216                 0,
   2217                 0,
   2218                 0,
   2219                 0,
   2220                 0,
   2221                 0,
   2222                 0,
   2223                 0,
   2224                 0,
   2225                 0,
   2226                 0,
   2227                 // flags.
   2228                 // UP, UV, and ED (right-to-left).
   2229                 0b1000_0101,
   2230                 // signCount.
   2231                 // 0 as 32-bit big endian.
   2232                 0,
   2233                 0,
   2234                 0,
   2235                 0,
   2236                 CBOR_MAP | 1,
   2237                 CBOR_TEXT | 11,
   2238                 b'h',
   2239                 b'm',
   2240                 b'a',
   2241                 b'c',
   2242                 b'-',
   2243                 b's',
   2244                 b'e',
   2245                 b'c',
   2246                 b'r',
   2247                 b'e',
   2248                 b't',
   2249                 CBOR_BYTES | 24,
   2250                 // Length is 80.
   2251                 80,
   2252                 // Two HMAC outputs concatenated and encrypted.
   2253                 0,
   2254                 0,
   2255                 0,
   2256                 0,
   2257                 0,
   2258                 0,
   2259                 0,
   2260                 0,
   2261                 0,
   2262                 0,
   2263                 0,
   2264                 0,
   2265                 0,
   2266                 0,
   2267                 0,
   2268                 0,
   2269                 0,
   2270                 0,
   2271                 0,
   2272                 0,
   2273                 0,
   2274                 0,
   2275                 0,
   2276                 0,
   2277                 0,
   2278                 0,
   2279                 0,
   2280                 0,
   2281                 0,
   2282                 0,
   2283                 0,
   2284                 0,
   2285                 0,
   2286                 0,
   2287                 0,
   2288                 0,
   2289                 0,
   2290                 0,
   2291                 0,
   2292                 0,
   2293                 0,
   2294                 0,
   2295                 0,
   2296                 0,
   2297                 0,
   2298                 0,
   2299                 0,
   2300                 0,
   2301                 0,
   2302                 0,
   2303                 0,
   2304                 0,
   2305                 0,
   2306                 0,
   2307                 0,
   2308                 0,
   2309                 0,
   2310                 0,
   2311                 0,
   2312                 0,
   2313                 0,
   2314                 0,
   2315                 0,
   2316                 0,
   2317                 0,
   2318                 0,
   2319                 0,
   2320                 0,
   2321                 0,
   2322                 0,
   2323                 0,
   2324                 0,
   2325                 0,
   2326                 0,
   2327                 0,
   2328                 0,
   2329                 0,
   2330                 0,
   2331                 0,
   2332                 0,
   2333             ]
   2334             .as_slice(),
   2335         );
   2336         authenticator_data[..32]
   2337             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   2338         authenticator_data
   2339             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   2340         let ed_priv = SigningKey::from([0; 32]);
   2341         let sig = ed_priv.sign(authenticator_data.as_slice()).to_vec();
   2342         authenticator_data.truncate(132);
   2343         assert!(!opts.start_ceremony()?.0.verify(
   2344             RP_ID,
   2345             &NonDiscoverableAuthentication {
   2346                 raw_id: CredentialId::try_from(vec![0; 16])?,
   2347                 response: NonDiscoverableAuthenticatorAssertion::with_user(
   2348                     client_data_json,
   2349                     authenticator_data,
   2350                     sig,
   2351                     UserHandle::from([0]),
   2352                 ),
   2353                 authenticator_attachment: AuthenticatorAttachment::None,
   2354             },
   2355             &mut AuthenticatedCredential::new(
   2356                 CredentialId::try_from([0; 16].as_slice())?,
   2357                 &UserHandle::from([0]),
   2358                 StaticState {
   2359                     credential_public_key: CompressedPubKey::<_, &[u8], &[u8], &[u8]>::Ed25519(
   2360                         Ed25519PubKey::from(ed_priv.verifying_key().to_bytes()),
   2361                     ),
   2362                     extensions: AuthenticatorExtensionOutputStaticState {
   2363                         cred_protect: CredentialProtectionPolicy::None,
   2364                         hmac_secret: Some(true),
   2365                     },
   2366                     client_extension_results: ClientExtensionsOutputsStaticState {
   2367                         prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true }),
   2368                     }
   2369                 },
   2370                 DynamicState {
   2371                     user_verified: true,
   2372                     backup: Backup::NotEligible,
   2373                     sign_count: 0,
   2374                     authenticator_attachment: AuthenticatorAttachment::None,
   2375                 },
   2376             )?,
   2377             &AuthenticationVerificationOptions::<&str, &str>::default(),
   2378         )?);
   2379         Ok(())
   2380     }
   2381     #[test]
   2382     #[cfg(feature = "custom")]
   2383     fn es256_reg() -> Result<(), AggErr> {
   2384         let id = UserHandle::from([0]);
   2385         let mut opts = CredentialCreationOptions::passkey(
   2386             RP_ID,
   2387             PublicKeyCredentialUserEntity {
   2388                 name: "foo".try_into()?,
   2389                 id: &id,
   2390                 display_name: None,
   2391             },
   2392             Vec::new(),
   2393         );
   2394         opts.public_key.challenge = Challenge(0);
   2395         let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   2396         // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information.
   2397         let mut attestation_object = Vec::with_capacity(210);
   2398         attestation_object.extend_from_slice(
   2399             [
   2400                 CBOR_MAP | 3,
   2401                 CBOR_TEXT | 3,
   2402                 b'f',
   2403                 b'm',
   2404                 b't',
   2405                 CBOR_TEXT | 4,
   2406                 b'n',
   2407                 b'o',
   2408                 b'n',
   2409                 b'e',
   2410                 CBOR_TEXT | 7,
   2411                 b'a',
   2412                 b't',
   2413                 b't',
   2414                 b'S',
   2415                 b't',
   2416                 b'm',
   2417                 b't',
   2418                 CBOR_MAP,
   2419                 CBOR_TEXT | 8,
   2420                 b'a',
   2421                 b'u',
   2422                 b't',
   2423                 b'h',
   2424                 b'D',
   2425                 b'a',
   2426                 b't',
   2427                 b'a',
   2428                 CBOR_BYTES | 24,
   2429                 // Length is 148.
   2430                 148,
   2431                 // RP ID HASH.
   2432                 // This will be overwritten later.
   2433                 0,
   2434                 0,
   2435                 0,
   2436                 0,
   2437                 0,
   2438                 0,
   2439                 0,
   2440                 0,
   2441                 0,
   2442                 0,
   2443                 0,
   2444                 0,
   2445                 0,
   2446                 0,
   2447                 0,
   2448                 0,
   2449                 0,
   2450                 0,
   2451                 0,
   2452                 0,
   2453                 0,
   2454                 0,
   2455                 0,
   2456                 0,
   2457                 0,
   2458                 0,
   2459                 0,
   2460                 0,
   2461                 0,
   2462                 0,
   2463                 0,
   2464                 0,
   2465                 // FLAGS.
   2466                 // UP, UV, and AT (right-to-left).
   2467                 0b0100_0101,
   2468                 // COUNTER.
   2469                 // 0 as 32-bit big endian.
   2470                 0,
   2471                 0,
   2472                 0,
   2473                 0,
   2474                 // AAGUID.
   2475                 0,
   2476                 0,
   2477                 0,
   2478                 0,
   2479                 0,
   2480                 0,
   2481                 0,
   2482                 0,
   2483                 0,
   2484                 0,
   2485                 0,
   2486                 0,
   2487                 0,
   2488                 0,
   2489                 0,
   2490                 0,
   2491                 // L.
   2492                 // CREDENTIAL ID length is 16 as 16-bit big endian.
   2493                 0,
   2494                 16,
   2495                 // CREDENTIAL ID.
   2496                 0,
   2497                 0,
   2498                 0,
   2499                 0,
   2500                 0,
   2501                 0,
   2502                 0,
   2503                 0,
   2504                 0,
   2505                 0,
   2506                 0,
   2507                 0,
   2508                 0,
   2509                 0,
   2510                 0,
   2511                 0,
   2512                 CBOR_MAP | 5,
   2513                 // COSE kty.
   2514                 CBOR_UINT | 1,
   2515                 // COSE EC2.
   2516                 CBOR_UINT | 2,
   2517                 // COSE alg.
   2518                 CBOR_UINT | 3,
   2519                 // COSE ES256.
   2520                 CBOR_NEG | 6,
   2521                 // COSE EC2 crv.
   2522                 CBOR_NEG,
   2523                 // COSE P-256.
   2524                 CBOR_UINT | 1,
   2525                 // COSE EC2 x.
   2526                 CBOR_NEG | 1,
   2527                 CBOR_BYTES | 24,
   2528                 // Length is 32.
   2529                 32,
   2530                 // X-coordinate. This will be overwritten later.
   2531                 0,
   2532                 0,
   2533                 0,
   2534                 0,
   2535                 0,
   2536                 0,
   2537                 0,
   2538                 0,
   2539                 0,
   2540                 0,
   2541                 0,
   2542                 0,
   2543                 0,
   2544                 0,
   2545                 0,
   2546                 0,
   2547                 0,
   2548                 0,
   2549                 0,
   2550                 0,
   2551                 0,
   2552                 0,
   2553                 0,
   2554                 0,
   2555                 0,
   2556                 0,
   2557                 0,
   2558                 0,
   2559                 0,
   2560                 0,
   2561                 0,
   2562                 0,
   2563                 // COSE EC2 y.
   2564                 CBOR_NEG | 2,
   2565                 CBOR_BYTES | 24,
   2566                 // Length is 32.
   2567                 32,
   2568                 // Y-coordinate. This will be overwritten later.
   2569                 0,
   2570                 0,
   2571                 0,
   2572                 0,
   2573                 0,
   2574                 0,
   2575                 0,
   2576                 0,
   2577                 0,
   2578                 0,
   2579                 0,
   2580                 0,
   2581                 0,
   2582                 0,
   2583                 0,
   2584                 0,
   2585                 0,
   2586                 0,
   2587                 0,
   2588                 0,
   2589                 0,
   2590                 0,
   2591                 0,
   2592                 0,
   2593                 0,
   2594                 0,
   2595                 0,
   2596                 0,
   2597                 0,
   2598                 0,
   2599                 0,
   2600                 0,
   2601             ]
   2602             .as_slice(),
   2603         );
   2604         attestation_object[30..62]
   2605             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   2606         let p256_key = P256Key::from_bytes(
   2607             &[
   2608                 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115,
   2609                 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95,
   2610             ]
   2611             .into(),
   2612         )
   2613         .unwrap()
   2614         .verifying_key()
   2615         .to_encoded_point(false);
   2616         let x = p256_key.x().unwrap();
   2617         let y = p256_key.y().unwrap();
   2618         attestation_object[111..143].copy_from_slice(x);
   2619         attestation_object[146..].copy_from_slice(y);
   2620         assert!(matches!(opts.start_ceremony()?.0.verify(
   2621             RP_ID,
   2622             &Registration {
   2623                 response: AuthenticatorAttestation::new(
   2624                     client_data_json,
   2625                     attestation_object,
   2626                     AuthTransports::NONE,
   2627                 ),
   2628                 authenticator_attachment: AuthenticatorAttachment::None,
   2629                 client_extension_results: ClientExtensionsOutputs {
   2630                     cred_props: None,
   2631                     prf: None,
   2632                 },
   2633             },
   2634             &RegistrationVerificationOptions::<&str, &str>::default(),
   2635         )?.static_state.credential_public_key, UncompressedPubKey::P256(k) if k.x() == x.as_slice() && k.y() == y.as_slice()));
   2636         Ok(())
   2637     }
   2638     #[test]
   2639     #[cfg(feature = "custom")]
   2640     fn es256_auth() -> Result<(), AggErr> {
   2641         let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID);
   2642         opts.public_key.challenge = Challenge(0);
   2643         let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   2644         // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information.
   2645         let mut authenticator_data = Vec::with_capacity(69);
   2646         authenticator_data.extend_from_slice(
   2647             [
   2648                 // rpIdHash.
   2649                 // This will be overwritten later.
   2650                 0,
   2651                 0,
   2652                 0,
   2653                 0,
   2654                 0,
   2655                 0,
   2656                 0,
   2657                 0,
   2658                 0,
   2659                 0,
   2660                 0,
   2661                 0,
   2662                 0,
   2663                 0,
   2664                 0,
   2665                 0,
   2666                 0,
   2667                 0,
   2668                 0,
   2669                 0,
   2670                 0,
   2671                 0,
   2672                 0,
   2673                 0,
   2674                 0,
   2675                 0,
   2676                 0,
   2677                 0,
   2678                 0,
   2679                 0,
   2680                 0,
   2681                 0,
   2682                 // flags.
   2683                 // UP and UV (right-to-left).
   2684                 0b0000_0101,
   2685                 // signCount.
   2686                 // 0 as 32-bit big endian.
   2687                 0,
   2688                 0,
   2689                 0,
   2690                 0,
   2691             ]
   2692             .as_slice(),
   2693         );
   2694         authenticator_data[..32]
   2695             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   2696         authenticator_data
   2697             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   2698         let p256_key = P256Key::from_bytes(
   2699             &[
   2700                 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115,
   2701                 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95,
   2702             ]
   2703             .into(),
   2704         )
   2705         .unwrap();
   2706         let der_sig: P256DerSig = p256_key.sign(authenticator_data.as_slice());
   2707         let pub_key = p256_key.verifying_key().to_encoded_point(true);
   2708         authenticator_data.truncate(37);
   2709         assert!(!opts.start_ceremony()?.0.verify(
   2710             RP_ID,
   2711             &DiscoverableAuthentication {
   2712                 raw_id: CredentialId::try_from(vec![0; 16])?,
   2713                 response: DiscoverableAuthenticatorAssertion::new(
   2714                     client_data_json,
   2715                     authenticator_data,
   2716                     der_sig.as_bytes().into(),
   2717                     UserHandle::from([0]),
   2718                 ),
   2719                 authenticator_attachment: AuthenticatorAttachment::None,
   2720             },
   2721             &mut AuthenticatedCredential::new(
   2722                 CredentialId::try_from([0; 16].as_slice())?,
   2723                 &UserHandle::from([0]),
   2724                 StaticState {
   2725                     credential_public_key: CompressedPubKey::<&[u8], _, &[u8], &[u8]>::P256(
   2726                         CompressedP256PubKey::from((
   2727                             (*pub_key.x().unwrap()).into(),
   2728                             pub_key.tag() == Tag::CompressedOddY
   2729                         )),
   2730                     ),
   2731                     extensions: AuthenticatorExtensionOutputStaticState {
   2732                         cred_protect: CredentialProtectionPolicy::None,
   2733                         hmac_secret: None,
   2734                     },
   2735                     client_extension_results: ClientExtensionsOutputsStaticState { prf: None }
   2736                 },
   2737                 DynamicState {
   2738                     user_verified: true,
   2739                     backup: Backup::NotEligible,
   2740                     sign_count: 0,
   2741                     authenticator_attachment: AuthenticatorAttachment::None,
   2742                 },
   2743             )?,
   2744             &AuthenticationVerificationOptions::<&str, &str>::default(),
   2745         )?);
   2746         Ok(())
   2747     }
   2748     #[test]
   2749     #[cfg(feature = "custom")]
   2750     fn es384_reg() -> Result<(), AggErr> {
   2751         let id = UserHandle::from([0]);
   2752         let mut opts = CredentialCreationOptions::passkey(
   2753             RP_ID,
   2754             PublicKeyCredentialUserEntity {
   2755                 name: "foo".try_into()?,
   2756                 id: &id,
   2757                 display_name: None,
   2758             },
   2759             Vec::new(),
   2760         );
   2761         opts.public_key.challenge = Challenge(0);
   2762         let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   2763         // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information.
   2764         let mut attestation_object = Vec::with_capacity(243);
   2765         attestation_object.extend_from_slice(
   2766             [
   2767                 CBOR_MAP | 3,
   2768                 CBOR_TEXT | 3,
   2769                 b'f',
   2770                 b'm',
   2771                 b't',
   2772                 CBOR_TEXT | 4,
   2773                 b'n',
   2774                 b'o',
   2775                 b'n',
   2776                 b'e',
   2777                 // CBOR text of length 7.
   2778                 CBOR_TEXT | 7,
   2779                 b'a',
   2780                 b't',
   2781                 b't',
   2782                 b'S',
   2783                 b't',
   2784                 b'm',
   2785                 b't',
   2786                 CBOR_MAP,
   2787                 CBOR_TEXT | 8,
   2788                 b'a',
   2789                 b'u',
   2790                 b't',
   2791                 b'h',
   2792                 b'D',
   2793                 b'a',
   2794                 b't',
   2795                 b'a',
   2796                 CBOR_BYTES | 24,
   2797                 // Length is 181.
   2798                 181,
   2799                 // RP ID HASH.
   2800                 // This will be overwritten later.
   2801                 0,
   2802                 0,
   2803                 0,
   2804                 0,
   2805                 0,
   2806                 0,
   2807                 0,
   2808                 0,
   2809                 0,
   2810                 0,
   2811                 0,
   2812                 0,
   2813                 0,
   2814                 0,
   2815                 0,
   2816                 0,
   2817                 0,
   2818                 0,
   2819                 0,
   2820                 0,
   2821                 0,
   2822                 0,
   2823                 0,
   2824                 0,
   2825                 0,
   2826                 0,
   2827                 0,
   2828                 0,
   2829                 0,
   2830                 0,
   2831                 0,
   2832                 0,
   2833                 // FLAGS.
   2834                 // UP, UV, and AT (right-to-left).
   2835                 0b0100_0101,
   2836                 // COUNTER.
   2837                 // 0 as 32-bit big-endian.
   2838                 0,
   2839                 0,
   2840                 0,
   2841                 0,
   2842                 // AAGUID.
   2843                 0,
   2844                 0,
   2845                 0,
   2846                 0,
   2847                 0,
   2848                 0,
   2849                 0,
   2850                 0,
   2851                 0,
   2852                 0,
   2853                 0,
   2854                 0,
   2855                 0,
   2856                 0,
   2857                 0,
   2858                 0,
   2859                 // L.
   2860                 // CREDENTIAL ID length is 16 as 16-bit big endian.
   2861                 0,
   2862                 16,
   2863                 // CREDENTIAL ID.
   2864                 0,
   2865                 0,
   2866                 0,
   2867                 0,
   2868                 0,
   2869                 0,
   2870                 0,
   2871                 0,
   2872                 0,
   2873                 0,
   2874                 0,
   2875                 0,
   2876                 0,
   2877                 0,
   2878                 0,
   2879                 0,
   2880                 CBOR_MAP | 5,
   2881                 // COSE kty.
   2882                 CBOR_UINT | 1,
   2883                 // COSE EC2.
   2884                 CBOR_UINT | 2,
   2885                 // COSE alg.
   2886                 CBOR_UINT | 3,
   2887                 CBOR_NEG | 24,
   2888                 // COSE ES384.
   2889                 34,
   2890                 // COSE EC2 crv.
   2891                 CBOR_NEG,
   2892                 // COSE P-384.
   2893                 CBOR_UINT | 2,
   2894                 // COSE EC2 x.
   2895                 CBOR_NEG | 1,
   2896                 CBOR_BYTES | 24,
   2897                 // Length is 48.
   2898                 48,
   2899                 // X-coordinate. This will be overwritten later.
   2900                 0,
   2901                 0,
   2902                 0,
   2903                 0,
   2904                 0,
   2905                 0,
   2906                 0,
   2907                 0,
   2908                 0,
   2909                 0,
   2910                 0,
   2911                 0,
   2912                 0,
   2913                 0,
   2914                 0,
   2915                 0,
   2916                 0,
   2917                 0,
   2918                 0,
   2919                 0,
   2920                 0,
   2921                 0,
   2922                 0,
   2923                 0,
   2924                 0,
   2925                 0,
   2926                 0,
   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                 0,
   2944                 0,
   2945                 0,
   2946                 0,
   2947                 0,
   2948                 // COSE EC2 y.
   2949                 CBOR_NEG | 2,
   2950                 CBOR_BYTES | 24,
   2951                 // Length is 48.
   2952                 48,
   2953                 // Y-coordinate. This will be overwritten later.
   2954                 0,
   2955                 0,
   2956                 0,
   2957                 0,
   2958                 0,
   2959                 0,
   2960                 0,
   2961                 0,
   2962                 0,
   2963                 0,
   2964                 0,
   2965                 0,
   2966                 0,
   2967                 0,
   2968                 0,
   2969                 0,
   2970                 0,
   2971                 0,
   2972                 0,
   2973                 0,
   2974                 0,
   2975                 0,
   2976                 0,
   2977                 0,
   2978                 0,
   2979                 0,
   2980                 0,
   2981                 0,
   2982                 0,
   2983                 0,
   2984                 0,
   2985                 0,
   2986                 0,
   2987                 0,
   2988                 0,
   2989                 0,
   2990                 0,
   2991                 0,
   2992                 0,
   2993                 0,
   2994                 0,
   2995                 0,
   2996                 0,
   2997                 0,
   2998                 0,
   2999                 0,
   3000                 0,
   3001                 0,
   3002             ]
   3003             .as_slice(),
   3004         );
   3005         attestation_object[30..62]
   3006             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   3007         let p384_key = P384Key::from_bytes(
   3008             &[
   3009                 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232,
   3010                 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1,
   3011                 32, 86, 220, 68, 182, 11, 105, 223, 75, 70,
   3012             ]
   3013             .into(),
   3014         )
   3015         .unwrap()
   3016         .verifying_key()
   3017         .to_encoded_point(false);
   3018         let x = p384_key.x().unwrap();
   3019         let y = p384_key.y().unwrap();
   3020         attestation_object[112..160].copy_from_slice(x);
   3021         attestation_object[163..].copy_from_slice(y);
   3022         assert!(matches!(opts.start_ceremony()?.0.verify(
   3023             RP_ID,
   3024             &Registration {
   3025                 response: AuthenticatorAttestation::new(
   3026                     client_data_json,
   3027                     attestation_object,
   3028                     AuthTransports::NONE,
   3029                 ),
   3030                 authenticator_attachment: AuthenticatorAttachment::None,
   3031                 client_extension_results: ClientExtensionsOutputs {
   3032                     cred_props: None,
   3033                     prf: None,
   3034                 },
   3035             },
   3036             &RegistrationVerificationOptions::<&str, &str>::default(),
   3037         )?.static_state.credential_public_key, UncompressedPubKey::P384(k) if k.x() == x.as_slice() && k.y() == y.as_slice()));
   3038         Ok(())
   3039     }
   3040     #[test]
   3041     #[cfg(feature = "custom")]
   3042     fn es384_auth() -> Result<(), AggErr> {
   3043         let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID);
   3044         opts.public_key.challenge = Challenge(0);
   3045         let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   3046         // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information.
   3047         let mut authenticator_data = Vec::with_capacity(69);
   3048         authenticator_data.extend_from_slice(
   3049             [
   3050                 // rpIdHash.
   3051                 // This will be overwritten later.
   3052                 0,
   3053                 0,
   3054                 0,
   3055                 0,
   3056                 0,
   3057                 0,
   3058                 0,
   3059                 0,
   3060                 0,
   3061                 0,
   3062                 0,
   3063                 0,
   3064                 0,
   3065                 0,
   3066                 0,
   3067                 0,
   3068                 0,
   3069                 0,
   3070                 0,
   3071                 0,
   3072                 0,
   3073                 0,
   3074                 0,
   3075                 0,
   3076                 0,
   3077                 0,
   3078                 0,
   3079                 0,
   3080                 0,
   3081                 0,
   3082                 0,
   3083                 0,
   3084                 // flags.
   3085                 // UP and UV (right-to-left).
   3086                 0b0000_0101,
   3087                 // signCount.
   3088                 // 0 as 32-bit big-endian.
   3089                 0,
   3090                 0,
   3091                 0,
   3092                 0,
   3093             ]
   3094             .as_slice(),
   3095         );
   3096         authenticator_data[..32]
   3097             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   3098         authenticator_data
   3099             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   3100         let p384_key = P384Key::from_bytes(
   3101             &[
   3102                 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232,
   3103                 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1,
   3104                 32, 86, 220, 68, 182, 11, 105, 223, 75, 70,
   3105             ]
   3106             .into(),
   3107         )
   3108         .unwrap();
   3109         let der_sig: P384DerSig = p384_key.sign(authenticator_data.as_slice());
   3110         let pub_key = p384_key.verifying_key().to_encoded_point(true);
   3111         authenticator_data.truncate(37);
   3112         assert!(!opts.start_ceremony()?.0.verify(
   3113             RP_ID,
   3114             &DiscoverableAuthentication {
   3115                 raw_id: CredentialId::try_from(vec![0; 16])?,
   3116                 response: DiscoverableAuthenticatorAssertion::new(
   3117                     client_data_json,
   3118                     authenticator_data,
   3119                     der_sig.as_bytes().into(),
   3120                     UserHandle::from([0]),
   3121                 ),
   3122                 authenticator_attachment: AuthenticatorAttachment::None,
   3123             },
   3124             &mut AuthenticatedCredential::new(
   3125                 CredentialId::try_from([0; 16].as_slice())?,
   3126                 &UserHandle::from([0]),
   3127                 StaticState {
   3128                     credential_public_key: CompressedPubKey::<&[u8], &[u8], _, &[u8]>::P384(
   3129                         CompressedP384PubKey::from((
   3130                             (*pub_key.x().unwrap()).into(),
   3131                             pub_key.tag() == Tag::CompressedOddY
   3132                         )),
   3133                     ),
   3134                     extensions: AuthenticatorExtensionOutputStaticState {
   3135                         cred_protect: CredentialProtectionPolicy::None,
   3136                         hmac_secret: None,
   3137                     },
   3138                     client_extension_results: ClientExtensionsOutputsStaticState { prf: None }
   3139                 },
   3140                 DynamicState {
   3141                     user_verified: true,
   3142                     backup: Backup::NotEligible,
   3143                     sign_count: 0,
   3144                     authenticator_attachment: AuthenticatorAttachment::None,
   3145                 },
   3146             )?,
   3147             &AuthenticationVerificationOptions::<&str, &str>::default(),
   3148         )?);
   3149         Ok(())
   3150     }
   3151     #[test]
   3152     #[cfg(feature = "custom")]
   3153     fn rs256_reg() -> Result<(), AggErr> {
   3154         let id = UserHandle::from([0]);
   3155         let mut opts = CredentialCreationOptions::passkey(
   3156             RP_ID,
   3157             PublicKeyCredentialUserEntity {
   3158                 name: "foo".try_into()?,
   3159                 id: &id,
   3160                 display_name: None,
   3161             },
   3162             Vec::new(),
   3163         );
   3164         opts.public_key.challenge = Challenge(0);
   3165         let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   3166         // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information.
   3167         let mut attestation_object = Vec::with_capacity(406);
   3168         attestation_object.extend_from_slice(
   3169             [
   3170                 CBOR_MAP | 3,
   3171                 CBOR_TEXT | 3,
   3172                 b'f',
   3173                 b'm',
   3174                 b't',
   3175                 CBOR_TEXT | 4,
   3176                 b'n',
   3177                 b'o',
   3178                 b'n',
   3179                 b'e',
   3180                 CBOR_TEXT | 7,
   3181                 b'a',
   3182                 b't',
   3183                 b't',
   3184                 b'S',
   3185                 b't',
   3186                 b'm',
   3187                 b't',
   3188                 CBOR_MAP,
   3189                 CBOR_TEXT | 8,
   3190                 b'a',
   3191                 b'u',
   3192                 b't',
   3193                 b'h',
   3194                 b'D',
   3195                 b'a',
   3196                 b't',
   3197                 b'a',
   3198                 CBOR_BYTES | 25,
   3199                 // Length is 343 as 16-bit big-endian.
   3200                 1,
   3201                 87,
   3202                 // RP ID HASH.
   3203                 // This will be overwritten later.
   3204                 0,
   3205                 0,
   3206                 0,
   3207                 0,
   3208                 0,
   3209                 0,
   3210                 0,
   3211                 0,
   3212                 0,
   3213                 0,
   3214                 0,
   3215                 0,
   3216                 0,
   3217                 0,
   3218                 0,
   3219                 0,
   3220                 0,
   3221                 0,
   3222                 0,
   3223                 0,
   3224                 0,
   3225                 0,
   3226                 0,
   3227                 0,
   3228                 0,
   3229                 0,
   3230                 0,
   3231                 0,
   3232                 0,
   3233                 0,
   3234                 0,
   3235                 0,
   3236                 // FLAGS.
   3237                 // UP, UV, and AT (right-to-left).
   3238                 0b0100_0101,
   3239                 // COUNTER.
   3240                 // 0 as 32-bit big-endian.
   3241                 0,
   3242                 0,
   3243                 0,
   3244                 0,
   3245                 // AAGUID.
   3246                 0,
   3247                 0,
   3248                 0,
   3249                 0,
   3250                 0,
   3251                 0,
   3252                 0,
   3253                 0,
   3254                 0,
   3255                 0,
   3256                 0,
   3257                 0,
   3258                 0,
   3259                 0,
   3260                 0,
   3261                 0,
   3262                 // L.
   3263                 // CREDENTIAL ID length is 16 as 16-bit big endian.
   3264                 0,
   3265                 16,
   3266                 // CREDENTIAL ID.
   3267                 0,
   3268                 0,
   3269                 0,
   3270                 0,
   3271                 0,
   3272                 0,
   3273                 0,
   3274                 0,
   3275                 0,
   3276                 0,
   3277                 0,
   3278                 0,
   3279                 0,
   3280                 0,
   3281                 0,
   3282                 0,
   3283                 CBOR_MAP | 4,
   3284                 // COSE kty.
   3285                 CBOR_UINT | 1,
   3286                 // COSE RSA.
   3287                 CBOR_UINT | 3,
   3288                 // COSE alg.
   3289                 CBOR_UINT | 3,
   3290                 CBOR_NEG | 25,
   3291                 // COSE RS256.
   3292                 1,
   3293                 0,
   3294                 // COSE n.
   3295                 CBOR_NEG,
   3296                 CBOR_BYTES | 25,
   3297                 // Length is 256 as 16-bit big-endian.
   3298                 1,
   3299                 0,
   3300                 // N. This will be overwritten later.
   3301                 0,
   3302                 0,
   3303                 0,
   3304                 0,
   3305                 0,
   3306                 0,
   3307                 0,
   3308                 0,
   3309                 0,
   3310                 0,
   3311                 0,
   3312                 0,
   3313                 0,
   3314                 0,
   3315                 0,
   3316                 0,
   3317                 0,
   3318                 0,
   3319                 0,
   3320                 0,
   3321                 0,
   3322                 0,
   3323                 0,
   3324                 0,
   3325                 0,
   3326                 0,
   3327                 0,
   3328                 0,
   3329                 0,
   3330                 0,
   3331                 0,
   3332                 0,
   3333                 0,
   3334                 0,
   3335                 0,
   3336                 0,
   3337                 0,
   3338                 0,
   3339                 0,
   3340                 0,
   3341                 0,
   3342                 0,
   3343                 0,
   3344                 0,
   3345                 0,
   3346                 0,
   3347                 0,
   3348                 0,
   3349                 0,
   3350                 0,
   3351                 0,
   3352                 0,
   3353                 0,
   3354                 0,
   3355                 0,
   3356                 0,
   3357                 0,
   3358                 0,
   3359                 0,
   3360                 0,
   3361                 0,
   3362                 0,
   3363                 0,
   3364                 0,
   3365                 0,
   3366                 0,
   3367                 0,
   3368                 0,
   3369                 0,
   3370                 0,
   3371                 0,
   3372                 0,
   3373                 0,
   3374                 0,
   3375                 0,
   3376                 0,
   3377                 0,
   3378                 0,
   3379                 0,
   3380                 0,
   3381                 0,
   3382                 0,
   3383                 0,
   3384                 0,
   3385                 0,
   3386                 0,
   3387                 0,
   3388                 0,
   3389                 0,
   3390                 0,
   3391                 0,
   3392                 0,
   3393                 0,
   3394                 0,
   3395                 0,
   3396                 0,
   3397                 0,
   3398                 0,
   3399                 0,
   3400                 0,
   3401                 0,
   3402                 0,
   3403                 0,
   3404                 0,
   3405                 0,
   3406                 0,
   3407                 0,
   3408                 0,
   3409                 0,
   3410                 0,
   3411                 0,
   3412                 0,
   3413                 0,
   3414                 0,
   3415                 0,
   3416                 0,
   3417                 0,
   3418                 0,
   3419                 0,
   3420                 0,
   3421                 0,
   3422                 0,
   3423                 0,
   3424                 0,
   3425                 0,
   3426                 0,
   3427                 0,
   3428                 0,
   3429                 0,
   3430                 0,
   3431                 0,
   3432                 0,
   3433                 0,
   3434                 0,
   3435                 0,
   3436                 0,
   3437                 0,
   3438                 0,
   3439                 0,
   3440                 0,
   3441                 0,
   3442                 0,
   3443                 0,
   3444                 0,
   3445                 0,
   3446                 0,
   3447                 0,
   3448                 0,
   3449                 0,
   3450                 0,
   3451                 0,
   3452                 0,
   3453                 0,
   3454                 0,
   3455                 0,
   3456                 0,
   3457                 0,
   3458                 0,
   3459                 0,
   3460                 0,
   3461                 0,
   3462                 0,
   3463                 0,
   3464                 0,
   3465                 0,
   3466                 0,
   3467                 0,
   3468                 0,
   3469                 0,
   3470                 0,
   3471                 0,
   3472                 0,
   3473                 0,
   3474                 0,
   3475                 0,
   3476                 0,
   3477                 0,
   3478                 0,
   3479                 0,
   3480                 0,
   3481                 0,
   3482                 0,
   3483                 0,
   3484                 0,
   3485                 0,
   3486                 0,
   3487                 0,
   3488                 0,
   3489                 0,
   3490                 0,
   3491                 0,
   3492                 0,
   3493                 0,
   3494                 0,
   3495                 0,
   3496                 0,
   3497                 0,
   3498                 0,
   3499                 0,
   3500                 0,
   3501                 0,
   3502                 0,
   3503                 0,
   3504                 0,
   3505                 0,
   3506                 0,
   3507                 0,
   3508                 0,
   3509                 0,
   3510                 0,
   3511                 0,
   3512                 0,
   3513                 0,
   3514                 0,
   3515                 0,
   3516                 0,
   3517                 0,
   3518                 0,
   3519                 0,
   3520                 0,
   3521                 0,
   3522                 0,
   3523                 0,
   3524                 0,
   3525                 0,
   3526                 0,
   3527                 0,
   3528                 0,
   3529                 0,
   3530                 0,
   3531                 0,
   3532                 0,
   3533                 0,
   3534                 0,
   3535                 0,
   3536                 0,
   3537                 0,
   3538                 0,
   3539                 0,
   3540                 0,
   3541                 0,
   3542                 0,
   3543                 0,
   3544                 0,
   3545                 0,
   3546                 0,
   3547                 0,
   3548                 0,
   3549                 0,
   3550                 0,
   3551                 0,
   3552                 0,
   3553                 0,
   3554                 0,
   3555                 0,
   3556                 0,
   3557                 // COSE e.
   3558                 CBOR_NEG | 1,
   3559                 CBOR_BYTES | 3,
   3560                 // 65537 as 24-bit big-endian.
   3561                 1,
   3562                 0,
   3563                 1,
   3564             ]
   3565             .as_slice(),
   3566         );
   3567         attestation_object[31..63]
   3568             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   3569         let n = [
   3570             111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107,
   3571             195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206,
   3572             185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165,
   3573             100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204,
   3574             145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64,
   3575             87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105,
   3576             81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55,
   3577             117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21,
   3578             254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157,
   3579             108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188,
   3580             42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56,
   3581             104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13,
   3582             72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132,
   3583             153, 79, 0, 133, 78, 7, 218, 165, 241,
   3584         ];
   3585         let e = 65537;
   3586         let d = [
   3587             145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0,
   3588             132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168,
   3589             35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108,
   3590             154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102,
   3591             6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247,
   3592             82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166,
   3593             216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111,
   3594             215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242,
   3595             140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212,
   3596             249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83,
   3597             31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153,
   3598             162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142,
   3599             24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45,
   3600             50, 23, 3, 25, 253, 87, 227, 79, 119, 161,
   3601         ];
   3602         let p = BigUint::from_bytes_le(
   3603             [
   3604                 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60,
   3605                 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229,
   3606                 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161,
   3607                 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16,
   3608                 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79,
   3609                 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191,
   3610                 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188,
   3611                 24, 247,
   3612             ]
   3613             .as_slice(),
   3614         );
   3615         let p_2 = BigUint::from_bytes_le(
   3616             [
   3617                 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78,
   3618                 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177,
   3619                 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29,
   3620                 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11,
   3621                 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252,
   3622                 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214,
   3623                 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90,
   3624                 250,
   3625             ]
   3626             .as_slice(),
   3627         );
   3628         let rsa_key = RsaKey::<Sha256>::new(
   3629             RsaPrivateKey::from_components(
   3630                 BigUint::from_bytes_le(n.as_slice()),
   3631                 e.into(),
   3632                 BigUint::from_bytes_le(d.as_slice()),
   3633                 vec![p, p_2],
   3634             )
   3635             .unwrap(),
   3636         )
   3637         .verifying_key();
   3638         let n = rsa_key.as_ref().n().to_bytes_be();
   3639         attestation_object[113..369].copy_from_slice(n.as_slice());
   3640         assert!(matches!(opts.start_ceremony()?.0.verify(
   3641             RP_ID,
   3642             &Registration {
   3643                 response: AuthenticatorAttestation::new(
   3644                     client_data_json,
   3645                     attestation_object,
   3646                     AuthTransports::NONE,
   3647                 ),
   3648                 authenticator_attachment: AuthenticatorAttachment::None,
   3649                 client_extension_results: ClientExtensionsOutputs {
   3650                     cred_props: None,
   3651                     prf: None,
   3652                 },
   3653             },
   3654             &RegistrationVerificationOptions::<&str, &str>::default(),
   3655         )?.static_state.credential_public_key, UncompressedPubKey::Rsa(k) if *k.n() == n.as_slice() && k.e() == e));
   3656         Ok(())
   3657     }
   3658     #[test]
   3659     #[cfg(feature = "custom")]
   3660     fn rs256_auth() -> Result<(), AggErr> {
   3661         let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID);
   3662         opts.public_key.challenge = Challenge(0);
   3663         let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec();
   3664         // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information.
   3665         let mut authenticator_data = Vec::with_capacity(69);
   3666         authenticator_data.extend_from_slice(
   3667             [
   3668                 // rpIdHash.
   3669                 // This will be overwritten later.
   3670                 0,
   3671                 0,
   3672                 0,
   3673                 0,
   3674                 0,
   3675                 0,
   3676                 0,
   3677                 0,
   3678                 0,
   3679                 0,
   3680                 0,
   3681                 0,
   3682                 0,
   3683                 0,
   3684                 0,
   3685                 0,
   3686                 0,
   3687                 0,
   3688                 0,
   3689                 0,
   3690                 0,
   3691                 0,
   3692                 0,
   3693                 0,
   3694                 0,
   3695                 0,
   3696                 0,
   3697                 0,
   3698                 0,
   3699                 0,
   3700                 0,
   3701                 0,
   3702                 // flags.
   3703                 // UP and UV (right-to-left).
   3704                 0b0000_0101,
   3705                 // signCount.
   3706                 // 0 as 32-bit big-endian.
   3707                 0,
   3708                 0,
   3709                 0,
   3710                 0,
   3711             ]
   3712             .as_slice(),
   3713         );
   3714         authenticator_data[..32]
   3715             .copy_from_slice(Sha256::digest(RP_ID.as_ref().as_bytes()).as_slice());
   3716         authenticator_data
   3717             .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice());
   3718         let n = [
   3719             111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107,
   3720             195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206,
   3721             185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165,
   3722             100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204,
   3723             145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64,
   3724             87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105,
   3725             81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55,
   3726             117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21,
   3727             254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157,
   3728             108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188,
   3729             42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56,
   3730             104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13,
   3731             72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132,
   3732             153, 79, 0, 133, 78, 7, 218, 165, 241,
   3733         ];
   3734         let e = 65537;
   3735         let d = [
   3736             145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0,
   3737             132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168,
   3738             35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108,
   3739             154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102,
   3740             6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247,
   3741             82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166,
   3742             216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111,
   3743             215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242,
   3744             140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212,
   3745             249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83,
   3746             31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153,
   3747             162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142,
   3748             24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45,
   3749             50, 23, 3, 25, 253, 87, 227, 79, 119, 161,
   3750         ];
   3751         let p = BigUint::from_bytes_le(
   3752             [
   3753                 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60,
   3754                 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229,
   3755                 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161,
   3756                 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16,
   3757                 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79,
   3758                 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191,
   3759                 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188,
   3760                 24, 247,
   3761             ]
   3762             .as_slice(),
   3763         );
   3764         let p_2 = BigUint::from_bytes_le(
   3765             [
   3766                 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78,
   3767                 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177,
   3768                 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29,
   3769                 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11,
   3770                 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252,
   3771                 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214,
   3772                 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90,
   3773                 250,
   3774             ]
   3775             .as_slice(),
   3776         );
   3777         let rsa_key = RsaKey::<Sha256>::new(
   3778             RsaPrivateKey::from_components(
   3779                 BigUint::from_bytes_le(n.as_slice()),
   3780                 e.into(),
   3781                 BigUint::from_bytes_le(d.as_slice()),
   3782                 vec![p, p_2],
   3783             )
   3784             .unwrap(),
   3785         );
   3786         let rsa_pub = rsa_key.verifying_key();
   3787         let sig = rsa_key.sign(authenticator_data.as_slice()).to_vec();
   3788         authenticator_data.truncate(37);
   3789         assert!(!opts.start_ceremony()?.0.verify(
   3790             RP_ID,
   3791             &DiscoverableAuthentication {
   3792                 raw_id: CredentialId::try_from(vec![0; 16])?,
   3793                 response: DiscoverableAuthenticatorAssertion::new(
   3794                     client_data_json,
   3795                     authenticator_data,
   3796                     sig,
   3797                     UserHandle::from([0]),
   3798                 ),
   3799                 authenticator_attachment: AuthenticatorAttachment::None,
   3800             },
   3801             &mut AuthenticatedCredential::new(
   3802                 CredentialId::try_from([0; 16].as_slice())?,
   3803                 &UserHandle::from([0]),
   3804                 StaticState {
   3805                     credential_public_key: CompressedPubKey::<&[u8], &[u8], &[u8], _>::Rsa(
   3806                         RsaPubKey::try_from((rsa_pub.as_ref().n().to_bytes_be(), e)).unwrap(),
   3807                     ),
   3808                     extensions: AuthenticatorExtensionOutputStaticState {
   3809                         cred_protect: CredentialProtectionPolicy::None,
   3810                         hmac_secret: None,
   3811                     },
   3812                     client_extension_results: ClientExtensionsOutputsStaticState { prf: None }
   3813                 },
   3814                 DynamicState {
   3815                     user_verified: true,
   3816                     backup: Backup::NotEligible,
   3817                     sign_count: 0,
   3818                     authenticator_attachment: AuthenticatorAttachment::None,
   3819                 },
   3820             )?,
   3821             &AuthenticationVerificationOptions::<&str, &str>::default(),
   3822         )?);
   3823         Ok(())
   3824     }
   3825 }