webauthn_rp

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

request.rs (138628B)


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