webauthn_rp

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

request.rs (129376B)


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