webauthn_rp

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

response.rs (100199B)


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