webauthn_rp

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

response.rs (106467B)


      1 extern crate alloc;
      2 use crate::{
      3     request::{register::{PublicKeyCredentialUserEntity, UserHandle}, Challenge, RpId, Url},
      4     response::{
      5         auth::error::{
      6             AuthCeremonyErr, AuthenticatorDataErr as AuthAuthDataErr,
      7             AuthenticatorExtensionOutputErr as AuthAuthExtErr,
      8         },
      9         error::{CollectedClientDataErr, CredentialIdErr},
     10         register::error::{AttestationObjectErr, AttestedCredentialDataErr, AuthenticatorDataErr as RegAuthDataErr, AuthenticatorExtensionOutputErr as RegAuthExtErr, PubKeyErr, RegCeremonyErr},
     11     },
     12 };
     13 use alloc::borrow::Cow;
     14 use core::{
     15     borrow::Borrow,
     16     cmp::Ordering,
     17     convert::Infallible,
     18     fmt::{self, Display, Formatter},
     19     hash::{Hash, Hasher},
     20     str,
     21 };
     22 use rsa::sha2::{digest::OutputSizeUser as _, Sha256};
     23 #[cfg(feature = "serde_relaxed")]
     24 use ser_relaxed::SerdeJsonErr;
     25 /// Contains functionality for completing the
     26 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony).
     27 ///
     28 /// # Examples
     29 ///
     30 /// ```no_run
     31 /// # use core::convert;
     32 /// # use webauthn_rp::{
     33 /// #     hash::hash_set::FixedCapHashSet,
     34 /// #     request::{auth::{error::InvalidTimeout, DiscoverableAuthenticationClientState, DiscoverableCredentialRequestOptions, AuthenticationVerificationOptions}, register::{UserHandle, USER_HANDLE_MAX_LEN, UserHandle64}, BackupReq, RpId},
     35 /// #     response::{auth::{error::AuthCeremonyErr, DiscoverableAuthentication64}, error::CollectedClientDataErr, register::{AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState, CredentialProtectionPolicy, DynamicState, Ed25519PubKey, CompressedPubKeyOwned, StaticState}, AuthenticatorAttachment, Backup, CollectedClientData, CredentialId},
     36 /// #     AuthenticatedCredential, CredentialErr
     37 /// # };
     38 /// # #[derive(Debug)]
     39 /// # enum E {
     40 /// #     CollectedClientData(CollectedClientDataErr),
     41 /// #     InvalidTimeout(InvalidTimeout),
     42 /// #     SerdeJson(serde_json::Error),
     43 /// #     MissingUserHandle,
     44 /// #     MissingCeremony,
     45 /// #     UnknownCredential,
     46 /// #     Credential(CredentialErr),
     47 /// #     AuthCeremony(AuthCeremonyErr),
     48 /// # }
     49 /// # impl From<CollectedClientDataErr> for E {
     50 /// #     fn from(value: CollectedClientDataErr) -> Self {
     51 /// #         Self::CollectedClientData(value)
     52 /// #     }
     53 /// # }
     54 /// # impl From<InvalidTimeout> for E {
     55 /// #     fn from(value: InvalidTimeout) -> Self {
     56 /// #         Self::InvalidTimeout(value)
     57 /// #     }
     58 /// # }
     59 /// # impl From<serde_json::Error> for E {
     60 /// #     fn from(value: serde_json::Error) -> Self {
     61 /// #         Self::SerdeJson(value)
     62 /// #     }
     63 /// # }
     64 /// # impl From<CredentialErr> for E {
     65 /// #     fn from(value: CredentialErr) -> Self {
     66 /// #         Self::Credential(value)
     67 /// #     }
     68 /// # }
     69 /// # impl From<AuthCeremonyErr> for E {
     70 /// #     fn from(value: AuthCeremonyErr) -> Self {
     71 /// #         Self::AuthCeremony(value)
     72 /// #     }
     73 /// # }
     74 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap();
     75 /// let mut ceremonies = FixedCapHashSet::new(128);
     76 /// let (server, client) = DiscoverableCredentialRequestOptions::passkey(RP_ID).start_ceremony()?;
     77 /// assert!(
     78 ///     ceremonies.insert_remove_all_expired(server).map_or(false, convert::identity)
     79 /// );
     80 /// # #[cfg(feature = "serde")]
     81 /// let authentication = serde_json::from_str::<DiscoverableAuthentication64>(get_authentication_json(client).as_str())?;
     82 /// # #[cfg(feature = "serde")]
     83 /// let user_handle = authentication.response().user_handle();
     84 /// # #[cfg(feature = "serde")]
     85 /// let (static_state, dynamic_state) = get_credential(authentication.raw_id(), &user_handle).ok_or(E::UnknownCredential)?;
     86 /// # #[cfg(all(feature = "custom", feature = "serde"))]
     87 /// let mut cred = AuthenticatedCredential::new(authentication.raw_id(), &user_handle, static_state, dynamic_state)?;
     88 /// # #[cfg(all(feature = "custom", feature = "serde"))]
     89 /// if ceremonies.take(&authentication.challenge()?).ok_or(E::MissingCeremony)?.verify(RP_ID, &authentication, &mut cred, &AuthenticationVerificationOptions::<&str, &str>::default())? {
     90 ///     update_cred(authentication.raw_id(), cred.dynamic_state());
     91 /// }
     92 /// /// Send `DiscoverableAuthenticationClientState` and receive `DiscoverableAuthentication64` JSON from client.
     93 /// # #[cfg(feature = "serde")]
     94 /// fn get_authentication_json(client: DiscoverableAuthenticationClientState<'_, '_, '_>) -> String {
     95 ///     // ⋮
     96 /// #     let client_data_json = base64url_nopad::encode(serde_json::json!({
     97 /// #         "type": "webauthn.get",
     98 /// #         "challenge": client.options().public_key.challenge,
     99 /// #         "origin": format!("https://{}", client.options().public_key.rp_id.as_ref()),
    100 /// #         "crossOrigin": false
    101 /// #     }).to_string().as_bytes());
    102 /// #     serde_json::json!({
    103 /// #         "id": "AAAAAAAAAAAAAAAAAAAAAA",
    104 /// #         "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    105 /// #         "response": {
    106 /// #             "clientDataJSON": client_data_json,
    107 /// #             "authenticatorData": "",
    108 /// #             "signature": "",
    109 /// #             "userHandle": "AA"
    110 /// #         },
    111 /// #         "clientExtensionResults": {},
    112 /// #         "type": "public-key"
    113 /// #     }).to_string()
    114 /// }
    115 /// /// Gets the `AuthenticatedCredential` parts associated with `id` and `user_handle` from the database.
    116 /// fn get_credential(id: CredentialId<&[u8]>, user_handle: &UserHandle64) -> Option<(StaticState<CompressedPubKeyOwned>, DynamicState)> {
    117 ///     // ⋮
    118 /// #     Some((StaticState { credential_public_key: CompressedPubKeyOwned::Ed25519(Ed25519PubKey::from([0; 32])), extensions: AuthenticatorExtensionOutputStaticState { cred_protect: CredentialProtectionPolicy::UserVerificationRequired, hmac_secret: None, }, client_extension_results: ClientExtensionsOutputsStaticState { prf: None, }, }, DynamicState { user_verified: true, backup: Backup::NotEligible, sign_count: 1, authenticator_attachment: AuthenticatorAttachment::None }))
    119 /// }
    120 /// /// Updates the current `DynamicState` associated with `id` in the database to
    121 /// /// `dyn_state`.
    122 /// fn update_cred(id: CredentialId<&[u8]>, dyn_state: DynamicState) {
    123 ///     // ⋮
    124 /// }
    125 /// # Ok::<_, E>(())
    126 /// ```
    127 pub mod auth;
    128 /// Contains functionality to (de)serialize data to a data store.
    129 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    130 #[cfg(feature = "bin")]
    131 pub mod bin;
    132 /// Contains constants useful for
    133 /// [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form).
    134 mod cbor;
    135 /// Contains functionality that needs to be accessible when `bin` or `serde` are not enabled.
    136 #[cfg_attr(docsrs, doc(cfg(feature = "custom")))]
    137 #[cfg(feature = "custom")]
    138 pub mod custom;
    139 /// Contains error types.
    140 pub mod error;
    141 /// Contains functionality for completing the
    142 /// [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony).
    143 ///
    144 /// # Examples
    145 ///
    146 /// ```no_run
    147 /// # use core::convert;
    148 /// # use webauthn_rp::{
    149 /// #     hash::hash_set::FixedCapHashSet,
    150 /// #     request::{register::{error::CreationOptionsErr, CredentialCreationOptions, PublicKeyCredentialUserEntity, RegistrationClientState, UserHandle, UserHandle64, USER_HANDLE_MAX_LEN, RegistrationVerificationOptions}, PublicKeyCredentialDescriptor, RpId},
    151 /// #     response::{register::{error::RegCeremonyErr, Registration}, error::CollectedClientDataErr, CollectedClientData},
    152 /// #     RegisteredCredential
    153 /// # };
    154 /// # #[derive(Debug)]
    155 /// # enum E {
    156 /// #     CollectedClientData(CollectedClientDataErr),
    157 /// #     CreationOptions(CreationOptionsErr),
    158 /// #     SerdeJson(serde_json::Error),
    159 /// #     MissingCeremony,
    160 /// #     RegCeremony(RegCeremonyErr),
    161 /// # }
    162 /// # impl From<CollectedClientDataErr> for E {
    163 /// #     fn from(value: CollectedClientDataErr) -> Self {
    164 /// #         Self::CollectedClientData(value)
    165 /// #     }
    166 /// # }
    167 /// # impl From<CreationOptionsErr> for E {
    168 /// #     fn from(value: CreationOptionsErr) -> Self {
    169 /// #         Self::CreationOptions(value)
    170 /// #     }
    171 /// # }
    172 /// # impl From<serde_json::Error> for E {
    173 /// #     fn from(value: serde_json::Error) -> Self {
    174 /// #         Self::SerdeJson(value)
    175 /// #     }
    176 /// # }
    177 /// # impl From<RegCeremonyErr> for E {
    178 /// #     fn from(value: RegCeremonyErr) -> Self {
    179 /// #         Self::RegCeremony(value)
    180 /// #     }
    181 /// # }
    182 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap();
    183 /// # #[cfg(feature = "custom")]
    184 /// let mut ceremonies = FixedCapHashSet::new(128);
    185 /// # #[cfg(feature = "custom")]
    186 /// let user_handle = get_user_handle();
    187 /// # #[cfg(feature = "custom")]
    188 /// let user = get_user_entity(&user_handle);
    189 /// # #[cfg(feature = "custom")]
    190 /// let creds = get_registered_credentials(user_handle);
    191 /// # #[cfg(feature = "custom")]
    192 /// let (server, client) = CredentialCreationOptions::passkey(RP_ID, user, creds).start_ceremony()?;
    193 /// # #[cfg(feature = "custom")]
    194 /// assert!(
    195 ///     ceremonies.insert_remove_all_expired(server).map_or(false, convert::identity)
    196 /// );
    197 /// # #[cfg(all(feature = "serde_relaxed", feature = "custom"))]
    198 /// let registration = serde_json::from_str::<Registration>(get_registration_json(client).as_str())?;
    199 /// let ver_opts = RegistrationVerificationOptions::<&str, &str>::default();
    200 /// # #[cfg(all(feature = "custom", feature = "serde_relaxed"))]
    201 /// insert_cred(ceremonies.take(&registration.challenge()?).ok_or(E::MissingCeremony)?.verify(RP_ID, &registration, &ver_opts)?);
    202 /// /// Extract `UserHandle` from session cookie if this is not the first credential registered.
    203 /// # #[cfg(feature = "custom")]
    204 /// fn get_user_handle() -> UserHandle64 {
    205 ///     // ⋮
    206 /// #     [0; USER_HANDLE_MAX_LEN].into()
    207 /// }
    208 /// /// Fetch `PublicKeyCredentialUserEntity` info associated with `user`.
    209 /// ///
    210 /// /// If this is the first time a credential is being registered, then `PublicKeyCredentialUserEntity`
    211 /// /// will need to be constructed with `name` and `display_name` passed from the client and `UserHandle::new`
    212 /// /// used for `id`. Once created, this info can be stored such that the entity information
    213 /// /// does not need to be requested for subsequent registrations.
    214 /// # #[cfg(feature = "custom")]
    215 /// fn get_user_entity(user: &UserHandle<USER_HANDLE_MAX_LEN>) -> PublicKeyCredentialUserEntity<'_, '_, '_, USER_HANDLE_MAX_LEN> {
    216 ///     // ⋮
    217 /// #     PublicKeyCredentialUserEntity {
    218 /// #         name: "foo".try_into().unwrap(),
    219 /// #         id: user,
    220 /// #         display_name: None,
    221 /// #     }
    222 /// }
    223 /// /// Send `RegistrationClientState` and receive `Registration` JSON from client.
    224 /// # #[cfg(feature = "serde")]
    225 /// fn get_registration_json(client: RegistrationClientState<'_, '_, '_, '_, '_, '_, USER_HANDLE_MAX_LEN>) -> String {
    226 ///     // ⋮
    227 /// #     let client_data_json = base64url_nopad::encode(serde_json::json!({
    228 /// #         "type": "webauthn.create",
    229 /// #         "challenge": client.options().public_key.challenge,
    230 /// #         "origin": format!("https://{}", client.options().public_key.rp_id.as_ref()),
    231 /// #         "crossOrigin": false
    232 /// #     }).to_string().as_bytes());
    233 /// #     serde_json::json!({
    234 /// #         "response": {
    235 /// #             "clientDataJSON": client_data_json,
    236 /// #             "attestationObject": ""
    237 /// #         }
    238 /// #     }).to_string()
    239 /// }
    240 /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`.
    241 /// ///
    242 /// /// This doesn't need to be called when this is the first credential registered for `user`; instead
    243 /// /// an empty `Vec` should be passed.
    244 /// fn get_registered_credentials(
    245 ///     user: UserHandle<USER_HANDLE_MAX_LEN>,
    246 /// ) -> Vec<PublicKeyCredentialDescriptor<Vec<u8>>> {
    247 ///     // ⋮
    248 /// #     Vec::new()
    249 /// }
    250 /// /// Inserts `RegisteredCredential::into_parts` into the database.
    251 /// fn insert_cred(cred: RegisteredCredential<'_, USER_HANDLE_MAX_LEN>) {
    252 ///     // ⋮
    253 /// }
    254 /// # Ok::<_, E>(())
    255 /// ```
    256 pub mod register;
    257 /// Contains functionality to (de)serialize data to/from a client.
    258 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
    259 #[cfg(feature = "serde")]
    260 pub(crate) mod ser;
    261 /// Contains functionality to deserialize data from a client in a "relaxed" way.
    262 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))]
    263 #[cfg(feature = "serde_relaxed")]
    264 pub mod ser_relaxed;
    265 /// [Backup eligibility](https://www.w3.org/TR/webauthn-3/#backup-eligibility) and
    266 /// [backup state](https://www.w3.org/TR/webauthn-3/#backup-state).
    267 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    268 pub enum Backup {
    269     /// [BE and BS](https://www.w3.org/TR/webauthn-3/#authdata-flags) flags are `0`.
    270     NotEligible,
    271     /// [BE and BS](https://www.w3.org/TR/webauthn-3/#authdata-flags) flags are `1` and `0` respectively.
    272     Eligible,
    273     /// [BE and BS](https://www.w3.org/TR/webauthn-3/#authdata-flags) flags are `1`.
    274     Exists,
    275 }
    276 impl PartialEq<&Self> for Backup {
    277     #[inline]
    278     fn eq(&self, other: &&Self) -> bool {
    279         *self == **other
    280     }
    281 }
    282 impl PartialEq<Backup> for &Backup {
    283     #[inline]
    284     fn eq(&self, other: &Backup) -> bool {
    285         **self == *other
    286     }
    287 }
    288 /// [`AuthenticatorTransport`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatortransport).
    289 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    290 pub enum AuthenticatorTransport {
    291     /// [`ble`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-ble).
    292     Ble,
    293     /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-hybrid).
    294     Hybrid,
    295     /// [`internal`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-internal).
    296     Internal,
    297     /// [`nfc`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-nfc).
    298     Nfc,
    299     /// [`smart-card`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-smart-card).
    300     SmartCard,
    301     /// [`usb`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-usb).
    302     Usb,
    303 }
    304 impl AuthenticatorTransport {
    305     /// Returns the encoded [`u8`] that `self` represents.
    306     const fn to_u8(self) -> u8 {
    307         match self {
    308             Self::Ble => 0x1,
    309             Self::Hybrid => 0x2,
    310             Self::Internal => 0x4,
    311             Self::Nfc => 0x8,
    312             Self::SmartCard => 0x10,
    313             Self::Usb => 0x20,
    314         }
    315     }
    316 }
    317 /// Set of [`AuthenticatorTransport`]s.
    318 #[derive(Clone, Copy, Debug)]
    319 pub struct AuthTransports(u8);
    320 impl AuthTransports {
    321     /// An empty `AuthTransports`.
    322     #[cfg_attr(docsrs, doc(cfg(feature = "custom")))]
    323     #[cfg(feature = "custom")]
    324     pub const NONE: Self = Self::new();
    325     /// An `AuthTransports` containing all possible [`AuthenticatorTransport`]s.
    326     #[cfg_attr(docsrs, doc(cfg(feature = "custom")))]
    327     #[cfg(feature = "custom")]
    328     pub const ALL: Self = Self::all();
    329     /// Construct an empty `AuthTransports`.
    330     #[cfg(any(feature = "bin", feature = "custom", feature = "serde"))]
    331     pub(super) const fn new() -> Self {
    332         Self(0)
    333     }
    334     #[cfg(any(feature = "bin", feature = "custom"))]
    335     /// Construct an `AuthTransports` containing all `AuthenticatorTransport`s.
    336     const fn all() -> Self {
    337         Self::new()
    338             .add_transport(AuthenticatorTransport::Ble)
    339             .add_transport(AuthenticatorTransport::Hybrid)
    340             .add_transport(AuthenticatorTransport::Internal)
    341             .add_transport(AuthenticatorTransport::Nfc)
    342             .add_transport(AuthenticatorTransport::SmartCard)
    343             .add_transport(AuthenticatorTransport::Usb)
    344     }
    345     /// Returns the number of [`AuthenticatorTransport`]s in `self`.
    346     ///
    347     /// # Examples
    348     ///
    349     /// ```
    350     /// # use webauthn_rp::response::AuthTransports;
    351     /// # #[cfg(feature = "custom")]
    352     /// assert_eq!(AuthTransports::ALL.count(), 6);
    353     /// ```
    354     #[inline]
    355     #[must_use]
    356     pub const fn count(self) -> u32 {
    357         self.0.count_ones()
    358     }
    359     /// Returns `true` iff there are no [`AuthenticatorTransport`]s in `self`.
    360     ///
    361     /// # Examples
    362     ///
    363     /// ```
    364     /// # use webauthn_rp::response::AuthTransports;
    365     /// # #[cfg(feature = "custom")]
    366     /// assert!(AuthTransports::NONE.is_empty());
    367     /// ```
    368     #[inline]
    369     #[must_use]
    370     pub const fn is_empty(self) -> bool {
    371         self.0 == 0
    372     }
    373     /// Returns `true` iff `self` contains `transport`.
    374     ///
    375     /// # Examples
    376     ///
    377     /// ```
    378     /// # use webauthn_rp::response::{AuthTransports, AuthenticatorTransport};
    379     /// # #[cfg(feature = "custom")]
    380     /// assert!(AuthTransports::ALL.contains(AuthenticatorTransport::Ble));
    381     /// ```
    382     #[inline]
    383     #[must_use]
    384     pub const fn contains(self, transport: AuthenticatorTransport) -> bool {
    385         let val = transport.to_u8();
    386         self.0 & val == val
    387     }
    388     /// Returns a copy of `self` with `transport` added.
    389     ///
    390     /// `self` is returned iff `transport` already exists.
    391     #[cfg(any(feature = "bin", feature = "custom", feature = "serde"))]
    392     const fn add_transport(self, transport: AuthenticatorTransport) -> Self {
    393         Self(self.0 | transport.to_u8())
    394     }
    395     /// Returns a copy of `self` with `transport` added.
    396     ///
    397     /// `self` is returned iff `transport` already exists.
    398     ///
    399     /// # Examples
    400     ///
    401     /// ```
    402     /// # use webauthn_rp::response::{AuthTransports, AuthenticatorTransport};
    403     /// assert_eq!(
    404     ///     AuthTransports::NONE
    405     ///         .add(AuthenticatorTransport::Usb)
    406     ///         .count(),
    407     ///     1
    408     /// );
    409     /// assert_eq!(
    410     ///     AuthTransports::ALL.add(AuthenticatorTransport::Usb).count(),
    411     ///     6
    412     /// );
    413     /// ```
    414     #[cfg_attr(docsrs, doc(cfg(feature = "custom")))]
    415     #[cfg(feature = "custom")]
    416     #[inline]
    417     #[must_use]
    418     pub const fn add(self, transport: AuthenticatorTransport) -> Self {
    419         self.add_transport(transport)
    420     }
    421     /// Returns a copy of `self` with `transport` removed.
    422     ///
    423     /// `self` is returned iff `transport` did not exist.
    424     ///
    425     /// # Examples
    426     ///
    427     /// ```
    428     /// # use webauthn_rp::response::{AuthTransports, AuthenticatorTransport};
    429     /// assert_eq!(
    430     ///     AuthTransports::ALL
    431     ///         .remove(AuthenticatorTransport::Internal)
    432     ///         .count(),
    433     ///     5
    434     /// );
    435     /// assert_eq!(
    436     ///     AuthTransports::NONE.remove(AuthenticatorTransport::Usb).count(),
    437     ///     0
    438     /// );
    439     /// ```
    440     #[cfg_attr(docsrs, doc(cfg(feature = "custom")))]
    441     #[cfg(feature = "custom")]
    442     #[inline]
    443     #[must_use]
    444     pub const fn remove(self, transport: AuthenticatorTransport) -> Self {
    445         Self(self.0 & !transport.to_u8())
    446     }
    447 }
    448 /// [`AuthenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatorattachment).
    449 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    450 pub enum AuthenticatorAttachment {
    451     /// No attachment information.
    452     None,
    453     /// [`platform`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-platform).
    454     Platform,
    455     /// [`cross-platform`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-cross-platform).
    456     CrossPlatform,
    457 }
    458 impl PartialEq<&Self> for AuthenticatorAttachment {
    459     #[inline]
    460     fn eq(&self, other: &&Self) -> bool {
    461         *self == **other
    462     }
    463 }
    464 impl PartialEq<AuthenticatorAttachment> for &AuthenticatorAttachment {
    465     #[inline]
    466     fn eq(&self, other: &AuthenticatorAttachment) -> bool {
    467         **self == *other
    468     }
    469 }
    470 /// The maximum number of bytes that can make up a Credential ID
    471 /// [per WebAuthn](https://www.w3.org/TR/webauthn-3/#credential-id).
    472 pub const CRED_ID_MAX_LEN: usize = 1023;
    473 /// The minimum number of bytes that can make up a Credential ID
    474 /// [per WebAuthn](https://www.w3.org/TR/webauthn-3/#credential-id).
    475 ///
    476 /// The spec does not call out this value directly instead it states the following:
    477 ///
    478 /// > Credential IDs are generated by authenticators in two forms:
    479 /// >
    480 /// > * At least 16 bytes that include at least 100 bits of entropy, or
    481 /// > * The [public key credential source](https://www.w3.org/TR/webauthn-3/#public-key-credential-source),
    482 /// >   without its Credential ID or mutable items, encrypted so only its managing
    483 /// >   authenticator can decrypt it. This form allows the authenticator to be nearly
    484 /// >   stateless, by having the Relying Party store any necessary state.
    485 ///
    486 /// One of the immutable items of the public key credential source is the private key
    487 /// which for any real-world signature algorithm will always be at least 16 bytes.
    488 pub const CRED_ID_MIN_LEN: usize = 16;
    489 /// A [Credential ID](https://www.w3.org/TR/webauthn-3/#credential-id) that is made up of
    490 /// [`CRED_ID_MIN_LEN`]–[`CRED_ID_MAX_LEN`] bytes.
    491 #[derive(Clone, Copy, Debug)]
    492 pub struct CredentialId<T>(T);
    493 impl<T> CredentialId<T> {
    494     /// Returns the contained data consuming `self`.
    495     #[inline]
    496     pub fn into_inner(self) -> T {
    497         self.0
    498     }
    499     /// Returns the contained data.
    500     #[inline]
    501     pub const fn inner(&self) -> &T {
    502         &self.0
    503     }
    504 }
    505 impl<'a> CredentialId<&'a [u8]> {
    506     /// Creates a `CredentialId` from a `slice`.
    507     #[expect(single_use_lifetimes, reason = "false positive")]
    508     fn from_slice<'b: 'a>(value: &'b [u8]) -> Result<Self, CredentialIdErr> {
    509         if (CRED_ID_MIN_LEN..=CRED_ID_MAX_LEN).contains(&value.len()) {
    510             Ok(Self(value))
    511         } else {
    512             Err(CredentialIdErr)
    513         }
    514     }
    515 }
    516 impl<T: AsRef<[u8]>> AsRef<[u8]> for CredentialId<T> {
    517     #[inline]
    518     fn as_ref(&self) -> &[u8] {
    519         self.0.as_ref()
    520     }
    521 }
    522 impl<T: Borrow<[u8]>> Borrow<[u8]> for CredentialId<T> {
    523     #[inline]
    524     fn borrow(&self) -> &[u8] {
    525         self.0.borrow()
    526     }
    527 }
    528 impl<'a: 'b, 'b> From<&'a CredentialId<Vec<u8>>> for CredentialId<&'b Vec<u8>> {
    529     #[inline]
    530     fn from(value: &'a CredentialId<Vec<u8>>) -> Self {
    531         Self(&value.0)
    532     }
    533 }
    534 impl<'a: 'b, 'b> From<CredentialId<&'a Vec<u8>>> for CredentialId<&'b [u8]> {
    535     #[inline]
    536     fn from(value: CredentialId<&'a Vec<u8>>) -> Self {
    537         Self(value.0.as_slice())
    538     }
    539 }
    540 impl<'a: 'b, 'b> From<&'a CredentialId<Vec<u8>>> for CredentialId<&'b [u8]> {
    541     #[inline]
    542     fn from(value: &'a CredentialId<Vec<u8>>) -> Self {
    543         Self(value.0.as_slice())
    544     }
    545 }
    546 impl From<CredentialId<&[u8]>> for CredentialId<Vec<u8>> {
    547     #[inline]
    548     fn from(value: CredentialId<&[u8]>) -> Self {
    549         Self(value.0.to_owned())
    550     }
    551 }
    552 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CredentialId<T>> for CredentialId<T2> {
    553     #[inline]
    554     fn eq(&self, other: &CredentialId<T>) -> bool {
    555         self.0 == other.0
    556     }
    557 }
    558 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CredentialId<T>> for &CredentialId<T2> {
    559     #[inline]
    560     fn eq(&self, other: &CredentialId<T>) -> bool {
    561         **self == *other
    562     }
    563 }
    564 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CredentialId<T>> for CredentialId<T2> {
    565     #[inline]
    566     fn eq(&self, other: &&CredentialId<T>) -> bool {
    567         *self == **other
    568     }
    569 }
    570 impl<T: Eq> Eq for CredentialId<T> {}
    571 impl<T: Hash> Hash for CredentialId<T> {
    572     #[inline]
    573     fn hash<H: Hasher>(&self, state: &mut H) {
    574         self.0.hash(state);
    575     }
    576 }
    577 impl<T: PartialOrd<T2>, T2: PartialOrd<T>> PartialOrd<CredentialId<T>> for CredentialId<T2> {
    578     #[inline]
    579     fn partial_cmp(&self, other: &CredentialId<T>) -> Option<Ordering> {
    580         self.0.partial_cmp(&other.0)
    581     }
    582 }
    583 impl<T: Ord> Ord for CredentialId<T> {
    584     #[inline]
    585     fn cmp(&self, other: &Self) -> Ordering {
    586         self.0.cmp(&other.0)
    587     }
    588 }
    589 // We define a separate type to ensure challenges sent to the client are always randomly generated;
    590 // otherwise one could deserialize arbitrary data into a `Challenge`.
    591 /// Copy of [`Challenge`] sent back from the client.
    592 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    593 pub struct SentChallenge(pub u128);
    594 impl SentChallenge {
    595     /// Transforms `value` into a `SentChallenge` by interpreting `value` as a
    596     /// little-endian `u128`.
    597     #[expect(clippy::little_endian_bytes, reason = "Challenge and SentChallenge need to be compatible, and we need to ensure the data is sent and received in the same order")]
    598     #[inline]
    599     #[must_use]
    600     pub const fn from_array(value: [u8; 16]) -> Self {
    601         Self(u128::from_le_bytes(value))
    602     }
    603     /// Transforms `value` into a `SentChallenge`.
    604     #[inline]
    605     #[must_use]
    606     pub const fn from_challenge(value: Challenge) -> Self {
    607         Self(value.into_data())
    608     }
    609 }
    610 impl From<Challenge> for SentChallenge {
    611     #[inline]
    612     fn from(value: Challenge) -> Self {
    613         Self::from_challenge(value)
    614     }
    615 }
    616 impl From<[u8; 16]> for SentChallenge {
    617     #[inline]
    618     fn from(value: [u8; 16]) -> Self {
    619         Self::from_array(value)
    620     }
    621 }
    622 impl PartialEq<&Self> for SentChallenge {
    623     #[inline]
    624     fn eq(&self, other: &&Self) -> bool {
    625         *self == **other
    626     }
    627 }
    628 impl PartialEq<SentChallenge> for &SentChallenge {
    629     #[inline]
    630     fn eq(&self, other: &SentChallenge) -> bool {
    631         **self == *other
    632     }
    633 }
    634 impl PartialOrd for SentChallenge {
    635     #[inline]
    636     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    637         Some(self.cmp(other))
    638     }
    639 }
    640 impl Ord for SentChallenge {
    641     #[inline]
    642     fn cmp(&self, other: &Self) -> Ordering {
    643         self.0.cmp(&other.0)
    644     }
    645 }
    646 impl Hash for SentChallenge {
    647     #[inline]
    648     fn hash<H: Hasher>(&self, state: &mut H) {
    649         state.write_u128(self.0);
    650     }
    651 }
    652 /// An [`origin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-origin) or
    653 /// [`topOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-toporigin).
    654 #[derive(Debug, Eq)]
    655 pub struct Origin<'a>(pub Cow<'a, str>);
    656 impl PartialEq<Origin<'_>> for Origin<'_> {
    657     #[inline]
    658     fn eq(&self, other: &Origin<'_>) -> bool {
    659         self.0 == other.0
    660     }
    661 }
    662 impl PartialEq<&Origin<'_>> for Origin<'_> {
    663     #[inline]
    664     fn eq(&self, other: &&Origin<'_>) -> bool {
    665         *self == **other
    666     }
    667 }
    668 impl PartialEq<Origin<'_>> for &Origin<'_> {
    669     #[inline]
    670     fn eq(&self, other: &Origin<'_>) -> bool {
    671         **self == *other
    672     }
    673 }
    674 impl PartialEq<str> for Origin<'_> {
    675     #[inline]
    676     fn eq(&self, other: &str) -> bool {
    677         self.0.as_ref() == other
    678     }
    679 }
    680 impl PartialEq<Origin<'_>> for str {
    681     #[inline]
    682     fn eq(&self, other: &Origin<'_>) -> bool {
    683         *other == *self
    684     }
    685 }
    686 impl PartialEq<&str> for Origin<'_> {
    687     #[inline]
    688     fn eq(&self, other: &&str) -> bool {
    689         *self == **other
    690     }
    691 }
    692 impl PartialEq<Origin<'_>> for &str {
    693     #[inline]
    694     fn eq(&self, other: &Origin<'_>) -> bool {
    695         **self == *other
    696     }
    697 }
    698 impl PartialEq<String> for Origin<'_> {
    699     #[inline]
    700     fn eq(&self, other: &String) -> bool {
    701         self.0 == *other
    702     }
    703 }
    704 impl PartialEq<Origin<'_>> for String {
    705     #[inline]
    706     fn eq(&self, other: &Origin<'_>) -> bool {
    707         *other == *self
    708     }
    709 }
    710 impl PartialEq<Url> for Origin<'_> {
    711     #[inline]
    712     fn eq(&self, other: &Url) -> bool {
    713         self.0.as_ref() == other.as_ref()
    714     }
    715 }
    716 impl PartialEq<Origin<'_>> for Url {
    717     #[inline]
    718     fn eq(&self, other: &Origin<'_>) -> bool {
    719         *other == *self
    720     }
    721 }
    722 impl PartialEq<&Url> for Origin<'_> {
    723     #[inline]
    724     fn eq(&self, other: &&Url) -> bool {
    725         *self == **other
    726     }
    727 }
    728 impl PartialEq<Origin<'_>> for &Url {
    729     #[inline]
    730     fn eq(&self, other: &Origin<'_>) -> bool {
    731         **self == *other
    732     }
    733 }
    734 /// [Authenticator data flags](https://www.w3.org/TR/webauthn-3/#authdata-flags).
    735 #[derive(Clone, Copy, Debug)]
    736 pub struct Flag {
    737     /// [`UP` flag](https://www.w3.org/TR/webauthn-3/#authdata-flags-up).
    738     ///
    739     /// Note this is always `true` when part of [`auth::AuthenticatorData::flags`].
    740     pub user_present: bool,
    741     /// [`UV` flag](https://www.w3.org/TR/webauthn-3/#concept-user-verified).
    742     pub user_verified: bool,
    743     /// [`BE`](https://www.w3.org/TR/webauthn-3/#backup-eligibility) and
    744     /// [`BS`](https://www.w3.org/TR/webauthn-3/#backup-state) flags.
    745     pub backup: Backup,
    746 }
    747 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
    748 pub(super) trait AuthData<'a>: Sized {
    749     /// Error returned by [`Self::user_is_not_present`].
    750     ///
    751     /// This should be [`Infallible`] in the event user must not always be present.
    752     type UpBitErr;
    753     /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata).
    754     type CredData;
    755     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions).
    756     type Ext: AuthExtOutput + Copy;
    757     /// Errors iff the user must always be present.
    758     fn user_is_not_present() -> Result<(), Self::UpBitErr>;
    759     /// `true` iff `AT` bit (i.e., bit 6) in [`Self::flag_data`] can and must be set to 1.
    760     fn contains_at_bit() -> bool;
    761     /// Constructor.
    762     fn new(rp_id_hash: &'a [u8], flags: Flag, sign_count: u32, attested_credential_data: Self::CredData, extensions: Self::Ext) -> Self;
    763     /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash).
    764     fn rp_hash(&self) -> &'a [u8];
    765     /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags).
    766     fn flag(&self) -> Flag;
    767 }
    768 /// [`CollectedClientData`](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata).
    769 #[derive(Debug)]
    770 pub struct CollectedClientData<'a> {
    771     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge).
    772     pub challenge: SentChallenge,
    773     /// [`origin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-origin).
    774     pub origin: Origin<'a>,
    775     /// [`crossOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-crossorigin).
    776     pub cross_origin: bool,
    777     /// [`topOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-toporigin).
    778     ///
    779     /// When `CollectedClientData` is constructed via [`Self::from_client_data_json`], this can only be
    780     /// `Some` if [`Self::cross_origin`]; and if `Some`, it will be different than [`Self::origin`].
    781     pub top_origin: Option<Origin<'a>>,
    782 }
    783 impl<'a> CollectedClientData<'a> {
    784     /// Parses `json` based on the
    785     /// [limited verification algorithm](https://www.w3.org/TR/webauthn-3/#clientdatajson-verification).
    786     ///
    787     /// Additionally, [`topOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-toporigin) is only
    788     /// allowed to exist if it has a different value than
    789     /// [`origin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-origin) and
    790     /// [`crossOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-crossorigin) is `true`.
    791     ///
    792     /// `REGISTRATION` iff [`type`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-type) must be
    793     /// `"webauthn.create"`; otherwise it must be `"webauthn.get"`.
    794     ///
    795     /// # Errors
    796     ///
    797     /// Errors iff `json` cannot be parsed based on the aforementioned requirements.
    798     ///
    799     /// # Examples
    800     ///
    801     /// ```
    802     /// # use webauthn_rp::response::{error::CollectedClientDataErr, CollectedClientData};
    803     /// assert!(!CollectedClientData::from_client_data_json::<true>(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice())?.cross_origin);
    804     /// assert!(!CollectedClientData::from_client_data_json::<false>(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice())?.cross_origin);
    805     /// # Ok::<_, CollectedClientDataErr>(())
    806     /// ```
    807     #[expect(single_use_lifetimes, reason = "false positive")]
    808     #[inline]
    809     pub fn from_client_data_json<'b: 'a, const REGISTRATION: bool>(json: &'b [u8]) -> Result<Self, CollectedClientDataErr> {
    810         LimitedVerificationParser::<REGISTRATION>::parse(json)
    811     }
    812     /// Parses `json` in a "relaxed" way.
    813     ///
    814     /// Unlike [`Self::from_client_data_json`] which requires `json` to be an output from the
    815     /// [JSON-compatible serialization of client data](https://www.w3.org/TR/webauthn-3/#clientdatajson-serialization),
    816     /// this parses `json` based entirely on the
    817     /// [`CollectedClientData`](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata) Web IDL `dictionary`.
    818     ///
    819     /// L1 clients predate the JSON-compatible serialization of client data; additionally there are L2 and L3
    820     /// clients that don't adhere to the JSON-compatible serialization of client data despite being required to.
    821     /// These clients serialize `CollectedClientData` so that it's valid JSON and conforms to the Web IDL `dictionary`
    822     /// and nothing more. Furthermore, when not relying on the
    823     /// [limited verification algorithm](https://www.w3.org/TR/webauthn-3/#clientdatajson-verification), the spec
    824     /// requires the data to be decoded in a way equivalent to
    825     /// [UTF-8 decode](https://encoding.spec.whatwg.org/#utf-8-decode) which both interprets a leading zero
    826     /// width no-breaking space (i.e., U+FEFF) as a byte-order mark (BOM) as well as replaces any sequences of
    827     /// invalid UTF-8 code units with the replacement character (i.e., U+FFFD). That is precisely what this
    828     /// function does.
    829     ///
    830     /// # Errors
    831     ///
    832     /// Errors iff any of the following is true:
    833     /// * The payload is not valid JSON _after_ ignoring a leading U+FEFF and replacing any sequences of invalid
    834     ///   UTF-8 code units with U+FFFD.
    835     /// * The JSON does not conform to the Web IDL `dictionary`.
    836     /// * [`type`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-type) is not `"webauthn.create"`
    837     ///   or `"webauthn.get"` when `REGISTRATION` and `!REGISTRATION` respectively.
    838     /// * [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge) is not a
    839     ///   base64url-encoded [`Challenge`].
    840     /// * Existence of duplicate keys for the keys that are expected.
    841     ///
    842     /// # Examples
    843     ///
    844     /// ```
    845     /// # use webauthn_rp::response::{ser_relaxed::SerdeJsonErr, CollectedClientData};
    846     /// assert!(!CollectedClientData::from_client_data_json_relaxed::<true>(b"\xef\xbb\xbf{
    847     ///   \"type\": \"webauthn.create\",
    848     ///   \"origin\": \"https://example.com\",
    849     ///   \"f\xffo\": 123,
    850     ///   \"topOrigin\": \"https://example.com\",
    851     ///   \"challenge\": \"AAAAAAAAAAAAAAAAAAAAAA\"
    852     /// }")?.cross_origin);
    853     /// # Ok::<_, SerdeJsonErr>(())
    854     /// ```
    855     #[expect(single_use_lifetimes, reason = "false positive")]
    856     #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))]
    857     #[cfg(feature = "serde_relaxed")]
    858     #[inline]
    859     pub fn from_client_data_json_relaxed<'b: 'a, const REGISTRATION: bool>(json: &'b [u8]) -> Result<Self, SerdeJsonErr> {
    860         ser_relaxed::RelaxedClientDataJsonParser::<REGISTRATION>::parse(json)
    861     }
    862 }
    863 /// Parser of 
    864 /// [`JSON-compatible serialization of client data`](https://www.w3.org/TR/webauthn-3/#collectedclientdata-json-compatible-serialization-of-client-data).
    865 trait ClientDataJsonParser {
    866     /// Error returned by [`Self::parse`].
    867     type Err;
    868     /// Parses `json` into `CollectedClientData` based on the value of
    869     /// [`type`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-type).
    870     ///
    871     /// # Errors
    872     ///
    873     /// Errors iff `json` cannot be parsed into a `CollectedClientData`.
    874     fn parse(json: &[u8]) -> Result<CollectedClientData<'_>, Self::Err>;
    875     /// Extracts [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge)
    876     /// from `json`.
    877     ///
    878     /// Note `json` should be minimally parsed such that only `challenge` is extracted; thus
    879     /// `Ok` being returned does _not_ mean `json` is in fact valid.
    880     fn get_sent_challenge(json: &[u8]) -> Result<SentChallenge, Self::Err>;
    881 }
    882 /// [`ClientDataJsonParser`] based on the
    883 /// [limited verification algorithm](https://www.w3.org/TR/webauthn-3/#clientdatajson-verification)
    884 /// with the following additional requirements:
    885 /// * Unknown keys are not allowed.
    886 /// * The entire payload is parsed; thus the payload is guaranteed to be valid UTF-8 and JSON.
    887 /// * [`CollectedClientData::top_origin`] can only be `Some` if
    888 ///   [`crossOrigin`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-crossorigin).
    889 /// * If `CollectedClientData::top_origin` is `Some`, then it does not equal [`CollectedClientData::origin`].
    890 ///
    891 /// `REGISTRATION` iff [`ClientDataJsonParser::parse`] requires
    892 /// [`type`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-type) to be `"webauthn.create"`;
    893 /// otherwise it must be `"webauthn.get"`.
    894 struct LimitedVerificationParser<const REGISTRATION: bool>;
    895 impl<const R: bool> LimitedVerificationParser<R> {
    896     /// Parses `val` as a JSON string with possibly trailing data. `val` MUST NOT begin with an opening quote. Upon
    897     /// encountering the first non-escaped quote, the parsed value is returned in addition to the remaining
    898     /// portion of `val` _after_ the closing quote. The limited verification algorithm is adhered to; thus the
    899     /// _only_ Unicode scalar values that are allowed (and must) be hex-escaped are U+0000 to U+001F inclusively.
    900     /// Similarly only `b'\\'` and `b'"'` are allowed (and must) be escaped with `b'\\'`.
    901     #[expect(unsafe_code, reason = "comment justifies its correctness")] 
    902     #[expect(clippy::arithmetic_side_effects, clippy::indexing_slicing, reason = "comments justify their correctness")]
    903     fn parse_string(val: &[u8]) -> Result<(Cow<'_, str>, &'_ [u8]), CollectedClientDataErr> {
    904         /// Tracks the state of the current Unicode scalar value that is being parsed.
    905         enum State {
    906             /// We are not parsing `'"'`, `'\\'`, or U+0000 to U+001F.
    907             Normal,
    908             /// We just encountered the escape character.
    909             Escape,
    910             /// We just encountered `b"\\u"`.
    911             UnicodeEscape,
    912             /// We just encountered `b"\\u0"`.
    913             UnicodeHex1,
    914             /// We just encountered `b"\\u00"`.
    915             UnicodeHex2,
    916             /// We just encountered `b"\\u000"` or `b"\\u001"`. The contained `u8` is `0` iff the former; otherwise
    917             /// `0x10`.
    918             UnicodeHex3(u8),
    919         }
    920         // We parse this as UTF-8 only at the end iff it is not empty. This contains all the potential Unicode scalar
    921         // values after de-escaping.
    922         let mut utf8 = Vec::new();
    923         // We check for all `u8`s already; thus we might as well check if we encounter a non-ASCII `u8`.
    924         // If we don't, then we can rely on `str::from_utf8_unchecked`.
    925         let mut all_ascii = true;
    926         // This tracks the start index of the next slice to add. We add slices iff we encounter the escape character or
    927         // we return the parsed `Cow` (i.e., encounter an unescaped `b'"'`).
    928         let mut cur_idx = 0;
    929         // The state of the yet-to-be-parsed Unicode scalar value.
    930         let mut state = State::Normal;
    931         for (counter, &b) in val.iter().enumerate() {
    932             match state {
    933                 State::Normal => {
    934                     match b {
    935                         b'"' => {
    936                             if utf8.is_empty() {
    937                                 if all_ascii {
    938                                     // `cur_idx` is 0 or 1. The latter is true iff `val` starts with a
    939                                     // `b'\\'` or `b'"'` but contains no other escaped characters.
    940                                     let s = &val[cur_idx..counter];
    941                                     // SAFETY:
    942                                     // `all_ascii` is `false` iff we encountered any `u8` that was not
    943                                     // an ASCII `u8`; thus we know `s` is valid ASCII which in turn means
    944                                     // it's valid UTF-8.
    945                                     let v = unsafe { str::from_utf8_unchecked(s) };
    946                                     // `val.len() > counter`, so indexing is fine and overflow cannot happen.
    947                                     return Ok((Cow::Borrowed(v), &val[counter + 1..]));
    948                                 }
    949                                 // `cur_idx` is 0 or 1. The latter is true iff `val` starts with a
    950                                 // `b'\\'` or `b'"'` but contains no other escaped characters.
    951                                 return str::from_utf8(&val[cur_idx..counter])
    952                                     .map_err(CollectedClientDataErr::Utf8)
    953                                     // `val.len() > counter`, so indexing is fine and overflow cannot happen.
    954                                     .map(|v| (Cow::Borrowed(v), &val[counter + 1..]));
    955                             }
    956                             // `val.len() > counter && counter >= cur_idx`, so indexing is fine and overflow
    957                             // cannot happen.
    958                             utf8.extend_from_slice(&val[cur_idx..counter]);
    959                             if all_ascii {
    960                                 // SAFETY:
    961                                 // `all_ascii` is `false` iff we encountered any `u8` that was not
    962                                 // an ASCII `u8`; thus we know `utf8` is valid ASCII which in turn means
    963                                 // it's valid UTF-8.
    964                                 let v = unsafe { String::from_utf8_unchecked(utf8) };
    965                                 // `val.len() > counter`, so indexing is fine and overflow cannot happen.
    966                                 return Ok((Cow::Owned(v), &val[counter + 1..]));
    967                             }
    968                             return String::from_utf8(utf8)
    969                                 .map_err(CollectedClientDataErr::Utf8Owned)
    970                                 // `val.len() > counter`, so indexing is fine and overflow cannot happen.
    971                                 .map(|v| (Cow::Owned(v), &val[counter + 1..]));
    972                         }
    973                         b'\\' => {
    974                             // Write the current slice of data.
    975                             utf8.extend_from_slice(&val[cur_idx..counter]);
    976                             state = State::Escape;
    977                         }
    978                         // ASCII is a subset of UTF-8 and this is a subset of ASCII. The code unit that is used for an
    979                         // ASCII Unicode scalar value _never_ appears in multi-code-unit Unicode scalar values; thus we
    980                         // error immediately.
    981                         ..=0x1f => return Err(CollectedClientDataErr::InvalidEscapedString),
    982                         128.. => all_ascii = false,
    983                         _ => (),
    984                     }
    985                 }
    986                 State::Escape => {
    987                     match b {
    988                         b'"' | b'\\' => {
    989                             // We start the next slice here since we need to add it.
    990                             cur_idx = counter;
    991                             state = State::Normal;
    992                         }
    993                         b'u' => {
    994                             state = State::UnicodeEscape;
    995                         }
    996                         _ => {
    997                             return Err(CollectedClientDataErr::InvalidEscapedString);
    998                         }
    999                     }
   1000                 }
   1001                 State::UnicodeEscape => {
   1002                     if b != b'0' {
   1003                         return Err(CollectedClientDataErr::InvalidEscapedString);
   1004                     }
   1005                     state = State::UnicodeHex1;
   1006                 }
   1007                 State::UnicodeHex1 => {
   1008                     if b != b'0' {
   1009                         return Err(CollectedClientDataErr::InvalidEscapedString);
   1010                     }
   1011                     state = State::UnicodeHex2;
   1012                 }
   1013                 State::UnicodeHex2 => {
   1014                     state = State::UnicodeHex3(match b {
   1015                         b'0' => 0,
   1016                         b'1' => 0x10,
   1017                         _ => return Err(CollectedClientDataErr::InvalidEscapedString),
   1018                     });
   1019                 }
   1020                 State::UnicodeHex3(v) => {
   1021                     match b {
   1022                         // Only and all _lowercase_ hex is allowed.
   1023                         b'0'..=b'9' | b'a'..=b'f' => {
   1024                             // When `b < b'a'`, then `b >= b'0'`; and `b'a' > 87`; thus underflow cannot happen.
   1025                             // Note `b'a' - 10 == 87`.
   1026                             utf8.push(v | (b - if b < b'a' { b'0' } else { 87 }));
   1027                             // `counter < val.len()`, so overflow cannot happen.
   1028                             cur_idx = counter + 1;
   1029                             state = State::Normal;
   1030                         }
   1031                         _ => return Err(CollectedClientDataErr::InvalidEscapedString),
   1032                     }
   1033                 }
   1034             }
   1035         }
   1036         // We never encountered an unescaped `b'"'`; thus we could not parse a string.
   1037         Err(CollectedClientDataErr::InvalidObject)
   1038     }
   1039 }
   1040 impl<const R: bool> ClientDataJsonParser for LimitedVerificationParser<R> {
   1041     type Err = CollectedClientDataErr;
   1042     #[expect(clippy::little_endian_bytes, reason = "Challenge::serialize and this need to be consistent across architectures")]
   1043     #[expect(clippy::too_many_lines, reason = "110 lines is fine")]
   1044     fn parse(json: &[u8]) -> Result<CollectedClientData<'_>, Self::Err> {
   1045         // `{"type":"webauthn.<create|get>","challenge":"<22 bytes>","origin":"<bytes>","crossOrigin":<true|false>[,"topOrigin":"<bytes>"][,<anything>]}`.
   1046         /// First portion of `value`.
   1047         const HEADER: &[u8; 18] = br#"{"type":"webauthn."#;
   1048         /// `get`.
   1049         const GET: &[u8; 3] = b"get";
   1050         /// `create`.
   1051         const CREATE: &[u8; 6] = b"create";
   1052         /// Value after type before the start of the base64url-encoded challenge.
   1053         const AFTER_TYPE: &[u8; 15] = br#"","challenge":""#;
   1054         /// Value after challenge before the start of the origin value.
   1055         const AFTER_CHALLENGE: &[u8; 12] = br#"","origin":""#;
   1056         /// Value after origin before the start of the crossOrigin value.
   1057         const AFTER_ORIGIN: &[u8; 15] = br#","crossOrigin":"#;
   1058         /// `true`.
   1059         const TRUE: &[u8; 4] = b"true";
   1060         /// `false`.
   1061         const FALSE: &[u8; 5] = b"false";
   1062         /// Value after crossOrigin before the start of the topOrigin value.
   1063         const AFTER_CROSS: &[u8; 13] = br#""topOrigin":""#;
   1064         json.split_last().ok_or(CollectedClientDataErr::Len).and_then(|(last, last_rem)| {
   1065             if *last == b'}' {
   1066                 last_rem.split_at_checked(HEADER.len()).ok_or(CollectedClientDataErr::Len).and_then(|(header, header_rem)| {
   1067                     if header == HEADER {
   1068                         if R {
   1069                             header_rem.split_at_checked(CREATE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(create, create_rem)| {
   1070                                 if create == CREATE {
   1071                                     Ok(create_rem)
   1072                                 } else {
   1073                                     Err(CollectedClientDataErr::Type)
   1074                                 }
   1075                             })
   1076                         } else {
   1077                             header_rem.split_at_checked(GET.len()).ok_or(CollectedClientDataErr::Len).and_then(|(get, get_rem)| {
   1078                                 if get == GET {
   1079                                     Ok(get_rem)
   1080                                 } else {
   1081                                     Err(CollectedClientDataErr::Type)
   1082                                 }
   1083                             })
   1084                         }.and_then(|type_rem| {
   1085                             type_rem.split_at_checked(AFTER_TYPE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(chall_key, chall_key_rem)| {
   1086                                 if chall_key == AFTER_TYPE {
   1087                                     chall_key_rem.split_at_checked(Challenge::BASE64_LEN).ok_or(CollectedClientDataErr::Len).and_then(|(base64_chall, base64_chall_rem)| {
   1088                                         let mut chall = [0; 16];
   1089                                         base64url_nopad::decode_buffer_exact(base64_chall, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).and_then(|()| {
   1090                                             base64_chall_rem.split_at_checked(AFTER_CHALLENGE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(origin_key, origin_key_rem)| {
   1091                                                 if origin_key == AFTER_CHALLENGE {
   1092                                                     Self::parse_string(origin_key_rem).and_then(|(origin, origin_rem)| {
   1093                                                         origin_rem.split_at_checked(AFTER_ORIGIN.len()).ok_or(CollectedClientDataErr::Len).and_then(|(cross_key, cross_key_rem)| {
   1094                                                             if cross_key == AFTER_ORIGIN {
   1095                                                                 // `FALSE.len() > TRUE.len()`, so we check for `FALSE` in `and_then`.
   1096                                                                 cross_key_rem.split_at_checked(TRUE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(cross_true, cross_true_rem)| {
   1097                                                                     if cross_true == TRUE {
   1098                                                                         Ok((true, cross_true_rem))
   1099                                                                     } else {
   1100                                                                         cross_key_rem.split_at_checked(FALSE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(cross_false, cross_false_rem)| {
   1101                                                                             if cross_false == FALSE {
   1102                                                                                 Ok((false, cross_false_rem))
   1103                                                                             } else {
   1104                                                                                 Err(CollectedClientDataErr::CrossOrigin)
   1105                                                                             }
   1106                                                                         })
   1107                                                                     }.and_then(|(cross, cross_rem)| {
   1108                                                                         cross_rem.split_first().map_or(Ok((cross, None)), |(comma, comma_rem)| {
   1109                                                                             if *comma == b',' {
   1110                                                                                 comma_rem.split_at_checked(AFTER_CROSS.len()).map_or(Ok((cross, None)), |(top, top_rem)| {
   1111                                                                                     if top == AFTER_CROSS {
   1112                                                                                         if cross {
   1113                                                                                             Self::parse_string(top_rem).and_then(|(top_origin, top_origin_rem)| {
   1114                                                                                                 top_origin_rem.first().map_or(Ok(()), |v| {
   1115                                                                                                     if *v == b',' {
   1116                                                                                                         Ok(())
   1117                                                                                                     } else {
   1118                                                                                                         Err(CollectedClientDataErr::InvalidObject)
   1119                                                                                                     }
   1120                                                                                                 }).and_then(|()| {
   1121                                                                                                     if origin == top_origin {
   1122                                                                                                         Err(CollectedClientDataErr::TopOriginSameAsOrigin)
   1123                                                                                                     } else {
   1124                                                                                                         Ok((true, Some(Origin(top_origin))))
   1125                                                                                                     }
   1126                                                                                                 })
   1127                                                                                             })
   1128                                                                                         } else {
   1129                                                                                             Err(CollectedClientDataErr::TopOriginWithoutCrossOrigin)
   1130                                                                                         }
   1131                                                                                     } else {
   1132                                                                                         Ok((cross, None))
   1133                                                                                     }
   1134                                                                                 })
   1135                                                                             } else {
   1136                                                                                 Err(CollectedClientDataErr::InvalidObject)
   1137                                                                             }
   1138                                                                         }).map(|(cross_origin, top_origin)| CollectedClientData { challenge: SentChallenge(u128::from_le_bytes(chall)), origin: Origin(origin), cross_origin, top_origin, })
   1139                                                                     })
   1140                                                                 })
   1141                                                             } else {
   1142                                                                 Err(CollectedClientDataErr::CrossOriginKey)
   1143                                                             }
   1144                                                         })
   1145                                                     })
   1146                                                 } else {
   1147                                                     Err(CollectedClientDataErr::OriginKey)
   1148                                                 }
   1149                                             })
   1150                                         })
   1151                                     })
   1152                                 } else {
   1153                                     Err(CollectedClientDataErr::ChallengeKey)
   1154                                 }
   1155                             })
   1156                         })
   1157                     } else {
   1158                         Err(CollectedClientDataErr::InvalidStart)
   1159                     }
   1160                 })
   1161             } else {
   1162                 Err(CollectedClientDataErr::InvalidObject)
   1163             }
   1164         })
   1165     }
   1166     #[expect(clippy::arithmetic_side_effects, reason = "comment justifies correctness")]
   1167     #[expect(clippy::little_endian_bytes, reason = "Challenge::serialize and this need to be consistent across architectures")]
   1168     fn get_sent_challenge(json: &[u8]) -> Result<SentChallenge, Self::Err> {
   1169         // Index 39.
   1170         // `{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA"...`.
   1171         // Index 36.
   1172         // `{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA"...`.
   1173         let idx = if R { 39 } else { 36 };
   1174         // This maxes at 39 + 22 = 61; thus overflow is not an issue.
   1175         json.get(idx..idx + Challenge::BASE64_LEN).ok_or(CollectedClientDataErr::Len).and_then(|chall_slice| {
   1176             let mut chall = [0; 16];
   1177             base64url_nopad::decode_buffer_exact(chall_slice, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).map(|()| {
   1178                 SentChallenge(u128::from_le_bytes(chall))
   1179             })
   1180         })
   1181     }
   1182 }
   1183 /// Authenticator extension outputs;
   1184 pub(super) trait AuthExtOutput {
   1185     /// MUST return `true` iff there is no data.
   1186     fn missing(self) -> bool;
   1187 }
   1188 /// Successful return type from [`FromCbor::from_cbor`].
   1189 struct CborSuccess<'a, T> {
   1190     /// Value parsed from the slice.
   1191     value: T,
   1192     /// Remaining unprocessed data.
   1193     remaining: &'a [u8],
   1194 }
   1195 /// Types that parse
   1196 /// [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form)
   1197 /// data without necessarily consuming all the data.
   1198 ///
   1199 /// The purpose of this `trait` is to allow chains of types to progressively consume `cbor` by passing
   1200 /// [`CborSuccess::remaining`] into the next `FromCbor` type.
   1201 trait FromCbor<'a>: Sized {
   1202     /// Error when conversion fails.
   1203     type Err;
   1204     /// Parses `cbor` into `Self`.
   1205     ///
   1206     /// # Errors
   1207     ///
   1208     /// Errors if `cbor` cannot be parsed into `Self:`.
   1209     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err>;
   1210 }
   1211 /// Error returned from [`A::from_cbor`] `where A: AuthData`.
   1212 enum AuthenticatorDataErr<UpErr, CredData, AuthExt> {
   1213     /// The `slice` had an invalid length.
   1214     Len,
   1215     /// [UP](https://www.w3.org/TR/webauthn-3/#authdata-flags-at) bit was 0.
   1216     UserNotPresent(UpErr),
   1217     /// Bit 1 in [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags) is not 0.
   1218     FlagsBit1Not0,
   1219     /// Bit 5 in [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags) is not 0.
   1220     FlagsBit5Not0,
   1221     /// [AT](https://www.w3.org/TR/webauthn-3/#authdata-flags-at) bit was 0 during registration or was 1
   1222     /// during authentication.
   1223     AttestedCredentialData,
   1224     /// [BE](https://www.w3.org/TR/webauthn-3/#authdata-flags-be) and
   1225     /// [BS](https://www.w3.org/TR/webauthn-3/#authdata-flags-bs) bits were 0 and 1 respectively.
   1226     BackupWithoutEligibility,
   1227     /// Error returned from [`AttestedCredentialData::from_cbor`].
   1228     AttestedCredential(CredData),
   1229     /// Error returned from [`register::AuthenticatorExtensionOutput::from_cbor`] and
   1230     /// [`auth::AuthenticatorExtensionOutput::from_cbor`].
   1231     AuthenticatorExtension(AuthExt),
   1232     /// [ED](https://www.w3.org/TR/webauthn-3/#authdata-flags-ed) bit was 0, but
   1233     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) existed.
   1234     NoExtensionBitWithData,
   1235     /// [ED](https://www.w3.org/TR/webauthn-3/#authdata-flags-ed) bit was 1, but
   1236     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) did not exist.
   1237     ExtensionBitWithoutData,
   1238     /// There was trailing data that could not be deserialized.
   1239     TrailingData,
   1240 }
   1241 impl<U, C: Display, A: Display> Display for AuthenticatorDataErr<U, C, A> {
   1242     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1243         match *self {
   1244             Self::Len => f.write_str("authenticator data had an invalid length"),
   1245             Self::UserNotPresent(_) => f.write_str("user was not present"),
   1246             Self::FlagsBit1Not0 => f.write_str("flags 1-bit was 1"),
   1247             Self::FlagsBit5Not0 => f.write_str("flags 5-bit was 1"),
   1248             Self::AttestedCredentialData => f.write_str("attested credential data was included during authentication or was not included during registration"),
   1249             Self::BackupWithoutEligibility => {
   1250                 f.write_str("backup state bit was 1 despite backup eligibility being 0")
   1251             }
   1252             Self::AttestedCredential(ref err) => err.fmt(f),
   1253             Self::AuthenticatorExtension(ref err) => err.fmt(f),
   1254             Self::NoExtensionBitWithData => {
   1255                 f.write_str("extension bit was 0 despite extensions existing")
   1256             }
   1257             Self::ExtensionBitWithoutData => {
   1258                 f.write_str("extension bit was 1 despite no extensions existing")
   1259             }
   1260             Self::TrailingData => {
   1261                 f.write_str("slice had trailing data that could not be deserialized")
   1262             }
   1263         }
   1264     }
   1265 }
   1266 impl From<AuthenticatorDataErr<Infallible, AttestedCredentialDataErr, RegAuthExtErr>> for RegAuthDataErr {
   1267     #[inline]
   1268     fn from(value: AuthenticatorDataErr<Infallible, AttestedCredentialDataErr, RegAuthExtErr>) -> Self {
   1269         match value {
   1270             AuthenticatorDataErr::Len => Self::Len,
   1271             AuthenticatorDataErr::UserNotPresent(v) => match v {},
   1272             AuthenticatorDataErr::FlagsBit1Not0 => Self::FlagsBit1Not0,
   1273             AuthenticatorDataErr::FlagsBit5Not0 => Self::FlagsBit5Not0,
   1274             AuthenticatorDataErr::AttestedCredentialData => Self::AttestedCredentialDataNotIncluded,
   1275             AuthenticatorDataErr::BackupWithoutEligibility => Self::BackupWithoutEligibility,
   1276             AuthenticatorDataErr::AttestedCredential(err) => Self::AttestedCredential(err),
   1277             AuthenticatorDataErr::AuthenticatorExtension(err) => Self::AuthenticatorExtension(err),
   1278             AuthenticatorDataErr::NoExtensionBitWithData => Self::NoExtensionBitWithData,
   1279             AuthenticatorDataErr::ExtensionBitWithoutData => Self::ExtensionBitWithoutData,
   1280             AuthenticatorDataErr::TrailingData => Self::TrailingData,
   1281         }
   1282     }
   1283 }
   1284 impl From<AuthenticatorDataErr<(), Infallible, AuthAuthExtErr>> for AuthAuthDataErr {
   1285     #[inline]
   1286     fn from(value: AuthenticatorDataErr<(), Infallible, AuthAuthExtErr>) -> Self {
   1287         match value {
   1288             AuthenticatorDataErr::Len => Self::Len,
   1289             AuthenticatorDataErr::UserNotPresent(()) => Self::UserNotPresent,
   1290             AuthenticatorDataErr::FlagsBit1Not0 => Self::FlagsBit1Not0,
   1291             AuthenticatorDataErr::FlagsBit5Not0 => Self::FlagsBit5Not0,
   1292             AuthenticatorDataErr::AttestedCredentialData => Self::AttestedCredentialDataIncluded,
   1293             AuthenticatorDataErr::AttestedCredential(val) => match val {},
   1294             AuthenticatorDataErr::BackupWithoutEligibility => Self::BackupWithoutEligibility,
   1295             AuthenticatorDataErr::AuthenticatorExtension(err) => Self::AuthenticatorExtension(err),
   1296             AuthenticatorDataErr::NoExtensionBitWithData => Self::NoExtensionBitWithData,
   1297             AuthenticatorDataErr::ExtensionBitWithoutData => Self::ExtensionBitWithoutData,
   1298             AuthenticatorDataErr::TrailingData => Self::TrailingData,
   1299         }
   1300     }
   1301 }
   1302 impl<'a, A> FromCbor<'a> for A
   1303 where
   1304     A: AuthData<'a>,
   1305     A::CredData: FromCbor<'a>,
   1306     A::Ext: FromCbor<'a>,
   1307 {
   1308     type Err = AuthenticatorDataErr<A::UpBitErr, <A::CredData as FromCbor<'a>>::Err, <A::Ext as FromCbor<'a>>::Err>;
   1309     #[expect(clippy::big_endian_bytes, reason = "CBOR integers are in big-endian")]
   1310     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1311         /// Length of `signCount`.
   1312         const SIGN_COUNT_LEN: usize = 4;
   1313         /// `UP` bit (i.e., bit 0) set to 1.
   1314         const UP: u8 = 0b0000_0001;
   1315         /// `RFU1` bit (i.e., bit 1) set to 1.
   1316         const RFU1: u8 = UP << 1;
   1317         /// `UV` bit (i.e., bit 2) set to 1.
   1318         const UV: u8 = RFU1 << 1;
   1319         /// `BE` bit (i.e., bit 3) set to 1.
   1320         const BE: u8 = UV << 1;
   1321         /// `BS` bit (i.e., bit 4) set to 1.
   1322         const BS: u8 = BE << 1;
   1323         /// `RFU2` bit (i.e., bit 5) set to 1.
   1324         const RFU2: u8 = BS << 1;
   1325         /// `AT` bit (i.e., bit 6) set to 1.
   1326         const AT: u8 = RFU2 << 1;
   1327         /// `ED` bit (i.e., bit 7) set to 1.
   1328         const ED: u8 = AT << 1;
   1329         cbor.split_at_checked(Sha256::output_size()).ok_or_else(|| AuthenticatorDataErr::Len).and_then(|(rp_id_slice, rp_id_rem)| {
   1330             rp_id_rem.split_first().ok_or_else(|| AuthenticatorDataErr::Len).and_then(|(&flag, flag_rem)| {
   1331                 let user_present = flag & UP == UP;
   1332                 if user_present {
   1333                     Ok(())
   1334                 } else {
   1335                     A::user_is_not_present().map_err(AuthenticatorDataErr::UserNotPresent)   
   1336                 }
   1337                 .and_then(|()| {
   1338                     if flag & RFU1 == 0 {
   1339                         if flag & RFU2 == 0 {
   1340                             let at_bit = A::contains_at_bit();
   1341                             if flag & AT == AT {
   1342                                 if at_bit {
   1343                                     Ok(())
   1344                                 } else {
   1345                                     Err(AuthenticatorDataErr::AttestedCredentialData)
   1346                                 }
   1347                             } else if at_bit {
   1348                                 Err(AuthenticatorDataErr::AttestedCredentialData)
   1349                             } else {
   1350                                 Ok(())
   1351                             }.and_then(|()| {
   1352                                 let bs = flag & BS == BS;
   1353                                 if flag & BE == BE {
   1354                                     if bs {
   1355                                         Ok(Backup::Exists)
   1356                                     } else {
   1357                                         Ok(Backup::Eligible)
   1358                                     }
   1359                                 } else if bs {
   1360                                     Err(AuthenticatorDataErr::BackupWithoutEligibility)
   1361                                 } else {
   1362                                     Ok(Backup::NotEligible)
   1363                                 }
   1364                                 .and_then(|backup| {
   1365                                     flag_rem.split_at_checked(SIGN_COUNT_LEN).ok_or_else(|| AuthenticatorDataErr::Len).and_then(|(count_slice, count_rem)| {
   1366                                         A::CredData::from_cbor(count_rem).map_err(AuthenticatorDataErr::AttestedCredential).and_then(|att_data| {
   1367                                             A::Ext::from_cbor(att_data.remaining).map_err(AuthenticatorDataErr::AuthenticatorExtension).and_then(|ext| {
   1368                                                 if ext.remaining.is_empty() {
   1369                                                     let ed = flag & ED == ED;
   1370                                                     if ext.value.missing() {
   1371                                                         if ed {
   1372                                                             Err(AuthenticatorDataErr::ExtensionBitWithoutData)
   1373                                                         } else {
   1374                                                             Ok(())
   1375                                                         }
   1376                                                     } else if ed {
   1377                                                         Ok(())
   1378                                                     } else {
   1379                                                         Err(AuthenticatorDataErr::NoExtensionBitWithData)
   1380                                                     }.map(|()| {
   1381                                                         let mut sign_count = [0; SIGN_COUNT_LEN];
   1382                                                         sign_count.copy_from_slice(count_slice);
   1383                                                         // `signCount` is in big-endian.
   1384                                                         CborSuccess { value: A::new(rp_id_slice, Flag { user_present, user_verified: flag & UV == UV, backup, }, u32::from_be_bytes(sign_count), att_data.value, ext.value), remaining: ext.remaining, }
   1385                                                     })
   1386                                                 } else {
   1387                                                     Err(AuthenticatorDataErr::TrailingData)
   1388                                                 }
   1389                                             })
   1390                                         })
   1391                                     })
   1392                                 })
   1393                             })
   1394                         } else {
   1395                             Err(AuthenticatorDataErr::FlagsBit5Not0)
   1396                         }
   1397                     } else {
   1398                         Err(AuthenticatorDataErr::FlagsBit1Not0)
   1399                     }
   1400                 })
   1401             })
   1402         })
   1403     }
   1404 }
   1405 /// Data returned by [`AuthDataContainer::from_data`].
   1406 pub(super) struct ParsedAuthData<'a, A> {
   1407     /// The data the CBOR is parsed into.
   1408     data: A,
   1409     /// The raw authenticator data and 32-bytes of trailing data.
   1410     auth_data_and_32_trailing_bytes: &'a [u8],
   1411 }
   1412 /// Error returned by [`AuthResponse::parse_data_and_verify_sig`].
   1413 pub(super) enum AuthRespErr<AuthDataErr> {
   1414     /// Variant returned when parsing
   1415     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson)
   1416     /// into [`CollectedClientData`] fails.
   1417     CollectedClientData(CollectedClientDataErr),
   1418     /// Variant returned when parsing
   1419     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson)
   1420     /// in a "relaxed" way into [`CollectedClientData`] fails.
   1421     #[cfg(feature = "serde_relaxed")]
   1422     CollectedClientDataRelaxed(SerdeJsonErr),
   1423     /// Variant returned when parsing [`AuthResponse::Auth`] fails.
   1424     Auth(AuthDataErr),
   1425     /// Variant when the [`CompressedPubKey`] or [`UncompressePubKey`] is not valid.
   1426     PubKey(PubKeyErr),
   1427     /// Variant returned when the signature, if one exists, associated with
   1428     /// [`Self::AuthResponse`] is invalid.
   1429     Signature,
   1430 }
   1431 impl<A: Display> Display for AuthRespErr<A> {
   1432     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1433         match *self {
   1434             Self::CollectedClientData(ref err) => write!(f, "CollectedClientData could not be parsed: {err}"),
   1435             #[cfg(feature = "serde_relaxed")]
   1436             Self::CollectedClientDataRelaxed(ref err) => write!(f, "CollectedClientData could not be parsed: {err}"),
   1437             Self::Auth(ref err) => write!(f, "auth data could not be parsed: {err}"),
   1438             Self::PubKey(err) => err.fmt(f),
   1439             Self::Signature => f.write_str("the signature over the authenticator data and CollectedClientData could not be verified"),
   1440         }
   1441     }
   1442 }
   1443 impl From<AuthRespErr<AttestationObjectErr>> for RegCeremonyErr {
   1444     #[inline]
   1445     fn from(value: AuthRespErr<AttestationObjectErr>) -> Self {
   1446         match value {
   1447             AuthRespErr::CollectedClientData(err) => Self::CollectedClientData(err),
   1448             #[cfg(feature = "serde_relaxed")]
   1449             AuthRespErr::CollectedClientDataRelaxed(err) => Self::CollectedClientDataRelaxed(err),
   1450             AuthRespErr::Auth(err) => Self::AttestationObject(err),
   1451             AuthRespErr::PubKey(err) => Self::PubKey(err),
   1452             AuthRespErr::Signature => Self::AttestationSignature,
   1453         }
   1454     }
   1455 }
   1456 impl From<AuthRespErr<AuthAuthDataErr>> for AuthCeremonyErr {
   1457     #[inline]
   1458     fn from(value: AuthRespErr<AuthAuthDataErr>) -> Self {
   1459         match value {
   1460             AuthRespErr::CollectedClientData(err) => Self::CollectedClientData(err),
   1461             #[cfg(feature = "serde_relaxed")]
   1462             AuthRespErr::CollectedClientDataRelaxed(err) => Self::CollectedClientDataRelaxed(err),
   1463             AuthRespErr::Auth(err) => Self::AuthenticatorData(err),
   1464             AuthRespErr::PubKey(err) => Self::PubKey(err),
   1465             AuthRespErr::Signature => Self::AssertionSignature,
   1466         }
   1467     }
   1468 }
   1469 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data)
   1470 /// container.
   1471 ///
   1472 /// Note [`Self::Auth`] may be `Self`.
   1473 pub(super) trait AuthDataContainer<'a>: Sized {
   1474     /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   1475     type Auth: AuthData<'a>;
   1476     /// Error returned from [`Self::from_data`].
   1477     type Err;
   1478     /// Converts `data` into [`ParsedAuthData`].
   1479     ///
   1480     /// # Errors
   1481     ///
   1482     /// Errors iff `data` cannot be converted into `ParsedAuthData`.
   1483     fn from_data(data: &'a [u8]) -> Result<ParsedAuthData<'a, Self>, Self::Err>;
   1484     /// Returns the contained
   1485     /// [authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   1486     fn authenticator_data(&self) -> &Self::Auth;
   1487 }
   1488 /// [`AuthenticatorResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorresponse).
   1489 pub(super) trait AuthResponse {
   1490     /// [Attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object) or
   1491     /// [authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   1492     type Auth<'a>: AuthDataContainer<'a> where Self: 'a;
   1493     /// Public key to use to verify the contained signature.
   1494     type CredKey<'a>;
   1495     /// Parses
   1496     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson)
   1497     /// based on `RELAXED` and [`Self::Auth`] via [`AuthDataContainer::from_data`] in addition to
   1498     /// verifying any possible signature over the concatenation of the raw
   1499     /// [`AuthDataContainer::Auth`] and `clientDataJSON` using `key` or the contained
   1500     /// public key if one exists. If `Self` contains a public key and should not be passed one, then it should set
   1501     /// [`Self::CredKey`] to `()`.
   1502     ///
   1503     /// # Errors
   1504     ///
   1505     /// Errors iff parsing `clientDataJSON` errors, [`AuthDataContainer::from_data`] does, or the signature
   1506     /// is invalid.
   1507     ///
   1508     /// # Panics
   1509     ///
   1510     /// `panic`s iff `relaxed` and `serde_relaxed` is not enabled.
   1511     #[expect(
   1512         clippy::type_complexity,
   1513         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
   1514     )]
   1515     fn parse_data_and_verify_sig(&self, key: Self::CredKey<'_>, relaxed: bool) -> Result<(CollectedClientData<'_>, Self::Auth<'_>), AuthRespErr<<Self::Auth<'_> as AuthDataContainer<'_>>::Err>>;
   1516 }
   1517 /// Ceremony response (i.e., [`PublicKeyCredential`](https://www.w3.org/TR/webauthn-3/#publickeycredential)).
   1518 pub(super) trait Response {
   1519     /// [`AuthenticatorResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorresponse).
   1520     type Auth: AuthResponse;
   1521     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response).
   1522     fn auth(&self) -> &Self::Auth;
   1523 }
   1524 /// Error returned from [`Ceremony::partial_validate`].
   1525 pub(super) enum CeremonyErr<AuthDataErr> {
   1526     /// Timeout occurred.
   1527     Timeout,
   1528     /// Read [`AuthRespErr`] for information.
   1529     AuthResp(AuthRespErr<AuthDataErr>),
   1530     /// Origin did not validate.
   1531     OriginMismatch,
   1532     /// Cross origin was `true` but was not allowed to be.
   1533     CrossOrigin,
   1534     /// Top origin did not validate.
   1535     TopOriginMismatch,
   1536     /// Challenges don't match.
   1537     ChallengeMismatch,
   1538     /// `rpIdHash` does not match the SHA-256 hash of the [`RpId`].
   1539     RpIdHashMismatch,
   1540     /// User was not verified despite being required to.
   1541     UserNotVerified,
   1542     /// [`Backup::NotEligible`] was not sent back despite [`BackupReq::NotEligible`].
   1543     BackupEligible,
   1544     /// [`Backup::NotEligible`] was sent back despite [`BackupReq::Eligible`].
   1545     BackupNotEligible,
   1546     /// [`Backup::Eligible`] was not sent back despite [`BackupReq::EligibleNoteExists`].
   1547     BackupExists,
   1548     /// [`Backup::Exists`] was not sent back despite [`BackupReq::Exists`].
   1549     BackupDoesNotExist,
   1550 }
   1551 impl<A: Display> Display for CeremonyErr<A> {
   1552     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1553         match *self {
   1554             Self::Timeout => f.write_str("ceremony timed out"),
   1555             Self::AuthResp(ref err) => err.fmt(f),
   1556             Self::OriginMismatch => {
   1557                 f.write_str("the origin sent from the client is not an allowed origin")
   1558             }
   1559             Self::CrossOrigin => {
   1560                 f.write_str("cross origin was from the client, but it is not allowed")
   1561             }
   1562             Self::TopOriginMismatch => {
   1563                 f.write_str("the top origin sent from the client is not an allowed top origin")
   1564             }
   1565             Self::ChallengeMismatch => f.write_str(
   1566                 "the challenge sent to the client does not match the challenge sent back",
   1567             ),
   1568             Self::RpIdHashMismatch => f.write_str(
   1569                 "the SHA-256 hash of the RP ID doesn't match the hash sent from the client",
   1570             ),
   1571             Self::UserNotVerified => f.write_str("user was not verified despite being required to"),
   1572             Self::BackupEligible => f.write_str("credential is eligible to be backed up despite requiring that it not be"),
   1573             Self::BackupNotEligible => f.write_str("credential is not eligible to be backed up despite requiring that it be"),
   1574             Self::BackupExists => f.write_str("credential backup exists despite requiring that a backup not exist"),
   1575             Self::BackupDoesNotExist => f.write_str("credential backup does not exist despite requiring that a backup exist"),
   1576         }
   1577     }
   1578 }
   1579 impl From<CeremonyErr<AttestationObjectErr>> for RegCeremonyErr {
   1580     #[inline]
   1581     fn from(value: CeremonyErr<AttestationObjectErr>) -> Self {
   1582         match value {
   1583             CeremonyErr::Timeout => Self::Timeout,
   1584             CeremonyErr::AuthResp(err) => err.into(),
   1585             CeremonyErr::OriginMismatch => Self::OriginMismatch,
   1586             CeremonyErr::CrossOrigin => Self::CrossOrigin,
   1587             CeremonyErr::TopOriginMismatch => Self::TopOriginMismatch,
   1588             CeremonyErr::ChallengeMismatch => Self::ChallengeMismatch,
   1589             CeremonyErr::RpIdHashMismatch => Self::RpIdHashMismatch,
   1590             CeremonyErr::UserNotVerified => Self::UserNotVerified,
   1591             CeremonyErr::BackupEligible => Self::BackupEligible,
   1592             CeremonyErr::BackupNotEligible => Self::BackupNotEligible,
   1593             CeremonyErr::BackupExists => Self::BackupExists,
   1594             CeremonyErr::BackupDoesNotExist => Self::BackupDoesNotExist,
   1595         }
   1596     }
   1597 }
   1598 impl From<CeremonyErr<AuthAuthDataErr>> for AuthCeremonyErr {
   1599     #[inline]
   1600     fn from(value: CeremonyErr<AuthAuthDataErr>) -> Self {
   1601         match value {
   1602             CeremonyErr::Timeout => Self::Timeout,
   1603             CeremonyErr::AuthResp(err) => err.into(),
   1604             CeremonyErr::OriginMismatch => Self::OriginMismatch,
   1605             CeremonyErr::CrossOrigin => Self::CrossOrigin,
   1606             CeremonyErr::TopOriginMismatch => Self::TopOriginMismatch,
   1607             CeremonyErr::ChallengeMismatch => Self::ChallengeMismatch,
   1608             CeremonyErr::RpIdHashMismatch => Self::RpIdHashMismatch,
   1609             CeremonyErr::UserNotVerified => Self::UserNotVerified,
   1610             CeremonyErr::BackupEligible => Self::BackupEligible,
   1611             CeremonyErr::BackupNotEligible => Self::BackupNotEligible,
   1612             CeremonyErr::BackupExists => Self::BackupExists,
   1613             CeremonyErr::BackupDoesNotExist => Self::BackupDoesNotExist,
   1614         }
   1615     }
   1616 }
   1617 /// [`AllAcceptedCredentialsOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-allacceptedcredentialsoptions).
   1618 ///
   1619 /// This can be sent to _an already authenticated user_ to inform what credentials are currently registered.
   1620 /// This can be useful when a user deletes credentials on the RP's side but does not do so on the authenticator.
   1621 /// When the client forwards this response to the authenticator, it can remove all credentials that don't have
   1622 /// a [`CredentialId`] in [`Self::all_accepted_credential_ids`].
   1623 #[derive(Debug)]
   1624 pub struct AllAcceptedCredentialsOptions<'rp, 'user, const USER_LEN: usize> {
   1625     /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dictdef-allacceptedcredentialsoptions-rpid).
   1626     pub rp_id: &'rp RpId,
   1627     /// [`userId`](https://www.w3.org/TR/webauthn-3/#dictdef-allacceptedcredentialsoptions-userid).
   1628     pub user_id: &'user UserHandle<USER_LEN>,
   1629     /// [`allAcceptedCredentialIds`](https://www.w3.org/TR/webauthn-3/#dictdef-allacceptedcredentialsoptions-allacceptedcredentialids).
   1630     pub all_accepted_credential_ids: Vec<CredentialId<Vec<u8>>>,
   1631 }
   1632 /// [`CurrentUserDetailsOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions).
   1633 ///
   1634 /// This can be sent to _an already authenticated user_ to inform the user information.
   1635 /// This can be useful when a user updates their user information on the RP's side but does not do so on the authenticator.
   1636 /// When the client forwards this response to the authenticator, it can update the user info for the associated credential.
   1637 #[derive(Debug)]
   1638 pub struct CurrentUserDetailsOptions<'rp_id, 'name, 'display_name, 'id, const LEN: usize> {
   1639     /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions-rpid).
   1640     pub rp_id: &'rp_id RpId,
   1641     /// [`userId`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions-userid),
   1642     /// [`name`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions-name), and
   1643     /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions-displayname).
   1644     pub user: PublicKeyCredentialUserEntity<'name, 'display_name, 'id, LEN>,
   1645 }
   1646 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension)
   1647 /// during authentication and
   1648 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension)
   1649 /// during registration.
   1650 ///
   1651 /// `REG` iff `hmac-secret-mc`.
   1652 enum HmacSecretGet<const REG: bool> {
   1653     /// No `hmac-secret` response.
   1654     None,
   1655     /// One encrypted `hmac-secret`.
   1656     One,
   1657     /// Two encrypted `hmac-secret`s.
   1658     Two,
   1659 }
   1660 /// Error returned by [`HmacSecretGet::from_cbor`]
   1661 enum HmacSecretGetErr {
   1662     /// Error related to the length of the CBOR input.
   1663     Len,
   1664     /// Error related to the type of the CBOR key.
   1665     Type,
   1666     /// Error related to the value of the CBOR value.
   1667     Value,
   1668 }
   1669 impl<const REG: bool> FromCbor<'_> for HmacSecretGet<REG> {
   1670     type Err = HmacSecretGetErr;
   1671     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
   1672         /// AES block size.
   1673         const AES_BLOCK_SIZE: usize = 16;
   1674         /// HMAC-SHA-256 output length.
   1675         const HMAC_SHA_256_LEN: usize = 32;
   1676         /// Length of two HMAC-SHA-256 outputs concatenated together.
   1677         const TWO_HMAC_SHA_256_LEN: usize = HMAC_SHA_256_LEN << 1;
   1678         // We need the smallest multiple of `AES_BLOCK_SIZE` that
   1679         // is strictly greater than `HMAC_SHA_256_LEN`.
   1680         /// AES-256 output length on a 32-byte input.
   1681         #[expect(
   1682             clippy::integer_division_remainder_used,
   1683             reason = "doesn't need to be constant time"
   1684         )]
   1685         const ONE_SECRET_LEN: usize =
   1686             HMAC_SHA_256_LEN + (AES_BLOCK_SIZE - (HMAC_SHA_256_LEN % AES_BLOCK_SIZE));
   1687         // We need the smallest multiple of `AES_BLOCK_SIZE` that
   1688         // is strictly greater than `TWO_HMAC_SHA_256_LEN`.
   1689         /// AES-256 output length on a 64-byte input.
   1690         #[expect(
   1691             clippy::integer_division_remainder_used,
   1692             reason = "doesn't need to be constant time"
   1693         )]
   1694         const TWO_SECRET_LEN: usize =
   1695             TWO_HMAC_SHA_256_LEN + (AES_BLOCK_SIZE - (TWO_HMAC_SHA_256_LEN % AES_BLOCK_SIZE));
   1696         /// `hmac-secret-mc`.
   1697         ///
   1698         /// This is the key iff `REG`.
   1699         const KEY: [u8; 15] = [
   1700             cbor::TEXT_14,
   1701             b'h',
   1702             b'm',
   1703             b'a',
   1704             b'c',
   1705             b'-',
   1706             b's',
   1707             b'e',
   1708             b'c',
   1709             b'r',
   1710             b'e',
   1711             b't',
   1712             b'-',
   1713             b'm',
   1714             b'c',
   1715         ];
   1716         /// Helper that unifies `HmacSecretGet`.
   1717         enum CborVal<'a> {
   1718             /// Extension does not exist with remaining payload
   1719             Success,
   1720             /// Extension exists with remaining payload.
   1721             Continue(&'a [u8]),
   1722         }
   1723         if REG {
   1724             cbor.split_at_checked(KEY.len()).map_or(
   1725                 Ok(CborVal::Success),
   1726                 |(key, key_rem)| {
   1727                     if key == KEY {
   1728                         Ok(CborVal::Continue(key_rem))
   1729                     } else {
   1730                         Ok(CborVal::Success)
   1731                     }
   1732                 }
   1733             )
   1734         } else {
   1735             cbor.split_at_checked(cbor::HMAC_SECRET.len()).map_or(
   1736                 Ok(CborVal::Success),
   1737                 |(key, key_rem)| {
   1738                     if key == cbor::HMAC_SECRET {
   1739                         Ok(CborVal::Continue(key_rem))
   1740                     } else {
   1741                         Ok(CborVal::Success)
   1742                     }
   1743                 }
   1744             )
   1745         }.and_then(|cbor_val| {
   1746             match cbor_val {
   1747                 CborVal::Success => Ok(CborSuccess { value: Self::None, remaining: cbor, }),
   1748                 CborVal::Continue(key_rem) => {
   1749                     key_rem
   1750                         .split_first()
   1751                         .ok_or(HmacSecretGetErr::Len)
   1752                         .and_then(|(bytes, bytes_rem)| {
   1753                             if *bytes == cbor::BYTES_INFO_24 {
   1754                                 bytes_rem
   1755                                     .split_first()
   1756                                     .ok_or(HmacSecretGetErr::Len)
   1757                                     .and_then(|(&len, len_rem)| {
   1758                                         len_rem.split_at_checked(usize::from(len)).ok_or(HmacSecretGetErr::Len).and_then(|(_, remaining)| {
   1759                                             match usize::from(len) {
   1760                                                 ONE_SECRET_LEN => {
   1761                                                     Ok(CborSuccess {
   1762                                                         value: Self::One,
   1763                                                         remaining,
   1764                                                     })
   1765                                                 }
   1766                                                 TWO_SECRET_LEN => {
   1767                                                     Ok(CborSuccess {
   1768                                                         value: Self::Two,
   1769                                                         remaining,
   1770                                                     })
   1771                                                 }
   1772                                                 _ => Err(HmacSecretGetErr::Value),
   1773                                             }
   1774                                         })
   1775                                     })
   1776                             } else {
   1777                                 Err(HmacSecretGetErr::Type)
   1778                             }
   1779                         })
   1780                 }
   1781             }
   1782         })
   1783     }
   1784 }
   1785 #[cfg(test)]
   1786 mod tests {
   1787     use super::{CollectedClientDataErr, ClientDataJsonParser, LimitedVerificationParser};
   1788     #[test]
   1789     fn parse_string() {
   1790         assert!(LimitedVerificationParser::<true>::parse_string(br#"abc""#)
   1791             .map_or(false, |tup| { tup.0 == "abc" && tup.1 == br#""# }));
   1792         assert!(LimitedVerificationParser::<false>::parse_string(br#"abc"23"#)
   1793             .map_or(false, |tup| { tup.0 == "abc" && tup.1 == br#"23"# }));
   1794         assert!(LimitedVerificationParser::<true>::parse_string(br#"ab\"c"23"#)
   1795             .map_or(false, |tup| { tup.0 == r#"ab"c"# && tup.1 == br#"23"# }));
   1796         assert!(LimitedVerificationParser::<false>::parse_string(br#"ab\\c"23"#)
   1797             .map_or(false, |tup| { tup.0 == r#"ab\c"# && tup.1 == br#"23"# }));
   1798         assert!(LimitedVerificationParser::<true>::parse_string(br#"ab\u001fc"23"#)
   1799             .map_or(false, |tup| { tup.0 == "ab\u{001f}c" && tup.1 == br#"23"# }));
   1800         assert!(LimitedVerificationParser::<false>::parse_string(br#"ab\u000dc"23"#)
   1801             .map_or(false, |tup| { tup.0 == "ab\u{000d}c" && tup.1 == br#"23"# }));
   1802         assert!(
   1803             LimitedVerificationParser::<true>::parse_string(b"\\\\\\\\\\\\a\\\\\\\\a\\\\\"").map_or(false, |tup| {
   1804                 tup.0 == "\\\\\\a\\\\a\\" && tup.1.is_empty()
   1805             })
   1806         );
   1807         assert!(
   1808             LimitedVerificationParser::<false>::parse_string(b"\\\\\\\\\\a\\\\\\\\a\\\\\"").map_or_else(
   1809                 |e| matches!(e, CollectedClientDataErr::InvalidEscapedString),
   1810                 |_| false
   1811             )
   1812         );
   1813         assert!(LimitedVerificationParser::<true>::parse_string(br#"ab\u0020c"23"#).map_or_else(
   1814             |err| matches!(err, CollectedClientDataErr::InvalidEscapedString),
   1815             |_| false
   1816         ));
   1817         assert!(LimitedVerificationParser::<false>::parse_string(br#"ab\ac"23"#).map_or_else(
   1818             |err| matches!(err, CollectedClientDataErr::InvalidEscapedString),
   1819             |_| false
   1820         ));
   1821         assert!(LimitedVerificationParser::<true>::parse_string(br#"ab\""#).map_or_else(
   1822             |err| matches!(err, CollectedClientDataErr::InvalidObject),
   1823             |_| false
   1824         ));
   1825         assert!(LimitedVerificationParser::<false>::parse_string(br#"ab\u001Fc"23"#).map_or_else(
   1826             |err| matches!(err, CollectedClientDataErr::InvalidEscapedString),
   1827             |_| false
   1828         ));
   1829         assert!(LimitedVerificationParser::<true>::parse_string([0, b'"'].as_slice()).map_or_else(
   1830             |err| matches!(err, CollectedClientDataErr::InvalidEscapedString),
   1831             |_| false
   1832         ));
   1833         assert!(LimitedVerificationParser::<false>::parse_string([b'a', 255, b'"'].as_slice())
   1834             .map_or_else(|err| matches!(err, CollectedClientDataErr::Utf8(_)), |_| false));
   1835         assert!(LimitedVerificationParser::<true>::parse_string([b'a', b'"', 255].as_slice()).is_ok());
   1836         assert!(
   1837             LimitedVerificationParser::<false>::parse_string(br#"""#).map_or(false, |tup| tup.0.is_empty() && tup.1.is_empty())
   1838         );
   1839     }
   1840     #[test]
   1841     fn c_data_json() {
   1842         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && !val.cross_origin && val.top_origin.is_none()));
   1843         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,{}}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && !val.cross_origin && val.top_origin.is_none()));
   1844         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && val.cross_origin && val.top_origin.is_none()));
   1845         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true,"topOrigin":"bob"}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && val.cross_origin && val.top_origin.map_or(false, |v| v == "bob")));
   1846         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true,"topOrigin":"bob",a}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && val.cross_origin && val.top_origin.map_or(false, |v| v == "bob")));
   1847         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true,"topOrigin":"bob"a}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1848         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"topOrigin":""}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::TopOriginWithoutCrossOrigin), |_| false));
   1849         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"topOrigin":""}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Challenge), |_| false));
   1850         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0.is_empty() && !val.cross_origin && val.top_origin.is_none()));
   1851         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Type), |_| false));
   1852         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create", "challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::ChallengeKey), |_| false));
   1853         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::OriginKey), |_| false));
   1854         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\\e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\\e.com" && !val.cross_origin && val.top_origin.is_none()));
   1855         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\"e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\"e.com" && !val.cross_origin && val.top_origin.is_none()));
   1856         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u0013e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\u{0013}e.com" && !val.cross_origin && val.top_origin.is_none()));
   1857         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\3e.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1858         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\e.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1859         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u0020.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1860         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u000A.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1861         assert!(LimitedVerificationParser::<true>::parse([].as_slice())
   1862             .map_or_else(|e| matches!(e, CollectedClientDataErr::Len), |_| false));
   1863         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"abc","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidStart), |_| false));
   1864         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1865         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","crossOrigin":false,"origin":"example.com"}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::OriginKey), |_| false));
   1866         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","topOrigin":"bob","crossOrigin":true}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::CrossOriginKey), |_| false));
   1867         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":"abc"}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::CrossOrigin), |_| false));
   1868         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true"a}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1869         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true","topOrigin":"https://abc.com"a}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1870         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && !val.cross_origin && val.top_origin.is_none()));
   1871         assert!(LimitedVerificationParser::<false>::parse(b"{\"type\":\"webauthn.get\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAA\",\"origin\":\"https://example.com\",\"crossOrigin\":false,\xff}".as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && !val.cross_origin && val.top_origin.is_none()));
   1872         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && val.cross_origin && val.top_origin.is_none()));
   1873         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true,"topOrigin":"bob"}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && val.cross_origin && val.top_origin.map_or(false, |v| v == "bob")));
   1874         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"topOrigin":""}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::TopOriginWithoutCrossOrigin), |_| false));
   1875         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"topOrigin":""}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Challenge), |_| false));
   1876         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0.is_empty() && !val.cross_origin && val.top_origin.is_none()));
   1877         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Type), |_| false));
   1878         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get", "challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::ChallengeKey), |_| false));
   1879         assert!(LimitedVerificationParser::<false>::parse(
   1880             br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","crossOrigin":false}"#
   1881                 .as_slice()
   1882         )
   1883         .map_or_else(|e| matches!(e, CollectedClientDataErr::OriginKey), |_| false));
   1884         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\\e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\\e.com" && !val.cross_origin && val.top_origin.is_none()));
   1885         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\"e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\"e.com" && !val.cross_origin && val.top_origin.is_none()));
   1886         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u0013e.com","crossOrigin":false}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://exampl\u{0013}e.com" && !val.cross_origin && val.top_origin.is_none()));
   1887         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\3e.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1888         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\e.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1889         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u0020.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1890         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://exampl\u000A.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidEscapedString), |_| false));
   1891         assert!(LimitedVerificationParser::<false>::parse([].as_slice())
   1892             .map_or_else(|e| matches!(e, CollectedClientDataErr::Len), |_| false));
   1893         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"abc","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidStart), |_| false));
   1894         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1895         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","crossOrigin":false,"origin":"example.com"}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::OriginKey), |_| false));
   1896         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","topOrigin":"bob","crossOrigin":true}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::CrossOriginKey), |_| false));
   1897         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":"abc"}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::CrossOrigin), |_| false));
   1898         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true"a}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::InvalidObject), |_| false));
   1899         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":true,"topOrigin":"https://example.com"}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::TopOriginSameAsOrigin), |_| false));
   1900         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"foo":true}"#.as_slice()).map_or(false, |val| val.challenge.0 == 0 && val.origin.0 == "https://example.com" && !val.cross_origin && val.top_origin.is_none()));
   1901         assert!(LimitedVerificationParser::<false>::parse(br#"{"type":"webauthn.get","challengE":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false,"foo":true}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::ChallengeKey), |_| false));
   1902         assert!(LimitedVerificationParser::<true>::parse(br#"{"type":"webauthn.create"challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossorigin":false,"foo":true}"#.as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::ChallengeKey), |_| false));
   1903     }
   1904     #[test]
   1905     fn c_data_challenge() {
   1906         assert!(LimitedVerificationParser::<false>::get_sent_challenge([].as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Len), |_| false));
   1907         assert!(LimitedVerificationParser::<true>::get_sent_challenge([].as_slice()).map_or_else(|e| matches!(e, CollectedClientDataErr::Len), |_| false));
   1908         assert!(LimitedVerificationParser::<true>::get_sent_challenge(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBB").map_or_else(|e| matches!(e, CollectedClientDataErr::Challenge), |_| false));
   1909         assert!(LimitedVerificationParser::<false>::get_sent_challenge(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBB").map_or_else(|e| matches!(e, CollectedClientDataErr::Challenge), |_| false));
   1910         assert!(LimitedVerificationParser::<true>::get_sent_challenge(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".as_slice()).map_or(false, |c| c.0 == 0));
   1911         assert!(LimitedVerificationParser::<false>::get_sent_challenge(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".as_slice()).map_or(false, |c| c.0 == 0));
   1912     }
   1913 }