webauthn_rp

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

ser_relaxed.rs (173423B)


      1 #[cfg(doc)]
      2 use super::super::{super::request::register::CoseAlgorithmIdentifier, Challenge, CredentialId};
      3 use super::{
      4     super::{
      5         register::ser::{
      6             AUTH_ATTEST_FIELDS, AttObj, AuthenticatorAttestationVisitor,
      7             ClientExtensionsOutputsVisitor, EXT_FIELDS,
      8         },
      9         ser::{
     10             AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions,
     11             PublicKeyCredential, Type,
     12         },
     13         ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed,
     14     },
     15     AttestationObject, AuthenticationExtensionsPrfOutputs, AuthenticatorAttachment,
     16     AuthenticatorAttestation, ClientExtensionsOutputs, CredentialPropertiesOutput, Registration,
     17     ser::{AuthAttest, CredentialPropertiesOutputVisitor, PROPS_FIELDS},
     18 };
     19 use core::{
     20     fmt::{self, Formatter},
     21     marker::PhantomData,
     22 };
     23 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Unexpected, Visitor};
     24 /// `newtype` around `CredentialPropertiesOutput` with a "relaxed" [`Self::deserialize`] implementation.
     25 #[derive(Clone, Copy, Debug)]
     26 pub struct CredentialPropertiesOutputRelaxed(pub CredentialPropertiesOutput);
     27 impl From<CredentialPropertiesOutputRelaxed> for CredentialPropertiesOutput {
     28     #[inline]
     29     fn from(value: CredentialPropertiesOutputRelaxed) -> Self {
     30         value.0
     31     }
     32 }
     33 impl<'de> Deserialize<'de> for CredentialPropertiesOutputRelaxed {
     34     /// Same as [`CredentialPropertiesOutput::deserialize`] except unknown keys are ignored.
     35     ///
     36     /// Note that duplicate keys are still forbidden.
     37     #[inline]
     38     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     39     where
     40         D: Deserializer<'de>,
     41     {
     42         deserializer
     43             .deserialize_struct(
     44                 "CredentialPropertiesOutputRelaxed",
     45                 PROPS_FIELDS,
     46                 CredentialPropertiesOutputVisitor::<true>,
     47             )
     48             .map(Self)
     49     }
     50 }
     51 /// `newtype` around `AuthenticationExtensionsPrfOutputs` with a "relaxed" [`Self::deserialize`] implementation.
     52 #[derive(Clone, Copy, Debug)]
     53 pub struct AuthenticationExtensionsPrfOutputsRelaxed(AuthenticationExtensionsPrfOutputs);
     54 impl From<AuthenticationExtensionsPrfOutputsRelaxed> for AuthenticationExtensionsPrfOutputs {
     55     #[inline]
     56     fn from(value: AuthenticationExtensionsPrfOutputsRelaxed) -> Self {
     57         value.0
     58     }
     59 }
     60 impl<'de> Deserialize<'de> for AuthenticationExtensionsPrfOutputsRelaxed {
     61     /// Same as [`AuthenticationExtensionsPrfOutputs::deserialize`] except unknown keys are ignored.
     62     ///
     63     /// Note that duplicate keys are still forbidden;
     64     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) must still exist
     65     /// (and not be `null`); and
     66     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
     67     /// be `null`, or be an
     68     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
     69     /// such that unknown keys are ignored, duplicate keys are forbidden,
     70     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but
     71     /// if it exists it must be `null`, and
     72     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
     73     /// must be `null` if so.
     74     #[inline]
     75     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
     76     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     77     where
     78         D: Deserializer<'de>,
     79     {
     80         AuthenticationExtensionsPrfOutputsHelper::<
     81             true,
     82             true,
     83             AuthenticationExtensionsPrfValuesRelaxed,
     84         >::deserialize(deserializer)
     85         .map(|v| {
     86             Self(AuthenticationExtensionsPrfOutputs {
     87                 enabled: v.0.unwrap_or_else(|| {
     88                     unreachable!(
     89                         "there is a bug in AuthenticationExtensionsPrfOutputsHelper::deserialize"
     90                     )
     91                 }),
     92             })
     93         })
     94     }
     95 }
     96 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation.
     97 #[derive(Clone, Copy, Debug)]
     98 pub struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs);
     99 impl ClientExtensions for ClientExtensionsOutputsRelaxed {
    100     fn empty() -> Self {
    101         Self(ClientExtensionsOutputs::empty())
    102     }
    103 }
    104 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed {
    105     /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored,
    106     /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops) is
    107     /// `null` or deserialized via [`CredentialPropertiesOutputRelaxed::deserialize`], and
    108     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is
    109     /// `null` or deserialized via [`AuthenticationExtensionsPrfOutputsRelaxed::deserialize`].
    110     ///
    111     /// Note that duplicate keys are still forbidden.
    112     #[inline]
    113     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    114     where
    115         D: Deserializer<'de>,
    116     {
    117         deserializer
    118             .deserialize_struct(
    119                 "ClientExtensionsOutputsRelaxed",
    120                 EXT_FIELDS,
    121                 ClientExtensionsOutputsVisitor::<
    122                     true,
    123                     CredentialPropertiesOutputRelaxed,
    124                     AuthenticationExtensionsPrfOutputsRelaxed,
    125                 >(PhantomData),
    126             )
    127             .map(Self)
    128     }
    129 }
    130 /// `newtype` around `AuthAttest` with a "relaxed" [`Self::deserialize`] implementation.
    131 struct AuthAttestRelaxed(pub AuthAttest);
    132 impl<'de> Deserialize<'de> for AuthAttestRelaxed {
    133     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    134     where
    135         D: Deserializer<'de>,
    136     {
    137         deserializer
    138             .deserialize_struct(
    139                 "AuthenticatorAttestation",
    140                 AUTH_ATTEST_FIELDS,
    141                 AuthenticatorAttestationVisitor::<true>,
    142             )
    143             .map(Self)
    144     }
    145 }
    146 /// `newtype` around `AuthenticatorAttestation` with a "relaxed" [`Self::deserialize`] implementation.
    147 #[derive(Debug)]
    148 pub struct AuthenticatorAttestationRelaxed(pub AuthenticatorAttestation);
    149 impl<'de> Deserialize<'de> for AuthenticatorAttestationRelaxed {
    150     /// Same as [`AuthenticatorAttestation::deserialize`] except unknown keys are ignored and only
    151     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-clientdatajson)
    152     /// and
    153     /// [`attestationObject`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-attestationobject)
    154     /// are required (and must not be `null`). For the other fields, they are allowed to not exist or be `null`.
    155     ///
    156     /// Note that duplicate keys are still forbidden, and data matching still applies when applicable.
    157     #[inline]
    158     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    159     where
    160         D: Deserializer<'de>,
    161     {
    162         AuthAttestRelaxed::deserialize(deserializer).map(|v| Self(v.0.attest))
    163     }
    164 }
    165 /// `newtype` around `Registration` with a "relaxed" [`Self::deserialize`] implementation.
    166 #[derive(Debug)]
    167 pub struct RegistrationRelaxed(pub Registration);
    168 impl<'de> Deserialize<'de> for RegistrationRelaxed {
    169     /// Same as [`Registration::deserialize`] except unknown keys are ignored,
    170     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-response) is deserialized
    171     /// via [`AuthenticatorAttestationRelaxed::deserialize`],
    172     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults)
    173     /// is `null` or deserialized via [`ClientExtensionsOutputsRelaxed::deserialize`], and only `response` is required.
    174     /// `id`, `rawId`, and `type` are allowed to not exist. For the other fields, they are allowed to not exist or
    175     /// be `null`.
    176     ///
    177     /// Note that duplicate keys are still forbidden, and data matching still applies when applicable.
    178     #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")]
    179     #[inline]
    180     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    181     where
    182         D: Deserializer<'de>,
    183     {
    184         PublicKeyCredential::<true, true, AuthAttestRelaxed, ClientExtensionsOutputsRelaxed>::deserialize(deserializer).and_then(|cred| {
    185             cred.id.map_or_else(|| Ok(()), |id| {
    186                 cred.response.0.cred_info.map_or_else(
    187                     || AttestationObject::try_from(cred.response.0.attest.attestation_object()).map_err(Error::custom).and_then(|att_obj| {
    188                         if id == att_obj.auth_data.attested_credential_data.credential_id {
    189                             Ok(())
    190                         } else {
    191                             Err(Error::invalid_value(Unexpected::Bytes(id.as_ref()), &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", att_obj.auth_data.attested_credential_data.credential_id.0).as_str()))
    192                         }
    193                     }),
    194                     // `start` and `last` were calculated based on `cred.response.attest.attestation_object()`
    195                     // and represent the starting and ending index of the `CredentialId`; therefore this is correct
    196                     // let alone won't `panic`.
    197                     |(start, last)| if id.0 == cred.response.0.attest.attestation_object()[start..last] {
    198                         Ok(())
    199                     } else {
    200                         Err(Error::invalid_value(Unexpected::Bytes(id.as_ref()), &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", &cred.response.0.attest.attestation_object()[start..last]).as_str()))
    201                     }
    202                 )
    203             }).map(|()| {
    204                 Self(Registration { response: cred.response.0.attest, authenticator_attachment: cred.authenticator_attachment, client_extension_results: cred.client_extension_results.0 })
    205             })
    206         })
    207     }
    208 }
    209 /// `newtype` around `Registration` with a custom [`Self::deserialize`] implementation.
    210 #[derive(Debug)]
    211 pub struct CustomRegistration(pub Registration);
    212 impl<'de> Deserialize<'de> for CustomRegistration {
    213     /// Despite the spec having a
    214     /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson) that clients
    215     /// can follow, the downside is the superfluous data it contains.
    216     ///
    217     /// There simply is no reason to send the [`CredentialId`] _four_ times. This redundant data puts RPs in
    218     /// a position where they either ignore the data or parse the data to ensure no contradictions exist
    219     /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)).
    220     ///
    221     /// While [`Registration::deserialize`] _strictly_ adheres to the JSON definition (e.g., it requires `publicKey`
    222     /// to exist and match with what is in both `authenticatorData` and `attestationObject` when the underlying
    223     /// algorithm is not [`CoseAlgorithmIdentifier::Es384`]), this implementation
    224     /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate
    225     /// and unknown keys are disallowed:
    226     ///
    227     /// ```json
    228     /// {
    229     ///   "attestationObject": <base64url string>,
    230     ///   "authenticatorAttachment": null | "platform" | "cross-platform",
    231     ///   "clientDataJSON": <base64url string>,
    232     ///   "clientExtensionResults": <see ClientExtensionsOutputs::deserialize>,
    233     ///   "transports": <see AuthTransports::deserialize>,
    234     ///   "type": "public-key"
    235     /// }
    236     /// ```
    237     ///
    238     /// All of the above keys are required with the exceptions of `"authenticatorAttachment"` and `"type"`.
    239     ///
    240     /// # Examples
    241     ///
    242     /// ```
    243     /// # use webauthn_rp::response::register::ser_relaxed::CustomRegistration;
    244     /// assert!(
    245     ///     // The below payload is technically valid, but `RegistrationServerState::verify` will fail
    246     ///     // since the attestationObject is not valid. This is true for `Registration::deserialize`
    247     ///     // as well since attestationObject parsing is always deferred.
    248     ///     serde_json::from_str::<CustomRegistration>(
    249     ///         r#"{
    250     ///             "transports": ["usb"],
    251     ///             "attestationObject": "AA",
    252     ///             "authenticatorAttachment": "cross-platform",
    253     ///             "clientExtensionResults": {},
    254     ///             "clientDataJSON": "AA",
    255     ///             "type": "public-key"
    256     ///         }"#
    257     ///     ).is_ok());
    258     /// ```
    259     #[expect(
    260         clippy::too_many_lines,
    261         reason = "want to hide; thus don't want to put in an outer scope"
    262     )]
    263     #[inline]
    264     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    265     where
    266         D: Deserializer<'de>,
    267     {
    268         /// `Visitor` for `CustomRegistration`.
    269         struct CustomRegistrationVisitor;
    270         impl<'d> Visitor<'d> for CustomRegistrationVisitor {
    271             type Value = CustomRegistration;
    272             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    273                 formatter.write_str("CustomRegistration")
    274             }
    275             #[expect(
    276                 clippy::too_many_lines,
    277                 reason = "want to hide; thus don't want to put in an outer scope"
    278             )]
    279             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    280             where
    281                 A: MapAccess<'d>,
    282             {
    283                 /// Fields in the JSON.
    284                 enum Field {
    285                     /// `attestationObject` key.
    286                     AttestationObject,
    287                     /// `authenticatorAttachment` key.
    288                     AuthenticatorAttachment,
    289                     /// `clientDataJSON` key.
    290                     ClientDataJson,
    291                     /// `clientExtensionResults` key.
    292                     ClientExtensionResults,
    293                     /// `transports` key.
    294                     Transports,
    295                     /// `type` key.
    296                     Type,
    297                 }
    298                 impl<'e> Deserialize<'e> for Field {
    299                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    300                     where
    301                         D: Deserializer<'e>,
    302                     {
    303                         /// `Visitor` for `Field`.
    304                         struct FieldVisitor;
    305                         impl Visitor<'_> for FieldVisitor {
    306                             type Value = Field;
    307                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    308                                 write!(
    309                                     formatter,
    310                                     "'{ATTESTATION_OBJECT}', '{AUTHENTICATOR_ATTACHMENT}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{TRANSPORTS}', or '{TYPE}'"
    311                                 )
    312                             }
    313                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    314                             where
    315                                 E: Error,
    316                             {
    317                                 match v {
    318                                     ATTESTATION_OBJECT => Ok(Field::AttestationObject),
    319                                     AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment),
    320                                     CLIENT_DATA_JSON => Ok(Field::ClientDataJson),
    321                                     CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults),
    322                                     TRANSPORTS => Ok(Field::Transports),
    323                                     TYPE => Ok(Field::Type),
    324                                     _ => Err(E::unknown_field(v, FIELDS)),
    325                                 }
    326                             }
    327                         }
    328                         deserializer.deserialize_identifier(FieldVisitor)
    329                     }
    330                 }
    331                 let mut attestation_object = None;
    332                 let mut authenticator_attachment = None;
    333                 let mut client_data_json = None;
    334                 let mut ext = None;
    335                 let mut transports = None;
    336                 let mut typ = false;
    337                 while let Some(key) = map.next_key()? {
    338                     match key {
    339                         Field::AttestationObject => {
    340                             if attestation_object.is_some() {
    341                                 return Err(Error::duplicate_field(ATTESTATION_OBJECT));
    342                             }
    343                             attestation_object =
    344                                 map.next_value::<AttObj>().map(|val| Some(val.0))?;
    345                         }
    346                         Field::AuthenticatorAttachment => {
    347                             if authenticator_attachment.is_some() {
    348                                 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT));
    349                             }
    350                             authenticator_attachment = map.next_value::<Option<_>>().map(Some)?;
    351                         }
    352                         Field::ClientDataJson => {
    353                             if client_data_json.is_some() {
    354                                 return Err(Error::duplicate_field(CLIENT_DATA_JSON));
    355                             }
    356                             client_data_json = map
    357                                 .next_value::<Base64DecodedVal>()
    358                                 .map(|val| Some(val.0))?;
    359                         }
    360                         Field::ClientExtensionResults => {
    361                             if ext.is_some() {
    362                                 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS));
    363                             }
    364                             ext = map.next_value().map(Some)?;
    365                         }
    366                         Field::Transports => {
    367                             if transports.is_some() {
    368                                 return Err(Error::duplicate_field(TRANSPORTS));
    369                             }
    370                             transports = map.next_value().map(Some)?;
    371                         }
    372                         Field::Type => {
    373                             if typ {
    374                                 return Err(Error::duplicate_field(TYPE));
    375                             }
    376                             typ = map.next_value::<Type>().map(|_| true)?;
    377                         }
    378                     }
    379                 }
    380                 attestation_object
    381                     .ok_or_else(|| Error::missing_field(ATTESTATION_OBJECT))
    382                     .and_then(|att_obj| {
    383                         client_data_json
    384                             .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON))
    385                             .and_then(|c_data| {
    386                                 ext.ok_or_else(|| Error::missing_field(CLIENT_EXTENSION_RESULTS))
    387                                     .and_then(|client_extension_results| {
    388                                         transports
    389                                             .ok_or_else(|| Error::missing_field(TRANSPORTS))
    390                                             .map(|trans| {
    391                                                 CustomRegistration(Registration {
    392                                                     response: AuthenticatorAttestation::new(
    393                                                         c_data, att_obj, trans,
    394                                                     ),
    395                                                     authenticator_attachment:
    396                                                         authenticator_attachment.map_or(
    397                                                             AuthenticatorAttachment::None,
    398                                                             |auth_attach| {
    399                                                                 auth_attach.unwrap_or(
    400                                                                     AuthenticatorAttachment::None,
    401                                                                 )
    402                                                             },
    403                                                         ),
    404                                                     client_extension_results,
    405                                                 })
    406                                             })
    407                                     })
    408                             })
    409                     })
    410             }
    411         }
    412         /// `attestationObject` key.
    413         const ATTESTATION_OBJECT: &str = "attestationObject";
    414         /// `authenticatorAttachment` key.
    415         const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment";
    416         /// `clientDataJSON` key.
    417         const CLIENT_DATA_JSON: &str = "clientDataJSON";
    418         /// `clientExtensionResults` key.
    419         const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults";
    420         /// `transports` key.
    421         const TRANSPORTS: &str = "transports";
    422         /// `type` key.
    423         const TYPE: &str = "type";
    424         /// Fields.
    425         const FIELDS: &[&str; 6] = &[
    426             ATTESTATION_OBJECT,
    427             AUTHENTICATOR_ATTACHMENT,
    428             CLIENT_DATA_JSON,
    429             CLIENT_EXTENSION_RESULTS,
    430             TRANSPORTS,
    431             TYPE,
    432         ];
    433         deserializer.deserialize_struct("CustomRegistration", FIELDS, CustomRegistrationVisitor)
    434     }
    435 }
    436 #[cfg(test)]
    437 mod tests {
    438     use super::{
    439         super::{
    440             super::super::request::register::CoseAlgorithmIdentifier, ALG, AuthenticatorAttachment,
    441             EC2, EDDSA, ES256, ES384, KTY, OKP, RSA, cbor,
    442         },
    443         CustomRegistration, RegistrationRelaxed,
    444     };
    445     use ed25519_dalek::{VerifyingKey, pkcs8::EncodePublicKey as _};
    446     use p256::{
    447         EncodedPoint as P256Pt, PublicKey as P256PubKey, SecretKey as P256Key,
    448         elliptic_curve::sec1::{FromEncodedPoint as _, ToEncodedPoint as _},
    449     };
    450     use p384::{EncodedPoint as P384Pt, PublicKey as P384PubKey, SecretKey as P384Key};
    451     use rsa::{
    452         BigUint, RsaPrivateKey,
    453         sha2::{Digest as _, Sha256},
    454         traits::PublicKeyParts as _,
    455     };
    456     use serde::de::{Error as _, Unexpected};
    457     use serde_json::Error;
    458     #[expect(clippy::unwrap_used, reason = "OK in tests")]
    459     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
    460     #[expect(
    461         clippy::cognitive_complexity,
    462         clippy::too_many_lines,
    463         reason = "a lot to test"
    464     )]
    465     #[test]
    466     fn eddsa_registration_deserialize_data_mismatch() {
    467         let c_data_json = serde_json::json!({}).to_string();
    468         let att_obj: [u8; 143] = [
    469             cbor::MAP_3,
    470             cbor::TEXT_3,
    471             b'f',
    472             b'm',
    473             b't',
    474             cbor::TEXT_4,
    475             b'n',
    476             b'o',
    477             b'n',
    478             b'e',
    479             cbor::TEXT_7,
    480             b'a',
    481             b't',
    482             b't',
    483             b'S',
    484             b't',
    485             b'm',
    486             b't',
    487             cbor::MAP_0,
    488             cbor::TEXT_8,
    489             b'a',
    490             b'u',
    491             b't',
    492             b'h',
    493             b'D',
    494             b'a',
    495             b't',
    496             b'a',
    497             cbor::BYTES_INFO_24,
    498             113,
    499             // `rpIdHash`.
    500             0,
    501             0,
    502             0,
    503             0,
    504             0,
    505             0,
    506             0,
    507             0,
    508             0,
    509             0,
    510             0,
    511             0,
    512             0,
    513             0,
    514             0,
    515             0,
    516             0,
    517             0,
    518             0,
    519             0,
    520             0,
    521             0,
    522             0,
    523             0,
    524             0,
    525             0,
    526             0,
    527             0,
    528             0,
    529             0,
    530             0,
    531             0,
    532             // `flags`.
    533             0b0100_0101,
    534             // `signCount`.
    535             0,
    536             0,
    537             0,
    538             0,
    539             // `aaguid`.
    540             0,
    541             0,
    542             0,
    543             0,
    544             0,
    545             0,
    546             0,
    547             0,
    548             0,
    549             0,
    550             0,
    551             0,
    552             0,
    553             0,
    554             0,
    555             0,
    556             // `credentialIdLength`.
    557             0,
    558             16,
    559             // `credentialId`.
    560             0,
    561             0,
    562             0,
    563             0,
    564             0,
    565             0,
    566             0,
    567             0,
    568             0,
    569             0,
    570             0,
    571             0,
    572             0,
    573             0,
    574             0,
    575             0,
    576             // Ed25519 COSE key.
    577             cbor::MAP_4,
    578             KTY,
    579             OKP,
    580             ALG,
    581             EDDSA,
    582             // `crv`.
    583             cbor::NEG_ONE,
    584             // `Ed25519`.
    585             cbor::SIX,
    586             // `x`.
    587             cbor::NEG_TWO,
    588             cbor::BYTES_INFO_24,
    589             32,
    590             // Compressed y-coordinate.
    591             1,
    592             1,
    593             1,
    594             1,
    595             1,
    596             1,
    597             1,
    598             1,
    599             1,
    600             1,
    601             1,
    602             1,
    603             1,
    604             1,
    605             1,
    606             1,
    607             1,
    608             1,
    609             1,
    610             1,
    611             1,
    612             1,
    613             1,
    614             1,
    615             1,
    616             1,
    617             1,
    618             1,
    619             1,
    620             1,
    621             1,
    622             1,
    623         ];
    624         let pub_key = VerifyingKey::from_bytes(&[1; 32])
    625             .unwrap()
    626             .to_public_key_der()
    627             .unwrap();
    628         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
    629         let att_obj_len = att_obj.len();
    630         let auth_data_start = att_obj_len - 113;
    631         let b64_adata = base64url_nopad::encode(&att_obj[auth_data_start..]);
    632         let b64_key = base64url_nopad::encode(pub_key.as_bytes());
    633         let b64_aobj = base64url_nopad::encode(att_obj.as_slice());
    634         // Base case is valid.
    635         assert!(
    636             serde_json::from_str::<RegistrationRelaxed>(
    637                 serde_json::json!({
    638                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    639                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    640                     "response": {
    641                         "clientDataJSON": b64_cdata_json,
    642                         "authenticatorData": b64_adata,
    643                         "transports": ["ble", "usb", "hybrid", "internal", "nfc", "smart-card"],
    644                         "publicKey": b64_key,
    645                         "publicKeyAlgorithm": -8i8,
    646                         "attestationObject": b64_aobj,
    647                     },
    648                     "authenticatorAttachment": "cross-platform",
    649                     "clientExtensionResults": {},
    650                     "type": "public-key"
    651                 })
    652                 .to_string()
    653                 .as_str()
    654             )
    655             .is_ok_and(
    656                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
    657                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
    658                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
    659                         == *Sha256::digest(c_data_json.as_bytes())
    660                     && reg.0.response.transports.count() == 6
    661                     && matches!(
    662                         reg.0.authenticator_attachment,
    663                         AuthenticatorAttachment::CrossPlatform
    664                     )
    665                     && reg.0.client_extension_results.cred_props.is_none()
    666                     && reg.0.client_extension_results.prf.is_none()
    667             )
    668         );
    669         // `id` and `rawId` mismatch.
    670         let mut err = Error::invalid_value(
    671             Unexpected::Bytes(
    672                 base64url_nopad::decode(b"ABABABABABABABABABABAA")
    673                     .unwrap()
    674                     .as_slice(),
    675             ),
    676             &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(),
    677         )
    678         .to_string()
    679         .into_bytes();
    680         assert_eq!(
    681             serde_json::from_str::<RegistrationRelaxed>(
    682                 serde_json::json!({
    683                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    684                     "rawId": "ABABABABABABABABABABAA",
    685                     "response": {
    686                         "clientDataJSON": b64_cdata_json,
    687                         "authenticatorData": b64_adata,
    688                         "transports": [],
    689                         "publicKey": b64_key,
    690                         "publicKeyAlgorithm": -8i8,
    691                         "attestationObject": b64_aobj,
    692                     },
    693                     "clientExtensionResults": {},
    694                     "type": "public-key"
    695                 })
    696                 .to_string()
    697                 .as_str()
    698             )
    699             .unwrap_err()
    700             .to_string()
    701             .into_bytes()
    702             .get(..err.len()),
    703             Some(err.as_slice())
    704         );
    705         // missing `id`.
    706         drop(
    707             serde_json::from_str::<RegistrationRelaxed>(
    708                 serde_json::json!({
    709                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    710                     "response": {
    711                         "clientDataJSON": b64_cdata_json,
    712                         "authenticatorData": b64_adata,
    713                         "transports": [],
    714                         "publicKey": b64_key,
    715                         "publicKeyAlgorithm": -8i8,
    716                         "attestationObject": b64_aobj,
    717                     },
    718                     "clientExtensionResults": {},
    719                     "type": "public-key"
    720                 })
    721                 .to_string()
    722                 .as_str(),
    723             )
    724             .unwrap(),
    725         );
    726         // `null` `id`.
    727         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    728             .to_string()
    729             .into_bytes();
    730         assert_eq!(
    731             serde_json::from_str::<RegistrationRelaxed>(
    732                 serde_json::json!({
    733                     "id": null,
    734                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    735                     "response": {
    736                         "clientDataJSON": null,
    737                         "authenticatorData": b64_adata,
    738                         "transports": [],
    739                         "publicKey": b64_key,
    740                         "publicKeyAlgorithm": -8i8,
    741                         "attestationObject": b64_aobj,
    742                     },
    743                     "clientExtensionResults": {},
    744                     "type": "public-key"
    745                 })
    746                 .to_string()
    747                 .as_str()
    748             )
    749             .unwrap_err()
    750             .to_string()
    751             .into_bytes()
    752             .get(..err.len()),
    753             Some(err.as_slice())
    754         );
    755         // Missing `rawId`.
    756         drop(
    757             serde_json::from_str::<RegistrationRelaxed>(
    758                 serde_json::json!({
    759                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    760                     "response": {
    761                         "clientDataJSON": b64_cdata_json,
    762                         "authenticatorData": b64_adata,
    763                         "transports": [],
    764                         "publicKey": b64_key,
    765                         "publicKeyAlgorithm": -8i8,
    766                         "attestationObject": b64_aobj,
    767                     },
    768                     "clientExtensionResults": {},
    769                     "type": "public-key"
    770                 })
    771                 .to_string()
    772                 .as_str(),
    773             )
    774             .unwrap(),
    775         );
    776         // `null` `rawId`.
    777         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    778             .to_string()
    779             .into_bytes();
    780         assert_eq!(
    781             serde_json::from_str::<RegistrationRelaxed>(
    782                 serde_json::json!({
    783                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    784                     "rawId": null,
    785                     "response": {
    786                         "clientDataJSON": b64_cdata_json,
    787                         "authenticatorData": b64_adata,
    788                         "transports": [],
    789                         "publicKey": b64_key,
    790                         "publicKeyAlgorithm": -8i8,
    791                         "attestationObject": b64_aobj,
    792                     },
    793                     "clientExtensionResults": {},
    794                     "type": "public-key"
    795                 })
    796                 .to_string()
    797                 .as_str()
    798             )
    799             .unwrap_err()
    800             .to_string()
    801             .into_bytes()
    802             .get(..err.len()),
    803             Some(err.as_slice())
    804         );
    805         // `id` and the credential id in authenticator data mismatch.
    806         err = Error::invalid_value(
    807             Unexpected::Bytes(
    808                 base64url_nopad
    809                     ::decode(b"ABABABABABABABABABABAA")
    810                     .unwrap()
    811                     .as_slice(),
    812             ),
    813             &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", [0u8; 16]).as_str(),
    814         )
    815         .to_string().into_bytes();
    816         assert_eq!(
    817             serde_json::from_str::<RegistrationRelaxed>(
    818                 serde_json::json!({
    819                     "id": "ABABABABABABABABABABAA",
    820                     "rawId": "ABABABABABABABABABABAA",
    821                     "response": {
    822                         "clientDataJSON": b64_cdata_json,
    823                         "authenticatorData": b64_adata,
    824                         "transports": [],
    825                         "publicKey": b64_key,
    826                         "publicKeyAlgorithm": -8i8,
    827                         "attestationObject": b64_aobj,
    828                     },
    829                     "clientExtensionResults": {},
    830                     "type": "public-key"
    831                 })
    832                 .to_string()
    833                 .as_str()
    834             )
    835             .unwrap_err()
    836             .to_string()
    837             .into_bytes()
    838             .get(..err.len()),
    839             Some(err.as_slice())
    840         );
    841         // `authenticatorData` mismatches `authData` in attestation object.
    842         let mut bad_auth = [0; 113];
    843         let bad_auth_len = bad_auth.len();
    844         bad_auth.copy_from_slice(&att_obj[auth_data_start..]);
    845         bad_auth[bad_auth_len - 32..].copy_from_slice([0; 32].as_slice());
    846         err = Error::invalid_value(
    847             Unexpected::Bytes(bad_auth.as_slice()),
    848             &format!("authenticator data to match the authenticator data portion of attestation object: {:?}", &att_obj[att_obj_len - bad_auth_len..]).as_str(),
    849         )
    850         .to_string().into_bytes();
    851         assert_eq!(
    852             serde_json::from_str::<RegistrationRelaxed>(
    853                 serde_json::json!({
    854                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    855                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    856                     "response": {
    857                         "clientDataJSON": b64_cdata_json,
    858                         "authenticatorData": base64url_nopad::encode(bad_auth.as_slice()),
    859                         "transports": [],
    860                         "publicKey": b64_key,
    861                         "publicKeyAlgorithm": -8i8,
    862                         "attestationObject": b64_aobj,
    863                     },
    864                     "clientExtensionResults": {},
    865                     "type": "public-key"
    866                 })
    867                 .to_string()
    868                 .as_str()
    869             )
    870             .unwrap_err()
    871             .to_string()
    872             .into_bytes()
    873             .get(..err.len()),
    874             Some(err.as_slice())
    875         );
    876         // Missing `authenticatorData`.
    877         drop(
    878             serde_json::from_str::<RegistrationRelaxed>(
    879                 serde_json::json!({
    880                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    881                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    882                     "response": {
    883                         "clientDataJSON": b64_cdata_json,
    884                         "transports": [],
    885                         "publicKey": b64_key,
    886                         "publicKeyAlgorithm": -8i8,
    887                         "attestationObject": b64_aobj,
    888                     },
    889                     "clientExtensionResults": {},
    890                     "type": "public-key"
    891                 })
    892                 .to_string()
    893                 .as_str(),
    894             )
    895             .unwrap(),
    896         );
    897         // `null `authenticatorData`.
    898         drop(
    899             serde_json::from_str::<RegistrationRelaxed>(
    900                 serde_json::json!({
    901                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    902                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    903                     "response": {
    904                         "clientDataJSON": b64_cdata_json,
    905                         "transports": [],
    906                         "authenticatorData": null,
    907                         "publicKey": b64_key,
    908                         "publicKeyAlgorithm": -8i8,
    909                         "attestationObject": b64_aobj,
    910                     },
    911                     "clientExtensionResults": {},
    912                     "type": "public-key"
    913                 })
    914                 .to_string()
    915                 .as_str(),
    916             )
    917             .unwrap(),
    918         );
    919         // `publicKeyAlgorithm` mismatch.
    920         err = Error::invalid_value(
    921             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()),
    922             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Eddsa).as_str()
    923         )
    924         .to_string().into_bytes();
    925         assert_eq!(
    926             serde_json::from_str::<RegistrationRelaxed>(
    927                 serde_json::json!({
    928                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    929                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    930                     "response": {
    931                         "clientDataJSON": b64_cdata_json,
    932                         "authenticatorData": b64_adata,
    933                         "transports": [],
    934                         "publicKey": b64_key,
    935                         "publicKeyAlgorithm": -7i8,
    936                         "attestationObject": b64_aobj,
    937                     },
    938                     "clientExtensionResults": {},
    939                     "type": "public-key"
    940                 })
    941                 .to_string()
    942                 .as_str()
    943             )
    944             .unwrap_err()
    945             .to_string()
    946             .into_bytes()
    947             .get(..err.len()),
    948             Some(err.as_slice())
    949         );
    950         // Missing `publicKeyAlgorithm`.
    951         drop(
    952             serde_json::from_str::<RegistrationRelaxed>(
    953                 serde_json::json!({
    954                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    955                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    956                     "response": {
    957                         "clientDataJSON": b64_cdata_json,
    958                         "authenticatorData": b64_adata,
    959                         "transports": [],
    960                         "publicKey": b64_key,
    961                         "attestationObject": b64_aobj,
    962                     },
    963                     "clientExtensionResults": {},
    964                     "type": "public-key"
    965                 })
    966                 .to_string()
    967                 .as_str(),
    968             )
    969             .unwrap(),
    970         );
    971         // `null` `publicKeyAlgorithm`.
    972         drop(
    973             serde_json::from_str::<RegistrationRelaxed>(
    974                 serde_json::json!({
    975                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    976                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    977                     "response": {
    978                         "clientDataJSON": b64_cdata_json,
    979                         "authenticatorData": b64_adata,
    980                         "transports": [],
    981                         "publicKey": b64_key,
    982                         "publicKeyAlgorithm": null,
    983                         "attestationObject": b64_aobj,
    984                     },
    985                     "clientExtensionResults": {},
    986                     "type": "public-key"
    987                 })
    988                 .to_string()
    989                 .as_str(),
    990             )
    991             .unwrap(),
    992         );
    993         // `publicKey` mismatch.
    994         err = Error::invalid_value(
    995             Unexpected::Bytes([0; 32].as_slice()),
    996             &format!(
    997                 "DER-encoded public key to match the public key within the attestation object: Ed25519(Ed25519PubKey({:?}))",
    998                 &att_obj[att_obj_len - 32..],
    999             )
   1000             .as_str(),
   1001         )
   1002         .to_string().into_bytes();
   1003         assert_eq!(serde_json::from_str::<RegistrationRelaxed>(
   1004             serde_json::json!({
   1005                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1006                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1007                 "response": {
   1008                     "clientDataJSON": b64_cdata_json,
   1009                     "authenticatorData": b64_adata,
   1010                     "transports": [],
   1011                     "publicKey": base64url_nopad::encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()),
   1012                     "publicKeyAlgorithm": -8i8,
   1013                     "attestationObject": b64_aobj,
   1014                 },
   1015                 "clientExtensionResults": {},
   1016                 "type": "public-key"
   1017             })
   1018             .to_string()
   1019             .as_str()
   1020             )
   1021             .unwrap_err().to_string().into_bytes().get(..err.len()),
   1022             Some(err.as_slice())
   1023         );
   1024         // Missing `publicKey`.
   1025         drop(
   1026             serde_json::from_str::<RegistrationRelaxed>(
   1027                 serde_json::json!({
   1028                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1029                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1030                     "response": {
   1031                         "clientDataJSON": b64_cdata_json,
   1032                         "authenticatorData": b64_adata,
   1033                         "transports": [],
   1034                         "publicKeyAlgorithm": -8i8,
   1035                         "attestationObject": b64_aobj,
   1036                     },
   1037                     "clientExtensionResults": {},
   1038                     "type": "public-key"
   1039                 })
   1040                 .to_string()
   1041                 .as_str(),
   1042             )
   1043             .unwrap(),
   1044         );
   1045         // `null` `publicKey`.
   1046         drop(
   1047             serde_json::from_str::<RegistrationRelaxed>(
   1048                 serde_json::json!({
   1049                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1050                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1051                     "response": {
   1052                         "clientDataJSON": b64_cdata_json,
   1053                         "authenticatorData": b64_adata,
   1054                         "transports": [],
   1055                         "publicKey": null,
   1056                         "publicKeyAlgorithm": -8i8,
   1057                         "attestationObject": b64_aobj,
   1058                     },
   1059                     "clientExtensionResults": {},
   1060                     "type": "public-key"
   1061                 })
   1062                 .to_string()
   1063                 .as_str(),
   1064             )
   1065             .unwrap(),
   1066         );
   1067         // Missing `transports`.
   1068         drop(
   1069             serde_json::from_str::<RegistrationRelaxed>(
   1070                 serde_json::json!({
   1071                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1072                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1073                     "response": {
   1074                         "clientDataJSON": b64_cdata_json,
   1075                         "authenticatorData": b64_adata,
   1076                         "publicKey": b64_key,
   1077                         "publicKeyAlgorithm": -8i8,
   1078                         "attestationObject": b64_aobj,
   1079                     },
   1080                     "clientExtensionResults": {},
   1081                     "type": "public-key"
   1082                 })
   1083                 .to_string()
   1084                 .as_str(),
   1085             )
   1086             .unwrap(),
   1087         );
   1088         // Duplicate `transports` are allowed.
   1089         assert!(
   1090             serde_json::from_str::<RegistrationRelaxed>(
   1091                 serde_json::json!({
   1092                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1093                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1094                     "response": {
   1095                         "clientDataJSON": b64_cdata_json,
   1096                         "authenticatorData": b64_adata,
   1097                         "transports": ["usb", "usb"],
   1098                         "publicKey": b64_key,
   1099                         "publicKeyAlgorithm": -8i8,
   1100                         "attestationObject": b64_aobj,
   1101                     },
   1102                     "clientExtensionResults": {},
   1103                     "type": "public-key"
   1104                 })
   1105                 .to_string()
   1106                 .as_str()
   1107             )
   1108             .is_ok_and(|reg| reg.0.response.transports.count() == 1)
   1109         );
   1110         // `null` `transports`.
   1111         drop(
   1112             serde_json::from_str::<RegistrationRelaxed>(
   1113                 serde_json::json!({
   1114                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1115                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1116                     "response": {
   1117                         "clientDataJSON": b64_cdata_json,
   1118                         "authenticatorData": b64_adata,
   1119                         "transports": null,
   1120                         "publicKey": b64_key,
   1121                         "publicKeyAlgorithm": -8i8,
   1122                         "attestationObject": b64_aobj,
   1123                     },
   1124                     "clientExtensionResults": {},
   1125                     "type": "public-key"
   1126                 })
   1127                 .to_string()
   1128                 .as_str(),
   1129             )
   1130             .unwrap(),
   1131         );
   1132         // Unknown `transports`.
   1133         err = Error::invalid_value(
   1134             Unexpected::Str("Usb"),
   1135             &"'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', or 'usb'",
   1136         )
   1137         .to_string()
   1138         .into_bytes();
   1139         assert_eq!(
   1140             serde_json::from_str::<RegistrationRelaxed>(
   1141                 serde_json::json!({
   1142                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1143                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1144                     "response": {
   1145                         "clientDataJSON": b64_cdata_json,
   1146                         "authenticatorData": b64_adata,
   1147                         "transports": ["Usb"],
   1148                         "publicKey": b64_key,
   1149                         "publicKeyAlgorithm": -8i8,
   1150                         "attestationObject": b64_aobj,
   1151                     },
   1152                     "clientExtensionResults": {},
   1153                     "type": "public-key"
   1154                 })
   1155                 .to_string()
   1156                 .as_str()
   1157             )
   1158             .unwrap_err()
   1159             .to_string()
   1160             .into_bytes()
   1161             .get(..err.len()),
   1162             Some(err.as_slice())
   1163         );
   1164         // `null` `authenticatorAttachment`.
   1165         assert!(
   1166             serde_json::from_str::<RegistrationRelaxed>(
   1167                 serde_json::json!({
   1168                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1169                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1170                     "response": {
   1171                         "clientDataJSON": b64_cdata_json,
   1172                         "authenticatorData": b64_adata,
   1173                         "transports": [],
   1174                         "publicKey": b64_key,
   1175                         "publicKeyAlgorithm": -8i8,
   1176                         "attestationObject": b64_aobj,
   1177                     },
   1178                     "authenticatorAttachment": null,
   1179                     "clientExtensionResults": {},
   1180                     "type": "public-key"
   1181                 })
   1182                 .to_string()
   1183                 .as_str()
   1184             )
   1185             .is_ok_and(|reg| matches!(
   1186                 reg.0.authenticator_attachment,
   1187                 AuthenticatorAttachment::None
   1188             ))
   1189         );
   1190         // Unknown `authenticatorAttachment`.
   1191         err = Error::invalid_value(
   1192             Unexpected::Str("Platform"),
   1193             &"'platform' or 'cross-platform'",
   1194         )
   1195         .to_string()
   1196         .into_bytes();
   1197         assert_eq!(
   1198             serde_json::from_str::<RegistrationRelaxed>(
   1199                 serde_json::json!({
   1200                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1201                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1202                     "response": {
   1203                         "clientDataJSON": b64_cdata_json,
   1204                         "authenticatorData": b64_adata,
   1205                         "transports": [],
   1206                         "publicKey": b64_key,
   1207                         "publicKeyAlgorithm": -8i8,
   1208                         "attestationObject": b64_aobj,
   1209                     },
   1210                     "authenticatorAttachment": "Platform",
   1211                     "clientExtensionResults": {},
   1212                     "type": "public-key"
   1213                 })
   1214                 .to_string()
   1215                 .as_str()
   1216             )
   1217             .unwrap_err()
   1218             .to_string()
   1219             .into_bytes()
   1220             .get(..err.len()),
   1221             Some(err.as_slice())
   1222         );
   1223         // Missing `clientDataJSON`.
   1224         err = Error::missing_field("clientDataJSON")
   1225             .to_string()
   1226             .into_bytes();
   1227         assert_eq!(
   1228             serde_json::from_str::<RegistrationRelaxed>(
   1229                 serde_json::json!({
   1230                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1231                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1232                     "response": {
   1233                         "authenticatorData": b64_adata,
   1234                         "transports": [],
   1235                         "publicKey": b64_key,
   1236                         "publicKeyAlgorithm": -8i8,
   1237                         "attestationObject": b64_aobj,
   1238                     },
   1239                     "clientExtensionResults": {},
   1240                     "type": "public-key"
   1241                 })
   1242                 .to_string()
   1243                 .as_str()
   1244             )
   1245             .unwrap_err()
   1246             .to_string()
   1247             .into_bytes()
   1248             .get(..err.len()),
   1249             Some(err.as_slice())
   1250         );
   1251         // `null` `clientDataJSON`.
   1252         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
   1253             .to_string()
   1254             .into_bytes();
   1255         assert_eq!(
   1256             serde_json::from_str::<RegistrationRelaxed>(
   1257                 serde_json::json!({
   1258                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1259                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1260                     "response": {
   1261                         "clientDataJSON": null,
   1262                         "authenticatorData": b64_adata,
   1263                         "transports": [],
   1264                         "publicKey": b64_key,
   1265                         "publicKeyAlgorithm": -8i8,
   1266                         "attestationObject": b64_aobj,
   1267                     },
   1268                     "clientExtensionResults": {},
   1269                     "type": "public-key"
   1270                 })
   1271                 .to_string()
   1272                 .as_str()
   1273             )
   1274             .unwrap_err()
   1275             .to_string()
   1276             .into_bytes()
   1277             .get(..err.len()),
   1278             Some(err.as_slice())
   1279         );
   1280         // Missing `attestationObject`.
   1281         err = Error::missing_field("attestationObject")
   1282             .to_string()
   1283             .into_bytes();
   1284         assert_eq!(
   1285             serde_json::from_str::<RegistrationRelaxed>(
   1286                 serde_json::json!({
   1287                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1288                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1289                     "response": {
   1290                         "clientDataJSON": b64_cdata_json,
   1291                         "authenticatorData": b64_adata,
   1292                         "transports": [],
   1293                         "publicKey": b64_key,
   1294                         "publicKeyAlgorithm": -8i8,
   1295                     },
   1296                     "clientExtensionResults": {},
   1297                     "type": "public-key"
   1298                 })
   1299                 .to_string()
   1300                 .as_str()
   1301             )
   1302             .unwrap_err()
   1303             .to_string()
   1304             .into_bytes()
   1305             .get(..err.len()),
   1306             Some(err.as_slice())
   1307         );
   1308         // `null` `attestationObject`.
   1309         err = Error::invalid_type(
   1310             Unexpected::Other("null"),
   1311             &"base64url-encoded attestation object",
   1312         )
   1313         .to_string()
   1314         .into_bytes();
   1315         assert_eq!(
   1316             serde_json::from_str::<RegistrationRelaxed>(
   1317                 serde_json::json!({
   1318                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1319                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1320                     "response": {
   1321                         "clientDataJSON": b64_cdata_json,
   1322                         "authenticatorData": b64_adata,
   1323                         "transports": [],
   1324                         "publicKey": b64_key,
   1325                         "publicKeyAlgorithm": -8i8,
   1326                         "attestationObject": null,
   1327                     },
   1328                     "clientExtensionResults": {},
   1329                     "type": "public-key"
   1330                 })
   1331                 .to_string()
   1332                 .as_str()
   1333             )
   1334             .unwrap_err()
   1335             .to_string()
   1336             .into_bytes()
   1337             .get(..err.len()),
   1338             Some(err.as_slice())
   1339         );
   1340         // Missing `response`.
   1341         err = Error::missing_field("response").to_string().into_bytes();
   1342         assert_eq!(
   1343             serde_json::from_str::<RegistrationRelaxed>(
   1344                 serde_json::json!({
   1345                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1346                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1347                     "clientExtensionResults": {},
   1348                     "type": "public-key"
   1349                 })
   1350                 .to_string()
   1351                 .as_str()
   1352             )
   1353             .unwrap_err()
   1354             .to_string()
   1355             .into_bytes()
   1356             .get(..err.len()),
   1357             Some(err.as_slice())
   1358         );
   1359         // `null` `response`.
   1360         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAttestation")
   1361             .to_string()
   1362             .into_bytes();
   1363         assert_eq!(
   1364             serde_json::from_str::<RegistrationRelaxed>(
   1365                 serde_json::json!({
   1366                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1367                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1368                     "response": null,
   1369                     "clientExtensionResults": {},
   1370                     "type": "public-key"
   1371                 })
   1372                 .to_string()
   1373                 .as_str()
   1374             )
   1375             .unwrap_err()
   1376             .to_string()
   1377             .into_bytes()
   1378             .get(..err.len()),
   1379             Some(err.as_slice())
   1380         );
   1381         // Empty `response`.
   1382         err = Error::missing_field("clientDataJSON")
   1383             .to_string()
   1384             .into_bytes();
   1385         assert_eq!(
   1386             serde_json::from_str::<RegistrationRelaxed>(
   1387                 serde_json::json!({
   1388                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1389                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1390                     "response": {},
   1391                     "clientExtensionResults": {},
   1392                     "type": "public-key"
   1393                 })
   1394                 .to_string()
   1395                 .as_str()
   1396             )
   1397             .unwrap_err()
   1398             .to_string()
   1399             .into_bytes()
   1400             .get(..err.len()),
   1401             Some(err.as_slice())
   1402         );
   1403         // Missing `clientExtensionResults`.
   1404         drop(
   1405             serde_json::from_str::<RegistrationRelaxed>(
   1406                 serde_json::json!({
   1407                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1408                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1409                     "response": {
   1410                         "clientDataJSON": b64_cdata_json,
   1411                         "authenticatorData": b64_adata,
   1412                         "transports": [],
   1413                         "publicKey": b64_key,
   1414                         "publicKeyAlgorithm": -8i8,
   1415                         "attestationObject": b64_aobj,
   1416                     },
   1417                     "type": "public-key"
   1418                 })
   1419                 .to_string()
   1420                 .as_str(),
   1421             )
   1422             .unwrap(),
   1423         );
   1424         // `null` `clientExtensionResults`.
   1425         drop(
   1426             serde_json::from_str::<RegistrationRelaxed>(
   1427                 serde_json::json!({
   1428                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1429                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1430                     "response": {
   1431                         "clientDataJSON": b64_cdata_json,
   1432                         "authenticatorData": b64_adata,
   1433                         "transports": [],
   1434                         "publicKey": b64_key,
   1435                         "publicKeyAlgorithm": -8i8,
   1436                         "attestationObject": b64_aobj,
   1437                     },
   1438                     "clientExtensionResults": null,
   1439                     "type": "public-key"
   1440                 })
   1441                 .to_string()
   1442                 .as_str(),
   1443             )
   1444             .unwrap(),
   1445         );
   1446         // Missing `type`.
   1447         drop(
   1448             serde_json::from_str::<RegistrationRelaxed>(
   1449                 serde_json::json!({
   1450                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1451                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1452                     "response": {
   1453                         "clientDataJSON": b64_cdata_json,
   1454                         "authenticatorData": b64_adata,
   1455                         "transports": [],
   1456                         "publicKey": b64_key,
   1457                         "publicKeyAlgorithm": -8i8,
   1458                         "attestationObject": b64_aobj,
   1459                     },
   1460                     "clientExtensionResults": {},
   1461                 })
   1462                 .to_string()
   1463                 .as_str(),
   1464             )
   1465             .unwrap(),
   1466         );
   1467         // `null` `type`.
   1468         err = Error::invalid_type(Unexpected::Other("null"), &"public-key")
   1469             .to_string()
   1470             .into_bytes();
   1471         assert_eq!(
   1472             serde_json::from_str::<RegistrationRelaxed>(
   1473                 serde_json::json!({
   1474                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1475                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1476                     "response": {
   1477                         "clientDataJSON": b64_cdata_json,
   1478                         "authenticatorData": b64_adata,
   1479                         "transports": [],
   1480                         "publicKey": b64_key,
   1481                         "publicKeyAlgorithm": -8i8,
   1482                         "attestationObject": b64_aobj,
   1483                     },
   1484                     "clientExtensionResults": {},
   1485                     "type": null
   1486                 })
   1487                 .to_string()
   1488                 .as_str()
   1489             )
   1490             .unwrap_err()
   1491             .to_string()
   1492             .into_bytes()
   1493             .get(..err.len()),
   1494             Some(err.as_slice())
   1495         );
   1496         // Not exactly `public-type` `type`.
   1497         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
   1498             .to_string()
   1499             .into_bytes();
   1500         assert_eq!(
   1501             serde_json::from_str::<RegistrationRelaxed>(
   1502                 serde_json::json!({
   1503                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1504                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1505                     "response": {
   1506                         "clientDataJSON": b64_cdata_json,
   1507                         "authenticatorData": b64_adata,
   1508                         "transports": [],
   1509                         "publicKey": b64_key,
   1510                         "publicKeyAlgorithm": -8i8,
   1511                         "attestationObject": b64_aobj,
   1512                     },
   1513                     "clientExtensionResults": {},
   1514                     "type": "Public-key"
   1515                 })
   1516                 .to_string()
   1517                 .as_str()
   1518             )
   1519             .unwrap_err()
   1520             .to_string()
   1521             .into_bytes()
   1522             .get(..err.len()),
   1523             Some(err.as_slice())
   1524         );
   1525         // `null`.
   1526         err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential")
   1527             .to_string()
   1528             .into_bytes();
   1529         assert_eq!(
   1530             serde_json::from_str::<RegistrationRelaxed>(
   1531                 serde_json::json!(null).to_string().as_str()
   1532             )
   1533             .unwrap_err()
   1534             .to_string()
   1535             .into_bytes()
   1536             .get(..err.len()),
   1537             Some(err.as_slice())
   1538         );
   1539         // Empty.
   1540         err = Error::missing_field("response").to_string().into_bytes();
   1541         assert_eq!(
   1542             serde_json::from_str::<RegistrationRelaxed>(serde_json::json!({}).to_string().as_str())
   1543                 .unwrap_err()
   1544                 .to_string()
   1545                 .into_bytes()
   1546                 .get(..err.len()),
   1547             Some(err.as_slice())
   1548         );
   1549         // Unknown field in `response`.
   1550         drop(
   1551             serde_json::from_str::<RegistrationRelaxed>(
   1552                 serde_json::json!({
   1553                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1554                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1555                     "response": {
   1556                         "clientDataJSON": b64_cdata_json,
   1557                         "authenticatorData": b64_adata,
   1558                         "transports": [],
   1559                         "publicKey": b64_key,
   1560                         "publicKeyAlgorithm": -8i8,
   1561                         "attestationObject": b64_aobj,
   1562                         "foo": true,
   1563                     },
   1564                     "clientExtensionResults": {},
   1565                     "type": "public-key"
   1566                 })
   1567                 .to_string()
   1568                 .as_str(),
   1569             )
   1570             .unwrap(),
   1571         );
   1572         // Duplicate field in `response`.
   1573         err = Error::duplicate_field("transports")
   1574             .to_string()
   1575             .into_bytes();
   1576         assert_eq!(
   1577             serde_json::from_str::<RegistrationRelaxed>(
   1578                 format!(
   1579                     "{{
   1580                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1581                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1582                        \"response\": {{
   1583                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1584                            \"authenticatorData\": \"{b64_adata}\",
   1585                            \"transports\": [],
   1586                            \"publicKey\": \"{b64_key}\",
   1587                            \"publicKeyAlgorithm\": -8,
   1588                            \"attestationObject\": \"{b64_aobj}\",
   1589                            \"transports\": []
   1590                        }},
   1591                        \"clientExtensionResults\": {{}},
   1592                        \"type\": \"public-key\"
   1593 
   1594                      }}"
   1595                 )
   1596                 .as_str()
   1597             )
   1598             .unwrap_err()
   1599             .to_string()
   1600             .into_bytes()
   1601             .get(..err.len()),
   1602             Some(err.as_slice())
   1603         );
   1604         // Unknown field in `PublicKeyCredential`.
   1605         drop(
   1606             serde_json::from_str::<RegistrationRelaxed>(
   1607                 serde_json::json!({
   1608                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1609                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1610                     "response": {
   1611                         "clientDataJSON": b64_cdata_json,
   1612                         "authenticatorData": b64_adata,
   1613                         "transports": [],
   1614                         "publicKey": b64_key,
   1615                         "publicKeyAlgorithm": -8i8,
   1616                         "attestationObject": b64_aobj
   1617                     },
   1618                     "clientExtensionResults": {},
   1619                     "type": "public-key",
   1620                     "foo": true,
   1621                 })
   1622                 .to_string()
   1623                 .as_str(),
   1624             )
   1625             .unwrap(),
   1626         );
   1627         // Duplicate field in `PublicKeyCredential`.
   1628         err = Error::duplicate_field("id").to_string().into_bytes();
   1629         assert_eq!(
   1630             serde_json::from_str::<RegistrationRelaxed>(
   1631                 format!(
   1632                     "{{
   1633                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1634                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1635                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1636                        \"response\": {{
   1637                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1638                            \"authenticatorData\": \"{b64_adata}\",
   1639                            \"transports\": [],
   1640                            \"publicKey\": \"{b64_key}\",
   1641                            \"publicKeyAlgorithm\": -8,
   1642                            \"attestationObject\": \"{b64_aobj}\"
   1643                        }},
   1644                        \"clientExtensionResults\": {{}},
   1645                        \"type\": \"public-key\"
   1646 
   1647                      }}"
   1648                 )
   1649                 .as_str()
   1650             )
   1651             .unwrap_err()
   1652             .to_string()
   1653             .into_bytes()
   1654             .get(..err.len()),
   1655             Some(err.as_slice())
   1656         );
   1657         // Base case is correct.
   1658         assert!(
   1659             serde_json::from_str::<CustomRegistration>(
   1660                 serde_json::json!({
   1661                     "attestationObject": b64_aobj,
   1662                     "authenticatorAttachment": "cross-platform",
   1663                     "clientDataJSON": b64_cdata_json,
   1664                     "clientExtensionResults": {},
   1665                     "transports": ["ble", "usb", "hybrid", "internal", "nfc", "smart-card"],
   1666                     "type": "public-key"
   1667                 })
   1668                 .to_string()
   1669                 .as_str()
   1670             )
   1671             .is_ok_and(
   1672                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   1673                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   1674                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   1675                         == *Sha256::digest(c_data_json.as_bytes())
   1676                     && reg.0.response.transports.count() == 6
   1677                     && matches!(
   1678                         reg.0.authenticator_attachment,
   1679                         AuthenticatorAttachment::CrossPlatform
   1680                     )
   1681                     && reg.0.client_extension_results.cred_props.is_none()
   1682                     && reg.0.client_extension_results.prf.is_none()
   1683             )
   1684         );
   1685         // Missing `transports`.
   1686         err = Error::missing_field("transports").to_string().into_bytes();
   1687         assert_eq!(
   1688             serde_json::from_str::<CustomRegistration>(
   1689                 serde_json::json!({
   1690                     "attestationObject": b64_aobj,
   1691                     "authenticatorAttachment": "cross-platform",
   1692                     "clientDataJSON": b64_cdata_json,
   1693                     "clientExtensionResults": {},
   1694                     "type": "public-key"
   1695                 })
   1696                 .to_string()
   1697                 .as_str()
   1698             )
   1699             .unwrap_err()
   1700             .to_string()
   1701             .into_bytes()
   1702             .get(..err.len()),
   1703             Some(err.as_slice())
   1704         );
   1705         // Duplicate `transports` are allowed.
   1706         assert!(
   1707             serde_json::from_str::<CustomRegistration>(
   1708                 serde_json::json!({
   1709                     "attestationObject": b64_aobj,
   1710                     "authenticatorAttachment": "cross-platform",
   1711                     "clientDataJSON": b64_cdata_json,
   1712                     "clientExtensionResults": {},
   1713                     "transports": ["usb", "usb"],
   1714                     "type": "public-key"
   1715                 })
   1716                 .to_string()
   1717                 .as_str()
   1718             )
   1719             .is_ok_and(|reg| reg.0.response.transports.count() == 1)
   1720         );
   1721         // `null` `transports`.
   1722         err = Error::invalid_type(Unexpected::Other("null"), &"AuthTransports")
   1723             .to_string()
   1724             .into_bytes();
   1725         assert_eq!(
   1726             serde_json::from_str::<CustomRegistration>(
   1727                 serde_json::json!({
   1728                     "clientDataJSON": b64_cdata_json,
   1729                     "transports": null,
   1730                     "attestationObject": b64_aobj,
   1731                     "clientExtensionResults": {},
   1732                     "type": "public-key"
   1733                 })
   1734                 .to_string()
   1735                 .as_str()
   1736             )
   1737             .unwrap_err()
   1738             .to_string()
   1739             .into_bytes()
   1740             .get(..err.len()),
   1741             Some(err.as_slice())
   1742         );
   1743         // Unknown `transports`.
   1744         err = Error::invalid_value(
   1745             Unexpected::Str("Usb"),
   1746             &"'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', or 'usb'",
   1747         )
   1748         .to_string()
   1749         .into_bytes();
   1750         assert_eq!(
   1751             serde_json::from_str::<CustomRegistration>(
   1752                 serde_json::json!({
   1753                     "attestationObject": b64_aobj,
   1754                     "authenticatorAttachment": "cross-platform",
   1755                     "clientDataJSON": b64_cdata_json,
   1756                     "clientExtensionResults": {},
   1757                     "transports": ["Usb"],
   1758                     "type": "public-key"
   1759                 })
   1760                 .to_string()
   1761                 .as_str()
   1762             )
   1763             .unwrap_err()
   1764             .to_string()
   1765             .into_bytes()
   1766             .get(..err.len()),
   1767             Some(err.as_slice())
   1768         );
   1769         // `null` `authenticatorAttachment`.
   1770         assert!(
   1771             serde_json::from_str::<CustomRegistration>(
   1772                 serde_json::json!({
   1773                     "attestationObject": b64_aobj,
   1774                     "authenticatorAttachment": null,
   1775                     "clientDataJSON": b64_cdata_json,
   1776                     "clientExtensionResults": {},
   1777                     "transports": [],
   1778                     "type": "public-key"
   1779                 })
   1780                 .to_string()
   1781                 .as_str()
   1782             )
   1783             .is_ok_and(|reg| matches!(
   1784                 reg.0.authenticator_attachment,
   1785                 AuthenticatorAttachment::None
   1786             ))
   1787         );
   1788         // Unknown `authenticatorAttachment`.
   1789         err = Error::invalid_value(
   1790             Unexpected::Str("Platform"),
   1791             &"'platform' or 'cross-platform'",
   1792         )
   1793         .to_string()
   1794         .into_bytes();
   1795         assert_eq!(
   1796             serde_json::from_str::<CustomRegistration>(
   1797                 serde_json::json!({
   1798                     "attestationObject": b64_aobj,
   1799                     "authenticatorAttachment": "Platform",
   1800                     "clientDataJSON": b64_cdata_json,
   1801                     "clientExtensionResults": {},
   1802                     "transports": [],
   1803                     "type": "public-key"
   1804                 })
   1805                 .to_string()
   1806                 .as_str()
   1807             )
   1808             .unwrap_err()
   1809             .to_string()
   1810             .into_bytes()
   1811             .get(..err.len()),
   1812             Some(err.as_slice())
   1813         );
   1814         // Missing `clientDataJSON`.
   1815         err = Error::missing_field("clientDataJSON")
   1816             .to_string()
   1817             .into_bytes();
   1818         assert_eq!(
   1819             serde_json::from_str::<CustomRegistration>(
   1820                 serde_json::json!({
   1821                     "transports": [],
   1822                     "attestationObject": b64_aobj,
   1823                     "clientExtensionResults": {},
   1824                     "type": "public-key"
   1825                 })
   1826                 .to_string()
   1827                 .as_str()
   1828             )
   1829             .unwrap_err()
   1830             .to_string()
   1831             .into_bytes()
   1832             .get(..err.len()),
   1833             Some(err.as_slice())
   1834         );
   1835         // `null` `clientDataJSON`.
   1836         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
   1837             .to_string()
   1838             .into_bytes();
   1839         assert_eq!(
   1840             serde_json::from_str::<CustomRegistration>(
   1841                 serde_json::json!({
   1842                     "clientDataJSON": null,
   1843                     "transports": [],
   1844                     "attestationObject": b64_aobj,
   1845                 })
   1846                 .to_string()
   1847                 .as_str()
   1848             )
   1849             .unwrap_err()
   1850             .to_string()
   1851             .into_bytes()
   1852             .get(..err.len()),
   1853             Some(err.as_slice())
   1854         );
   1855         // Missing `attestationObject`.
   1856         err = Error::missing_field("attestationObject")
   1857             .to_string()
   1858             .into_bytes();
   1859         assert_eq!(
   1860             serde_json::from_str::<CustomRegistration>(
   1861                 serde_json::json!({
   1862                     "clientDataJSON": b64_cdata_json,
   1863                     "transports": [],
   1864                     "clientExtensionResults": {},
   1865                     "type": "public-key"
   1866                 })
   1867                 .to_string()
   1868                 .as_str()
   1869             )
   1870             .unwrap_err()
   1871             .to_string()
   1872             .into_bytes()
   1873             .get(..err.len()),
   1874             Some(err.as_slice())
   1875         );
   1876         // `null` `attestationObject`.
   1877         err = Error::invalid_type(
   1878             Unexpected::Other("null"),
   1879             &"base64url-encoded attestation object",
   1880         )
   1881         .to_string()
   1882         .into_bytes();
   1883         assert_eq!(
   1884             serde_json::from_str::<CustomRegistration>(
   1885                 serde_json::json!({
   1886                     "clientDataJSON": b64_cdata_json,
   1887                     "transports": [],
   1888                     "attestationObject": null,
   1889                     "clientExtensionResults": {},
   1890                     "type": "public-key"
   1891                 })
   1892                 .to_string()
   1893                 .as_str()
   1894             )
   1895             .unwrap_err()
   1896             .to_string()
   1897             .into_bytes()
   1898             .get(..err.len()),
   1899             Some(err.as_slice())
   1900         );
   1901         // Missing `clientExtensionResults`.
   1902         err = Error::missing_field("clientExtensionResults")
   1903             .to_string()
   1904             .into_bytes();
   1905         assert_eq!(
   1906             serde_json::from_str::<CustomRegistration>(
   1907                 serde_json::json!({
   1908                     "clientDataJSON": b64_cdata_json,
   1909                     "transports": [],
   1910                     "attestationObject": b64_aobj,
   1911                     "type": "public-key"
   1912                 })
   1913                 .to_string()
   1914                 .as_str()
   1915             )
   1916             .unwrap_err()
   1917             .to_string()
   1918             .into_bytes()
   1919             .get(..err.len()),
   1920             Some(err.as_slice())
   1921         );
   1922         // `null` `clientExtensionResults`.
   1923         err = Error::invalid_type(Unexpected::Other("null"), &"ClientExtensionsOutputs")
   1924             .to_string()
   1925             .into_bytes();
   1926         assert_eq!(
   1927             serde_json::from_str::<CustomRegistration>(
   1928                 serde_json::json!({
   1929                     "clientDataJSON": b64_cdata_json,
   1930                     "transports": [],
   1931                     "attestationObject": b64_aobj,
   1932                     "clientExtensionResults": null,
   1933                     "type": "public-key"
   1934                 })
   1935                 .to_string()
   1936                 .as_str()
   1937             )
   1938             .unwrap_err()
   1939             .to_string()
   1940             .into_bytes()
   1941             .get(..err.len()),
   1942             Some(err.as_slice())
   1943         );
   1944         // Missing `type`.
   1945         assert!(
   1946             serde_json::from_str::<CustomRegistration>(
   1947                 serde_json::json!({
   1948                     "attestationObject": b64_aobj,
   1949                     "clientDataJSON": b64_cdata_json,
   1950                     "clientExtensionResults": {},
   1951                     "transports": []
   1952                 })
   1953                 .to_string()
   1954                 .as_str()
   1955             )
   1956             .is_ok_and(|_| true)
   1957         );
   1958         // `null` `type`.
   1959         err = Error::invalid_type(Unexpected::Other("null"), &"public-key")
   1960             .to_string()
   1961             .into_bytes();
   1962         assert_eq!(
   1963             serde_json::from_str::<CustomRegistration>(
   1964                 serde_json::json!({
   1965                     "attestationObject": b64_aobj,
   1966                     "clientDataJSON": b64_cdata_json,
   1967                     "clientExtensionResults": {},
   1968                     "transports": [],
   1969                     "type": null
   1970                 })
   1971                 .to_string()
   1972                 .as_str()
   1973             )
   1974             .unwrap_err()
   1975             .to_string()
   1976             .into_bytes()
   1977             .get(..err.len()),
   1978             Some(err.as_slice())
   1979         );
   1980         // Not exactly `public-type` `type`.
   1981         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
   1982             .to_string()
   1983             .into_bytes();
   1984         assert_eq!(
   1985             serde_json::from_str::<CustomRegistration>(
   1986                 serde_json::json!({
   1987                     "clientDataJSON": b64_cdata_json,
   1988                     "transports": [],
   1989                     "attestationObject": b64_aobj,
   1990                     "clientExtensionResults": {},
   1991                     "type": "Public-key"
   1992                 })
   1993                 .to_string()
   1994                 .as_str()
   1995             )
   1996             .unwrap_err()
   1997             .to_string()
   1998             .into_bytes()
   1999             .get(..err.len()),
   2000             Some(err.as_slice())
   2001         );
   2002         // `null`.
   2003         err = Error::invalid_type(Unexpected::Other("null"), &"CustomRegistration")
   2004             .to_string()
   2005             .into_bytes();
   2006         assert_eq!(
   2007             serde_json::from_str::<CustomRegistration>(
   2008                 serde_json::json!(null).to_string().as_str()
   2009             )
   2010             .unwrap_err()
   2011             .to_string()
   2012             .into_bytes()
   2013             .get(..err.len()),
   2014             Some(err.as_slice())
   2015         );
   2016         // Empty.
   2017         err = Error::missing_field("attestationObject")
   2018             .to_string()
   2019             .into_bytes();
   2020         assert_eq!(
   2021             serde_json::from_str::<CustomRegistration>(serde_json::json!({}).to_string().as_str())
   2022                 .unwrap_err()
   2023                 .to_string()
   2024                 .into_bytes()
   2025                 .get(..err.len()),
   2026             Some(err.as_slice())
   2027         );
   2028         // Unknown field.
   2029         err = Error::unknown_field(
   2030             "foo",
   2031             [
   2032                 "attestationObject",
   2033                 "authenticatorAttachment",
   2034                 "clientDataJSON",
   2035                 "clientExtensionResults",
   2036                 "transports",
   2037                 "type",
   2038             ]
   2039             .as_slice(),
   2040         )
   2041         .to_string()
   2042         .into_bytes();
   2043         assert_eq!(
   2044             serde_json::from_str::<CustomRegistration>(
   2045                 serde_json::json!({
   2046                     "clientDataJSON": b64_cdata_json,
   2047                     "transports": [],
   2048                     "attestationObject": b64_aobj,
   2049                     "foo": true,
   2050                     "clientExtensionResults": {},
   2051                     "type": "public-key"
   2052                 })
   2053                 .to_string()
   2054                 .as_str()
   2055             )
   2056             .unwrap_err()
   2057             .to_string()
   2058             .into_bytes()
   2059             .get(..err.len()),
   2060             Some(err.as_slice())
   2061         );
   2062         // Duplicate field.
   2063         err = Error::duplicate_field("transports")
   2064             .to_string()
   2065             .into_bytes();
   2066         assert_eq!(
   2067             serde_json::from_str::<CustomRegistration>(
   2068                 format!(
   2069                     "{{
   2070                        \"clientDataJSON\": \"{b64_cdata_json}\",
   2071                        \"transports\": [],
   2072                        \"attestationObject\": \"{b64_aobj}\",
   2073                        \"transports\": []
   2074                        \"clientExtensionResults\": {{}},
   2075                        \"type\": \"public-key\"
   2076                      }}"
   2077                 )
   2078                 .as_str()
   2079             )
   2080             .unwrap_err()
   2081             .to_string()
   2082             .into_bytes()
   2083             .get(..err.len()),
   2084             Some(err.as_slice())
   2085         );
   2086     }
   2087     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   2088     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   2089     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   2090     #[test]
   2091     fn client_extensions() {
   2092         let c_data_json = serde_json::json!({}).to_string();
   2093         let att_obj: [u8; 143] = [
   2094             cbor::MAP_3,
   2095             cbor::TEXT_3,
   2096             b'f',
   2097             b'm',
   2098             b't',
   2099             cbor::TEXT_4,
   2100             b'n',
   2101             b'o',
   2102             b'n',
   2103             b'e',
   2104             cbor::TEXT_7,
   2105             b'a',
   2106             b't',
   2107             b't',
   2108             b'S',
   2109             b't',
   2110             b'm',
   2111             b't',
   2112             cbor::MAP_0,
   2113             cbor::TEXT_8,
   2114             b'a',
   2115             b'u',
   2116             b't',
   2117             b'h',
   2118             b'D',
   2119             b'a',
   2120             b't',
   2121             b'a',
   2122             cbor::BYTES_INFO_24,
   2123             113,
   2124             // `rpIdHash`.
   2125             0,
   2126             0,
   2127             0,
   2128             0,
   2129             0,
   2130             0,
   2131             0,
   2132             0,
   2133             0,
   2134             0,
   2135             0,
   2136             0,
   2137             0,
   2138             0,
   2139             0,
   2140             0,
   2141             0,
   2142             0,
   2143             0,
   2144             0,
   2145             0,
   2146             0,
   2147             0,
   2148             0,
   2149             0,
   2150             0,
   2151             0,
   2152             0,
   2153             0,
   2154             0,
   2155             0,
   2156             0,
   2157             // `flags`.
   2158             0b0100_0101,
   2159             // `signCount`.
   2160             0,
   2161             0,
   2162             0,
   2163             0,
   2164             // `aaguid`.
   2165             0,
   2166             0,
   2167             0,
   2168             0,
   2169             0,
   2170             0,
   2171             0,
   2172             0,
   2173             0,
   2174             0,
   2175             0,
   2176             0,
   2177             0,
   2178             0,
   2179             0,
   2180             0,
   2181             // `credentialIdLength`.
   2182             0,
   2183             16,
   2184             // `credentialId`.
   2185             0,
   2186             0,
   2187             0,
   2188             0,
   2189             0,
   2190             0,
   2191             0,
   2192             0,
   2193             0,
   2194             0,
   2195             0,
   2196             0,
   2197             0,
   2198             0,
   2199             0,
   2200             0,
   2201             // Ed25519 COSE key.
   2202             cbor::MAP_4,
   2203             KTY,
   2204             OKP,
   2205             ALG,
   2206             EDDSA,
   2207             // `crv`.
   2208             cbor::NEG_ONE,
   2209             // `Ed25519`.
   2210             cbor::SIX,
   2211             // `x`.
   2212             cbor::NEG_TWO,
   2213             cbor::BYTES_INFO_24,
   2214             32,
   2215             // Compressed y-coordinate.
   2216             1,
   2217             1,
   2218             1,
   2219             1,
   2220             1,
   2221             1,
   2222             1,
   2223             1,
   2224             1,
   2225             1,
   2226             1,
   2227             1,
   2228             1,
   2229             1,
   2230             1,
   2231             1,
   2232             1,
   2233             1,
   2234             1,
   2235             1,
   2236             1,
   2237             1,
   2238             1,
   2239             1,
   2240             1,
   2241             1,
   2242             1,
   2243             1,
   2244             1,
   2245             1,
   2246             1,
   2247             1,
   2248         ];
   2249         let pub_key = VerifyingKey::from_bytes(&[1; 32])
   2250             .unwrap()
   2251             .to_public_key_der()
   2252             .unwrap();
   2253         let att_obj_len = att_obj.len();
   2254         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   2255         let auth_data_start = att_obj_len - 113;
   2256         let b64_adata = base64url_nopad::encode(&att_obj[auth_data_start..]);
   2257         let b64_key = base64url_nopad::encode(pub_key.as_bytes());
   2258         let b64_aobj = base64url_nopad::encode(att_obj.as_slice());
   2259         // Base case is valid.
   2260         assert!(
   2261             serde_json::from_str::<RegistrationRelaxed>(
   2262                 serde_json::json!({
   2263                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2264                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2265                     "response": {
   2266                         "clientDataJSON": b64_cdata_json,
   2267                         "authenticatorData": b64_adata,
   2268                         "transports": [],
   2269                         "publicKey": b64_key,
   2270                         "publicKeyAlgorithm": -8i8,
   2271                         "attestationObject": b64_aobj,
   2272                     },
   2273                     "clientExtensionResults": {},
   2274                     "type": "public-key"
   2275                 })
   2276                 .to_string()
   2277                 .as_str()
   2278             )
   2279             .is_ok_and(
   2280                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   2281                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   2282                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   2283                         == *Sha256::digest(c_data_json.as_bytes())
   2284                     && reg.0.response.transports.is_empty()
   2285                     && matches!(
   2286                         reg.0.authenticator_attachment,
   2287                         AuthenticatorAttachment::None
   2288                     )
   2289                     && reg.0.client_extension_results.cred_props.is_none()
   2290                     && reg.0.client_extension_results.prf.is_none()
   2291             )
   2292         );
   2293         // `null` `credProps`.
   2294         assert!(
   2295             serde_json::from_str::<RegistrationRelaxed>(
   2296                 serde_json::json!({
   2297                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2298                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2299                     "response": {
   2300                         "clientDataJSON": b64_cdata_json,
   2301                         "authenticatorData": b64_adata,
   2302                         "transports": [],
   2303                         "publicKey": b64_key,
   2304                         "publicKeyAlgorithm": -8i8,
   2305                         "attestationObject": b64_aobj,
   2306                     },
   2307                     "clientExtensionResults": {
   2308                         "credProps": null
   2309                     },
   2310                     "type": "public-key"
   2311                 })
   2312                 .to_string()
   2313                 .as_str()
   2314             )
   2315             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2316                 && reg.0.client_extension_results.prf.is_none())
   2317         );
   2318         // `null` `prf`.
   2319         assert!(
   2320             serde_json::from_str::<RegistrationRelaxed>(
   2321                 serde_json::json!({
   2322                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2323                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2324                     "response": {
   2325                         "clientDataJSON": b64_cdata_json,
   2326                         "authenticatorData": b64_adata,
   2327                         "transports": [],
   2328                         "publicKey": b64_key,
   2329                         "publicKeyAlgorithm": -8i8,
   2330                         "attestationObject": b64_aobj,
   2331                     },
   2332                     "clientExtensionResults": {
   2333                         "prf": null
   2334                     },
   2335                     "type": "public-key"
   2336                 })
   2337                 .to_string()
   2338                 .as_str()
   2339             )
   2340             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2341                 && reg.0.client_extension_results.prf.is_none())
   2342         );
   2343         // Unknown `clientExtensionResults`.
   2344         drop(
   2345             serde_json::from_str::<RegistrationRelaxed>(
   2346                 serde_json::json!({
   2347                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2348                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2349                     "response": {
   2350                         "clientDataJSON": b64_cdata_json,
   2351                         "authenticatorData": b64_adata,
   2352                         "transports": [],
   2353                         "publicKey": b64_key,
   2354                         "publicKeyAlgorithm": -8i8,
   2355                         "attestationObject": b64_aobj,
   2356                     },
   2357                     "clientExtensionResults": {
   2358                         "CredProps": {
   2359                             "rk": true
   2360                         }
   2361                     },
   2362                     "type": "public-key"
   2363                 })
   2364                 .to_string()
   2365                 .as_str(),
   2366             )
   2367             .unwrap(),
   2368         );
   2369         // Duplicate field.
   2370         let mut err = Error::duplicate_field("credProps").to_string().into_bytes();
   2371         assert_eq!(
   2372             serde_json::from_str::<RegistrationRelaxed>(
   2373                 format!(
   2374                     "{{
   2375                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2376                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2377                        \"response\": {{
   2378                            \"clientDataJSON\": \"{b64_cdata_json}\",
   2379                            \"authenticatorData\": \"{b64_adata}\",
   2380                            \"transports\": [],
   2381                            \"publicKey\": \"{b64_key}\",
   2382                            \"publicKeyAlgorithm\": -8,
   2383                            \"attestationObject\": \"{b64_aobj}\"
   2384                        }},
   2385                        \"clientExtensionResults\": {{
   2386                            \"credProps\": null,
   2387                            \"credProps\": null
   2388                        }},
   2389                        \"type\": \"public-key\"
   2390                      }}"
   2391                 )
   2392                 .as_str()
   2393             )
   2394             .unwrap_err()
   2395             .to_string()
   2396             .into_bytes()
   2397             .get(..err.len()),
   2398             Some(err.as_slice())
   2399         );
   2400         // `null` `rk`.
   2401         assert!(
   2402             serde_json::from_str::<RegistrationRelaxed>(
   2403                 serde_json::json!({
   2404                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2405                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2406                     "response": {
   2407                         "clientDataJSON": b64_cdata_json,
   2408                         "authenticatorData": b64_adata,
   2409                         "transports": [],
   2410                         "publicKey": b64_key,
   2411                         "publicKeyAlgorithm": -8i8,
   2412                         "attestationObject": b64_aobj,
   2413                     },
   2414                     "clientExtensionResults": {
   2415                         "credProps": {
   2416                             "rk": null
   2417                         }
   2418                     },
   2419                     "type": "public-key"
   2420                 })
   2421                 .to_string()
   2422                 .as_str()
   2423             )
   2424             .is_ok_and(|reg| reg
   2425                 .0
   2426                 .client_extension_results
   2427                 .cred_props
   2428                 .is_some_and(|props| props.rk.is_none())
   2429                 && reg.0.client_extension_results.prf.is_none())
   2430         );
   2431         // Missing `rk`.
   2432         assert!(
   2433             serde_json::from_str::<RegistrationRelaxed>(
   2434                 serde_json::json!({
   2435                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2436                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2437                     "response": {
   2438                         "clientDataJSON": b64_cdata_json,
   2439                         "authenticatorData": b64_adata,
   2440                         "transports": [],
   2441                         "publicKey": b64_key,
   2442                         "publicKeyAlgorithm": -8i8,
   2443                         "attestationObject": b64_aobj,
   2444                     },
   2445                     "clientExtensionResults": {
   2446                         "credProps": {}
   2447                     },
   2448                     "type": "public-key"
   2449                 })
   2450                 .to_string()
   2451                 .as_str()
   2452             )
   2453             .is_ok_and(|reg| reg
   2454                 .0
   2455                 .client_extension_results
   2456                 .cred_props
   2457                 .is_some_and(|props| props.rk.is_none())
   2458                 && reg.0.client_extension_results.prf.is_none())
   2459         );
   2460         // `true` rk`.
   2461         assert!(
   2462             serde_json::from_str::<RegistrationRelaxed>(
   2463                 serde_json::json!({
   2464                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2465                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2466                     "response": {
   2467                         "clientDataJSON": b64_cdata_json,
   2468                         "authenticatorData": b64_adata,
   2469                         "transports": [],
   2470                         "publicKey": b64_key,
   2471                         "publicKeyAlgorithm": -8i8,
   2472                         "attestationObject": b64_aobj,
   2473                     },
   2474                     "clientExtensionResults": {
   2475                         "credProps": {
   2476                             "rk": true
   2477                         }
   2478                     },
   2479                     "type": "public-key"
   2480                 })
   2481                 .to_string()
   2482                 .as_str()
   2483             )
   2484             .is_ok_and(|reg| reg
   2485                 .0
   2486                 .client_extension_results
   2487                 .cred_props
   2488                 .is_some_and(|props| props.rk.unwrap_or_default())
   2489                 && reg.0.client_extension_results.prf.is_none())
   2490         );
   2491         // `false` rk`.
   2492         assert!(
   2493             serde_json::from_str::<RegistrationRelaxed>(
   2494                 serde_json::json!({
   2495                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2496                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2497                     "response": {
   2498                         "clientDataJSON": b64_cdata_json,
   2499                         "authenticatorData": b64_adata,
   2500                         "transports": [],
   2501                         "publicKey": b64_key,
   2502                         "publicKeyAlgorithm": -8i8,
   2503                         "attestationObject": b64_aobj,
   2504                     },
   2505                     "clientExtensionResults": {
   2506                         "credProps": {
   2507                             "rk": false
   2508                         }
   2509                     },
   2510                     "type": "public-key"
   2511                 })
   2512                 .to_string()
   2513                 .as_str()
   2514             )
   2515             .is_ok_and(|reg| reg
   2516                 .0
   2517                 .client_extension_results
   2518                 .cred_props
   2519                 .is_some_and(|props| props.rk.is_some_and(|rk| !rk))
   2520                 && reg.0.client_extension_results.prf.is_none())
   2521         );
   2522         // Invalid `rk`.
   2523         err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean")
   2524             .to_string()
   2525             .into_bytes();
   2526         assert_eq!(
   2527             serde_json::from_str::<RegistrationRelaxed>(
   2528                 serde_json::json!({
   2529                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2530                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2531                     "response": {
   2532                         "clientDataJSON": b64_cdata_json,
   2533                         "authenticatorData": b64_adata,
   2534                         "transports": [],
   2535                         "publicKey": b64_key,
   2536                         "publicKeyAlgorithm": -8i8,
   2537                         "attestationObject": b64_aobj,
   2538                     },
   2539                     "clientExtensionResults": {
   2540                         "credProps": {
   2541                             "rk": 3u8
   2542                         }
   2543                     },
   2544                     "type": "public-key"
   2545                 })
   2546                 .to_string()
   2547                 .as_str()
   2548             )
   2549             .unwrap_err()
   2550             .to_string()
   2551             .into_bytes()
   2552             .get(..err.len()),
   2553             Some(err.as_slice())
   2554         );
   2555         // Unknown `credProps` field.
   2556         drop(
   2557             serde_json::from_str::<RegistrationRelaxed>(
   2558                 serde_json::json!({
   2559                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2560                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2561                     "response": {
   2562                         "clientDataJSON": b64_cdata_json,
   2563                         "authenticatorData": b64_adata,
   2564                         "transports": [],
   2565                         "publicKey": b64_key,
   2566                         "publicKeyAlgorithm": -8i8,
   2567                         "attestationObject": b64_aobj,
   2568                     },
   2569                     "clientExtensionResults": {
   2570                         "credProps": {
   2571                             "Rk": true,
   2572                         }
   2573                     },
   2574                     "type": "public-key"
   2575                 })
   2576                 .to_string()
   2577                 .as_str(),
   2578             )
   2579             .unwrap(),
   2580         );
   2581         // Duplicate field in `credProps`.
   2582         err = Error::duplicate_field("rk").to_string().into_bytes();
   2583         assert_eq!(
   2584             serde_json::from_str::<RegistrationRelaxed>(
   2585                 format!(
   2586                     "{{
   2587                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2588                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2589                        \"response\": {{
   2590                            \"clientDataJSON\": \"{b64_cdata_json}\",
   2591                            \"authenticatorData\": \"{b64_adata}\",
   2592                            \"transports\": [],
   2593                            \"publicKey\": \"{b64_key}\",
   2594                            \"publicKeyAlgorithm\": -8,
   2595                            \"attestationObject\": \"{b64_aobj}\"
   2596                        }},
   2597                        \"clientExtensionResults\": {{
   2598                            \"credProps\": {{
   2599                                \"rk\": true,
   2600                                \"rk\": true
   2601                            }}
   2602                        }},
   2603                        \"type\": \"public-key\"
   2604                      }}"
   2605                 )
   2606                 .as_str()
   2607             )
   2608             .unwrap_err()
   2609             .to_string()
   2610             .into_bytes()
   2611             .get(..err.len()),
   2612             Some(err.as_slice())
   2613         );
   2614         // `null` `enabled`.
   2615         err = Error::invalid_type(Unexpected::Other("null"), &"a boolean")
   2616             .to_string()
   2617             .into_bytes();
   2618         assert_eq!(
   2619             serde_json::from_str::<RegistrationRelaxed>(
   2620                 serde_json::json!({
   2621                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2622                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2623                     "response": {
   2624                         "clientDataJSON": b64_cdata_json,
   2625                         "authenticatorData": b64_adata,
   2626                         "transports": [],
   2627                         "publicKey": b64_key,
   2628                         "publicKeyAlgorithm": -8i8,
   2629                         "attestationObject": b64_aobj,
   2630                     },
   2631                     "clientExtensionResults": {
   2632                         "prf": {
   2633                             "enabled": null
   2634                         }
   2635                     },
   2636                     "type": "public-key"
   2637                 })
   2638                 .to_string()
   2639                 .as_str()
   2640             )
   2641             .unwrap_err()
   2642             .to_string()
   2643             .into_bytes()
   2644             .get(..err.len()),
   2645             Some(err.as_slice())
   2646         );
   2647         // Missing `enabled`.
   2648         err = Error::missing_field("enabled").to_string().into_bytes();
   2649         assert_eq!(
   2650             serde_json::from_str::<RegistrationRelaxed>(
   2651                 serde_json::json!({
   2652                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2653                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2654                     "response": {
   2655                         "clientDataJSON": b64_cdata_json,
   2656                         "authenticatorData": b64_adata,
   2657                         "transports": [],
   2658                         "publicKey": b64_key,
   2659                         "publicKeyAlgorithm": -8i8,
   2660                         "attestationObject": b64_aobj,
   2661                     },
   2662                     "clientExtensionResults": {
   2663                         "prf": {}
   2664                     },
   2665                     "type": "public-key"
   2666                 })
   2667                 .to_string()
   2668                 .as_str()
   2669             )
   2670             .unwrap_err()
   2671             .to_string()
   2672             .into_bytes()
   2673             .get(..err.len()),
   2674             Some(err.as_slice())
   2675         );
   2676         // `true` `enabled`.
   2677         assert!(
   2678             serde_json::from_str::<RegistrationRelaxed>(
   2679                 serde_json::json!({
   2680                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2681                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2682                     "response": {
   2683                         "clientDataJSON": b64_cdata_json,
   2684                         "authenticatorData": b64_adata,
   2685                         "transports": [],
   2686                         "publicKey": b64_key,
   2687                         "publicKeyAlgorithm": -8i8,
   2688                         "attestationObject": b64_aobj,
   2689                     },
   2690                     "clientExtensionResults": {
   2691                         "prf": {
   2692                             "enabled": true
   2693                         }
   2694                     },
   2695                     "type": "public-key"
   2696                 })
   2697                 .to_string()
   2698                 .as_str()
   2699             )
   2700             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2701                 && reg
   2702                     .0
   2703                     .client_extension_results
   2704                     .prf
   2705                     .is_some_and(|prf| prf.enabled))
   2706         );
   2707         // `false` `enabled`.
   2708         assert!(
   2709             serde_json::from_str::<RegistrationRelaxed>(
   2710                 serde_json::json!({
   2711                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2712                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2713                     "response": {
   2714                         "clientDataJSON": b64_cdata_json,
   2715                         "authenticatorData": b64_adata,
   2716                         "transports": [],
   2717                         "publicKey": b64_key,
   2718                         "publicKeyAlgorithm": -8i8,
   2719                         "attestationObject": b64_aobj,
   2720                     },
   2721                     "clientExtensionResults": {
   2722                         "prf": {
   2723                             "enabled": false,
   2724                         }
   2725                     },
   2726                     "type": "public-key"
   2727                 })
   2728                 .to_string()
   2729                 .as_str()
   2730             )
   2731             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2732                 && reg
   2733                     .0
   2734                     .client_extension_results
   2735                     .prf
   2736                     .is_some_and(|prf| !prf.enabled))
   2737         );
   2738         // Invalid `enabled`.
   2739         err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean")
   2740             .to_string()
   2741             .into_bytes();
   2742         assert_eq!(
   2743             serde_json::from_str::<RegistrationRelaxed>(
   2744                 serde_json::json!({
   2745                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2746                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2747                     "response": {
   2748                         "clientDataJSON": b64_cdata_json,
   2749                         "authenticatorData": b64_adata,
   2750                         "transports": [],
   2751                         "publicKey": b64_key,
   2752                         "publicKeyAlgorithm": -8i8,
   2753                         "attestationObject": b64_aobj,
   2754                     },
   2755                     "clientExtensionResults": {
   2756                         "prf": {
   2757                             "enabled": 3u8
   2758                         }
   2759                     },
   2760                     "type": "public-key"
   2761                 })
   2762                 .to_string()
   2763                 .as_str()
   2764             )
   2765             .unwrap_err()
   2766             .to_string()
   2767             .into_bytes()
   2768             .get(..err.len()),
   2769             Some(err.as_slice())
   2770         );
   2771         // `null` `results` with `enabled` `true`.
   2772         assert!(
   2773             serde_json::from_str::<RegistrationRelaxed>(
   2774                 serde_json::json!({
   2775                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2776                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2777                     "response": {
   2778                         "clientDataJSON": b64_cdata_json,
   2779                         "authenticatorData": b64_adata,
   2780                         "transports": [],
   2781                         "publicKey": b64_key,
   2782                         "publicKeyAlgorithm": -8i8,
   2783                         "attestationObject": b64_aobj,
   2784                     },
   2785                     "clientExtensionResults": {
   2786                         "prf": {
   2787                             "enabled": true,
   2788                             "results": null,
   2789                         }
   2790                     },
   2791                     "type": "public-key"
   2792                 })
   2793                 .to_string()
   2794                 .as_str()
   2795             )
   2796             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2797                 && reg
   2798                     .0
   2799                     .client_extension_results
   2800                     .prf
   2801                     .is_some_and(|prf| prf.enabled))
   2802         );
   2803         // `null` `results` with `enabled` `false`.
   2804         err = Error::custom(
   2805             "prf must not have 'results', including a null 'results', if 'enabled' is false",
   2806         )
   2807         .to_string()
   2808         .into_bytes();
   2809         assert_eq!(
   2810             serde_json::from_str::<RegistrationRelaxed>(
   2811                 serde_json::json!({
   2812                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2813                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2814                     "response": {
   2815                         "clientDataJSON": b64_cdata_json,
   2816                         "authenticatorData": b64_adata,
   2817                         "transports": [],
   2818                         "publicKey": b64_key,
   2819                         "publicKeyAlgorithm": -8i8,
   2820                         "attestationObject": b64_aobj,
   2821                     },
   2822                     "clientExtensionResults": {
   2823                         "prf": {
   2824                             "enabled": false,
   2825                             "results": null
   2826                         }
   2827                     },
   2828                     "type": "public-key"
   2829                 })
   2830                 .to_string()
   2831                 .as_str()
   2832             )
   2833             .unwrap_err()
   2834             .to_string()
   2835             .into_bytes()
   2836             .get(..err.len()),
   2837             Some(err.as_slice())
   2838         );
   2839         // Duplicate field in `prf`.
   2840         err = Error::duplicate_field("enabled").to_string().into_bytes();
   2841         assert_eq!(
   2842             serde_json::from_str::<RegistrationRelaxed>(
   2843                 format!(
   2844                     "{{
   2845                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2846                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2847                        \"response\": {{
   2848                            \"clientDataJSON\": \"{b64_cdata_json}\",
   2849                            \"authenticatorData\": \"{b64_adata}\",
   2850                            \"transports\": [],
   2851                            \"publicKey\": \"{b64_key}\",
   2852                            \"publicKeyAlgorithm\": -8,
   2853                            \"attestationObject\": \"{b64_aobj}\"
   2854                        }},
   2855                        \"clientExtensionResults\": {{
   2856                            \"prf\": {{
   2857                                \"enabled\": true,
   2858                                \"enabled\": true
   2859                            }}
   2860                        }},
   2861                        \"type\": \"public-key\"
   2862                      }}"
   2863                 )
   2864                 .as_str()
   2865             )
   2866             .unwrap_err()
   2867             .to_string()
   2868             .into_bytes()
   2869             .get(..err.len()),
   2870             Some(err.as_slice())
   2871         );
   2872         // Missing `first`.
   2873         drop(
   2874             serde_json::from_str::<RegistrationRelaxed>(
   2875                 serde_json::json!({
   2876                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2877                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2878                     "response": {
   2879                         "clientDataJSON": b64_cdata_json,
   2880                         "authenticatorData": b64_adata,
   2881                         "transports": [],
   2882                         "publicKey": b64_key,
   2883                         "publicKeyAlgorithm": -8i8,
   2884                         "attestationObject": b64_aobj,
   2885                     },
   2886                     "clientExtensionResults": {
   2887                         "prf": {
   2888                             "enabled": true,
   2889                             "results": {},
   2890                         }
   2891                     },
   2892                     "type": "public-key"
   2893                 })
   2894                 .to_string()
   2895                 .as_str(),
   2896             )
   2897             .unwrap(),
   2898         );
   2899         // `null` `first`.
   2900         assert!(
   2901             serde_json::from_str::<RegistrationRelaxed>(
   2902                 serde_json::json!({
   2903                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2904                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2905                     "response": {
   2906                         "clientDataJSON": b64_cdata_json,
   2907                         "authenticatorData": b64_adata,
   2908                         "transports": [],
   2909                         "publicKey": b64_key,
   2910                         "publicKeyAlgorithm": -8i8,
   2911                         "attestationObject": b64_aobj,
   2912                     },
   2913                     "clientExtensionResults": {
   2914                         "prf": {
   2915                             "enabled": true,
   2916                             "results": {
   2917                                 "first": null
   2918                             },
   2919                         }
   2920                     },
   2921                     "type": "public-key"
   2922                 })
   2923                 .to_string()
   2924                 .as_str()
   2925             )
   2926             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2927                 && reg
   2928                     .0
   2929                     .client_extension_results
   2930                     .prf
   2931                     .is_some_and(|prf| prf.enabled))
   2932         );
   2933         // `null` `second`.
   2934         assert!(
   2935             serde_json::from_str::<RegistrationRelaxed>(
   2936                 serde_json::json!({
   2937                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2938                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2939                     "response": {
   2940                         "clientDataJSON": b64_cdata_json,
   2941                         "authenticatorData": b64_adata,
   2942                         "transports": [],
   2943                         "publicKey": b64_key,
   2944                         "publicKeyAlgorithm": -8i8,
   2945                         "attestationObject": b64_aobj,
   2946                     },
   2947                     "clientExtensionResults": {
   2948                         "prf": {
   2949                             "enabled": true,
   2950                             "results": {
   2951                                 "first": null,
   2952                                 "second": null
   2953                             },
   2954                         }
   2955                     },
   2956                     "type": "public-key"
   2957                 })
   2958                 .to_string()
   2959                 .as_str()
   2960             )
   2961             .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none()
   2962                 && reg
   2963                     .0
   2964                     .client_extension_results
   2965                     .prf
   2966                     .is_some_and(|prf| prf.enabled))
   2967         );
   2968         // Non-`null` `first`.
   2969         err = Error::invalid_type(Unexpected::Option, &"null")
   2970             .to_string()
   2971             .into_bytes();
   2972         assert_eq!(
   2973             serde_json::from_str::<RegistrationRelaxed>(
   2974                 serde_json::json!({
   2975                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2976                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2977                     "response": {
   2978                         "clientDataJSON": b64_cdata_json,
   2979                         "authenticatorData": b64_adata,
   2980                         "transports": [],
   2981                         "publicKey": b64_key,
   2982                         "publicKeyAlgorithm": -8i8,
   2983                         "attestationObject": b64_aobj,
   2984                     },
   2985                     "clientExtensionResults": {
   2986                         "prf": {
   2987                             "enabled": true,
   2988                             "results": {
   2989                                 "first": ""
   2990                             },
   2991                         }
   2992                     },
   2993                     "type": "public-key"
   2994                 })
   2995                 .to_string()
   2996                 .as_str()
   2997             )
   2998             .unwrap_err()
   2999             .to_string()
   3000             .into_bytes()
   3001             .get(..err.len()),
   3002             Some(err.as_slice())
   3003         );
   3004         // Non-`null` `second`.
   3005         err = Error::invalid_type(Unexpected::Option, &"null")
   3006             .to_string()
   3007             .into_bytes();
   3008         assert_eq!(
   3009             serde_json::from_str::<RegistrationRelaxed>(
   3010                 serde_json::json!({
   3011                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3012                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3013                     "response": {
   3014                         "clientDataJSON": b64_cdata_json,
   3015                         "authenticatorData": b64_adata,
   3016                         "transports": [],
   3017                         "publicKey": b64_key,
   3018                         "publicKeyAlgorithm": -8i8,
   3019                         "attestationObject": b64_aobj,
   3020                     },
   3021                     "clientExtensionResults": {
   3022                         "prf": {
   3023                             "enabled": true,
   3024                             "results": {
   3025                                 "first": null,
   3026                                 "second": ""
   3027                             },
   3028                         }
   3029                     },
   3030                     "type": "public-key"
   3031                 })
   3032                 .to_string()
   3033                 .as_str()
   3034             )
   3035             .unwrap_err()
   3036             .to_string()
   3037             .into_bytes()
   3038             .get(..err.len()),
   3039             Some(err.as_slice())
   3040         );
   3041         // Unknown `prf` field.
   3042         drop(
   3043             serde_json::from_str::<RegistrationRelaxed>(
   3044                 serde_json::json!({
   3045                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3046                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3047                     "response": {
   3048                         "clientDataJSON": b64_cdata_json,
   3049                         "authenticatorData": b64_adata,
   3050                         "transports": [],
   3051                         "publicKey": b64_key,
   3052                         "publicKeyAlgorithm": -8i8,
   3053                         "attestationObject": b64_aobj,
   3054                     },
   3055                     "clientExtensionResults": {
   3056                         "prf": {
   3057                             "enabled": true,
   3058                             "Results": null
   3059                         }
   3060                     },
   3061                     "type": "public-key"
   3062                 })
   3063                 .to_string()
   3064                 .as_str(),
   3065             )
   3066             .unwrap(),
   3067         );
   3068         // Unknown `results` field.
   3069         drop(
   3070             serde_json::from_str::<RegistrationRelaxed>(
   3071                 serde_json::json!({
   3072                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3073                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3074                     "response": {
   3075                         "clientDataJSON": b64_cdata_json,
   3076                         "authenticatorData": b64_adata,
   3077                         "transports": [],
   3078                         "publicKey": b64_key,
   3079                         "publicKeyAlgorithm": -8i8,
   3080                         "attestationObject": b64_aobj,
   3081                     },
   3082                     "clientExtensionResults": {
   3083                         "prf": {
   3084                             "enabled": true,
   3085                             "results": {
   3086                                 "first": null,
   3087                                 "Second": null
   3088                             }
   3089                         }
   3090                     },
   3091                     "type": "public-key"
   3092                 })
   3093                 .to_string()
   3094                 .as_str(),
   3095             )
   3096             .unwrap(),
   3097         );
   3098         // Duplicate field in `results`.
   3099         err = Error::duplicate_field("first").to_string().into_bytes();
   3100         assert_eq!(
   3101             serde_json::from_str::<RegistrationRelaxed>(
   3102                 format!(
   3103                     "{{
   3104                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   3105                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   3106                        \"response\": {{
   3107                            \"clientDataJSON\": \"{b64_cdata_json}\",
   3108                            \"authenticatorData\": \"{b64_adata}\",
   3109                            \"transports\": [],
   3110                            \"publicKey\": \"{b64_key}\",
   3111                            \"publicKeyAlgorithm\": -8,
   3112                            \"attestationObject\": \"{b64_aobj}\"
   3113                        }},
   3114                        \"clientExtensionResults\": {{
   3115                            \"prf\": {{
   3116                                \"enabled\": true,
   3117                                \"results\": {{
   3118                                    \"first\": null,
   3119                                    \"first\": null
   3120                                }}
   3121                            }}
   3122                        }},
   3123                        \"type\": \"public-key\"
   3124                      }}"
   3125                 )
   3126                 .as_str()
   3127             )
   3128             .unwrap_err()
   3129             .to_string()
   3130             .into_bytes()
   3131             .get(..err.len()),
   3132             Some(err.as_slice())
   3133         );
   3134     }
   3135     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   3136     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   3137     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   3138     #[test]
   3139     fn es256_registration_deserialize_data_mismatch() {
   3140         let c_data_json = serde_json::json!({}).to_string();
   3141         let mut att_obj: [u8; 178] = [
   3142             cbor::MAP_3,
   3143             cbor::TEXT_3,
   3144             b'f',
   3145             b'm',
   3146             b't',
   3147             cbor::TEXT_4,
   3148             b'n',
   3149             b'o',
   3150             b'n',
   3151             b'e',
   3152             cbor::TEXT_7,
   3153             b'a',
   3154             b't',
   3155             b't',
   3156             b'S',
   3157             b't',
   3158             b'm',
   3159             b't',
   3160             cbor::MAP_0,
   3161             cbor::TEXT_8,
   3162             b'a',
   3163             b'u',
   3164             b't',
   3165             b'h',
   3166             b'D',
   3167             b'a',
   3168             b't',
   3169             b'a',
   3170             cbor::BYTES_INFO_24,
   3171             148,
   3172             // `rpIdHash`.
   3173             0,
   3174             0,
   3175             0,
   3176             0,
   3177             0,
   3178             0,
   3179             0,
   3180             0,
   3181             0,
   3182             0,
   3183             0,
   3184             0,
   3185             0,
   3186             0,
   3187             0,
   3188             0,
   3189             0,
   3190             0,
   3191             0,
   3192             0,
   3193             0,
   3194             0,
   3195             0,
   3196             0,
   3197             0,
   3198             0,
   3199             0,
   3200             0,
   3201             0,
   3202             0,
   3203             0,
   3204             0,
   3205             // `flags`.
   3206             0b0100_0101,
   3207             // `signCount`.
   3208             0,
   3209             0,
   3210             0,
   3211             0,
   3212             // `aaguid`.
   3213             0,
   3214             0,
   3215             0,
   3216             0,
   3217             0,
   3218             0,
   3219             0,
   3220             0,
   3221             0,
   3222             0,
   3223             0,
   3224             0,
   3225             0,
   3226             0,
   3227             0,
   3228             0,
   3229             // `credentialIdLength`.
   3230             0,
   3231             16,
   3232             // `credentialId`.
   3233             0,
   3234             0,
   3235             0,
   3236             0,
   3237             0,
   3238             0,
   3239             0,
   3240             0,
   3241             0,
   3242             0,
   3243             0,
   3244             0,
   3245             0,
   3246             0,
   3247             0,
   3248             0,
   3249             // P-256 COSE key.
   3250             cbor::MAP_5,
   3251             KTY,
   3252             EC2,
   3253             ALG,
   3254             ES256,
   3255             // `crv`.
   3256             cbor::NEG_ONE,
   3257             // `P-256`.
   3258             cbor::ONE,
   3259             // `x`.
   3260             cbor::NEG_TWO,
   3261             cbor::BYTES_INFO_24,
   3262             32,
   3263             // x-coordinate. This will be overwritten later.
   3264             0,
   3265             0,
   3266             0,
   3267             0,
   3268             0,
   3269             0,
   3270             0,
   3271             0,
   3272             0,
   3273             0,
   3274             0,
   3275             0,
   3276             0,
   3277             0,
   3278             0,
   3279             0,
   3280             0,
   3281             0,
   3282             0,
   3283             0,
   3284             0,
   3285             0,
   3286             0,
   3287             0,
   3288             0,
   3289             0,
   3290             0,
   3291             0,
   3292             0,
   3293             0,
   3294             0,
   3295             0,
   3296             // `y`.
   3297             cbor::NEG_THREE,
   3298             cbor::BYTES_INFO_24,
   3299             32,
   3300             // y-coordinate. This will be overwritten later.
   3301             0,
   3302             0,
   3303             0,
   3304             0,
   3305             0,
   3306             0,
   3307             0,
   3308             0,
   3309             0,
   3310             0,
   3311             0,
   3312             0,
   3313             0,
   3314             0,
   3315             0,
   3316             0,
   3317             0,
   3318             0,
   3319             0,
   3320             0,
   3321             0,
   3322             0,
   3323             0,
   3324             0,
   3325             0,
   3326             0,
   3327             0,
   3328             0,
   3329             0,
   3330             0,
   3331             0,
   3332             0,
   3333         ];
   3334         let key = P256Key::from_bytes(
   3335             &[
   3336                 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115,
   3337                 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95,
   3338             ]
   3339             .into(),
   3340         )
   3341         .unwrap()
   3342         .public_key();
   3343         let enc_key = key.to_encoded_point(false);
   3344         let pub_key = key.to_public_key_der().unwrap();
   3345         let att_obj_len = att_obj.len();
   3346         let x_start = att_obj_len - 67;
   3347         let y_meta_start = x_start + 32;
   3348         let y_start = y_meta_start + 3;
   3349         att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap());
   3350         att_obj[y_start..].copy_from_slice(enc_key.y().unwrap());
   3351         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   3352         let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 148..]);
   3353         let b64_key = base64url_nopad::encode(pub_key.as_bytes());
   3354         let b64_aobj = base64url_nopad::encode(att_obj.as_slice());
   3355         // Base case is valid.
   3356         assert!(
   3357             serde_json::from_str::<RegistrationRelaxed>(
   3358                 serde_json::json!({
   3359                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3360                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3361                     "response": {
   3362                         "clientDataJSON": b64_cdata_json,
   3363                         "authenticatorData": b64_adata,
   3364                         "transports": [],
   3365                         "publicKey": b64_key,
   3366                         "publicKeyAlgorithm": -7i8,
   3367                         "attestationObject": b64_aobj,
   3368                     },
   3369                     "clientExtensionResults": {},
   3370                     "type": "public-key"
   3371                 })
   3372                 .to_string()
   3373                 .as_str()
   3374             )
   3375             .is_ok_and(
   3376                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   3377                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   3378                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   3379                         == *Sha256::digest(c_data_json.as_bytes())
   3380                     && reg.0.response.transports.is_empty()
   3381                     && matches!(
   3382                         reg.0.authenticator_attachment,
   3383                         AuthenticatorAttachment::None
   3384                     )
   3385                     && reg.0.client_extension_results.cred_props.is_none()
   3386                     && reg.0.client_extension_results.prf.is_none()
   3387             )
   3388         );
   3389         // `publicKeyAlgorithm` mismatch.
   3390         let mut err = Error::invalid_value(
   3391             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()),
   3392             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es256).as_str()
   3393         )
   3394         .to_string().into_bytes();
   3395         assert_eq!(
   3396             serde_json::from_str::<RegistrationRelaxed>(
   3397                 serde_json::json!({
   3398                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3399                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3400                     "response": {
   3401                         "clientDataJSON": b64_cdata_json,
   3402                         "authenticatorData": b64_adata,
   3403                         "transports": [],
   3404                         "publicKey": b64_key,
   3405                         "publicKeyAlgorithm": -8i8,
   3406                         "attestationObject": b64_aobj,
   3407                     },
   3408                     "clientExtensionResults": {},
   3409                     "type": "public-key"
   3410                 })
   3411                 .to_string()
   3412                 .as_str()
   3413             )
   3414             .unwrap_err()
   3415             .to_string()
   3416             .into_bytes()
   3417             .get(..err.len()),
   3418             Some(err.as_slice())
   3419         );
   3420         // Missing `publicKeyAlgorithm`.
   3421         drop(
   3422             serde_json::from_str::<RegistrationRelaxed>(
   3423                 serde_json::json!({
   3424                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3425                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3426                     "response": {
   3427                         "clientDataJSON": b64_cdata_json,
   3428                         "authenticatorData": b64_adata,
   3429                         "transports": [],
   3430                         "publicKey": b64_key,
   3431                         "attestationObject": b64_aobj,
   3432                     },
   3433                     "clientExtensionResults": {},
   3434                     "type": "public-key"
   3435                 })
   3436                 .to_string()
   3437                 .as_str(),
   3438             )
   3439             .unwrap(),
   3440         );
   3441         // `null` `publicKeyAlgorithm`.
   3442         drop(
   3443             serde_json::from_str::<RegistrationRelaxed>(
   3444                 serde_json::json!({
   3445                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3446                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3447                     "response": {
   3448                         "clientDataJSON": b64_cdata_json,
   3449                         "authenticatorData": b64_adata,
   3450                         "transports": [],
   3451                         "publicKey": b64_key,
   3452                         "publicKeyAlgorithm": null,
   3453                         "attestationObject": b64_aobj,
   3454                     },
   3455                     "clientExtensionResults": {},
   3456                     "type": "public-key"
   3457                 })
   3458                 .to_string()
   3459                 .as_str(),
   3460             )
   3461             .unwrap(),
   3462         );
   3463         // `publicKey` mismatch.
   3464         let bad_pub_key = P256PubKey::from_encoded_point(&P256Pt::from_affine_coordinates(
   3465             &[
   3466                 66, 71, 188, 41, 125, 2, 226, 44, 148, 62, 63, 190, 172, 64, 33, 214, 6, 37, 148,
   3467                 23, 240, 235, 203, 84, 112, 219, 232, 197, 54, 182, 17, 235,
   3468             ]
   3469             .into(),
   3470             &[
   3471                 22, 172, 123, 13, 170, 242, 217, 248, 193, 209, 206, 163, 92, 4, 162, 168, 113, 63,
   3472                 2, 117, 16, 223, 239, 196, 109, 179, 10, 130, 43, 213, 205, 92,
   3473             ]
   3474             .into(),
   3475             false,
   3476         ))
   3477         .unwrap();
   3478         err = Error::invalid_value(
   3479             Unexpected::Bytes([0; 32].as_slice()),
   3480             &format!(
   3481                 "DER-encoded public key to match the public key within the attestation object: P256(UncompressedP256PubKey({:?}, {:?}))",
   3482                 &att_obj[x_start..y_meta_start],
   3483                 &att_obj[y_start..],
   3484             )
   3485             .as_str(),
   3486         )
   3487         .to_string().into_bytes();
   3488         assert_eq!(serde_json::from_str::<RegistrationRelaxed>(
   3489             serde_json::json!({
   3490                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3491                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3492                 "response": {
   3493                     "clientDataJSON": b64_cdata_json,
   3494                     "authenticatorData": b64_adata,
   3495                     "transports": [],
   3496                     "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
   3497                     "publicKeyAlgorithm": -7i8,
   3498                     "attestationObject": b64_aobj,
   3499                 },
   3500                 "clientExtensionResults": {},
   3501                 "type": "public-key"
   3502             })
   3503             .to_string()
   3504             .as_str()
   3505             )
   3506             .unwrap_err().to_string().into_bytes().get(..err.len()),
   3507             Some(err.as_slice())
   3508         );
   3509         // Missing `publicKey`.
   3510         drop(
   3511             serde_json::from_str::<RegistrationRelaxed>(
   3512                 serde_json::json!({
   3513                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3514                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3515                     "response": {
   3516                         "clientDataJSON": b64_cdata_json,
   3517                         "authenticatorData": b64_adata,
   3518                         "transports": [],
   3519                         "publicKeyAlgorithm": -7i8,
   3520                         "attestationObject": b64_aobj,
   3521                     },
   3522                     "clientExtensionResults": {},
   3523                     "type": "public-key"
   3524                 })
   3525                 .to_string()
   3526                 .as_str(),
   3527             )
   3528             .unwrap(),
   3529         );
   3530         // `null` `publicKey`.
   3531         drop(
   3532             serde_json::from_str::<RegistrationRelaxed>(
   3533                 serde_json::json!({
   3534                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3535                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3536                     "response": {
   3537                         "clientDataJSON": b64_cdata_json,
   3538                         "authenticatorData": b64_adata,
   3539                         "transports": [],
   3540                         "publicKey": null,
   3541                         "publicKeyAlgorithm": -7i8,
   3542                         "attestationObject": b64_aobj,
   3543                     },
   3544                     "clientExtensionResults": {},
   3545                     "type": "public-key"
   3546                 })
   3547                 .to_string()
   3548                 .as_str(),
   3549             )
   3550             .unwrap(),
   3551         );
   3552         // Base case is valid.
   3553         assert!(
   3554             serde_json::from_str::<CustomRegistration>(
   3555                 serde_json::json!({
   3556                     "clientDataJSON": b64_cdata_json,
   3557                     "transports": [],
   3558                     "attestationObject": b64_aobj,
   3559                     "clientExtensionResults": {},
   3560                     "type": "public-key"
   3561                 })
   3562                 .to_string()
   3563                 .as_str()
   3564             )
   3565             .is_ok_and(
   3566                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   3567                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   3568                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   3569                         == *Sha256::digest(c_data_json.as_bytes())
   3570                     && reg.0.response.transports.is_empty()
   3571                     && matches!(
   3572                         reg.0.authenticator_attachment,
   3573                         AuthenticatorAttachment::None
   3574                     )
   3575                     && reg.0.client_extension_results.cred_props.is_none()
   3576                     && reg.0.client_extension_results.prf.is_none()
   3577             )
   3578         );
   3579     }
   3580     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   3581     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   3582     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   3583     #[test]
   3584     fn es384_registration_deserialize_data_mismatch() {
   3585         let c_data_json = serde_json::json!({}).to_string();
   3586         let mut att_obj: [u8; 211] = [
   3587             cbor::MAP_3,
   3588             cbor::TEXT_3,
   3589             b'f',
   3590             b'm',
   3591             b't',
   3592             cbor::TEXT_4,
   3593             b'n',
   3594             b'o',
   3595             b'n',
   3596             b'e',
   3597             cbor::TEXT_7,
   3598             b'a',
   3599             b't',
   3600             b't',
   3601             b'S',
   3602             b't',
   3603             b'm',
   3604             b't',
   3605             cbor::MAP_0,
   3606             cbor::TEXT_8,
   3607             b'a',
   3608             b'u',
   3609             b't',
   3610             b'h',
   3611             b'D',
   3612             b'a',
   3613             b't',
   3614             b'a',
   3615             cbor::BYTES_INFO_24,
   3616             181,
   3617             // `rpIdHash`.
   3618             0,
   3619             0,
   3620             0,
   3621             0,
   3622             0,
   3623             0,
   3624             0,
   3625             0,
   3626             0,
   3627             0,
   3628             0,
   3629             0,
   3630             0,
   3631             0,
   3632             0,
   3633             0,
   3634             0,
   3635             0,
   3636             0,
   3637             0,
   3638             0,
   3639             0,
   3640             0,
   3641             0,
   3642             0,
   3643             0,
   3644             0,
   3645             0,
   3646             0,
   3647             0,
   3648             0,
   3649             0,
   3650             // `flags`.
   3651             0b0100_0101,
   3652             // `signCount`.
   3653             0,
   3654             0,
   3655             0,
   3656             0,
   3657             // `aaguid`.
   3658             0,
   3659             0,
   3660             0,
   3661             0,
   3662             0,
   3663             0,
   3664             0,
   3665             0,
   3666             0,
   3667             0,
   3668             0,
   3669             0,
   3670             0,
   3671             0,
   3672             0,
   3673             0,
   3674             // `credentialIdLength`.
   3675             0,
   3676             16,
   3677             // `credentialId`.
   3678             0,
   3679             0,
   3680             0,
   3681             0,
   3682             0,
   3683             0,
   3684             0,
   3685             0,
   3686             0,
   3687             0,
   3688             0,
   3689             0,
   3690             0,
   3691             0,
   3692             0,
   3693             0,
   3694             // P-384 COSE key.
   3695             cbor::MAP_5,
   3696             KTY,
   3697             EC2,
   3698             ALG,
   3699             cbor::NEG_INFO_24,
   3700             ES384,
   3701             // `crv`.
   3702             cbor::NEG_ONE,
   3703             // `P-384`.
   3704             cbor::TWO,
   3705             // `x`.
   3706             cbor::NEG_TWO,
   3707             cbor::BYTES_INFO_24,
   3708             48,
   3709             // x-coordinate. This will be overwritten later.
   3710             0,
   3711             0,
   3712             0,
   3713             0,
   3714             0,
   3715             0,
   3716             0,
   3717             0,
   3718             0,
   3719             0,
   3720             0,
   3721             0,
   3722             0,
   3723             0,
   3724             0,
   3725             0,
   3726             0,
   3727             0,
   3728             0,
   3729             0,
   3730             0,
   3731             0,
   3732             0,
   3733             0,
   3734             0,
   3735             0,
   3736             0,
   3737             0,
   3738             0,
   3739             0,
   3740             0,
   3741             0,
   3742             0,
   3743             0,
   3744             0,
   3745             0,
   3746             0,
   3747             0,
   3748             0,
   3749             0,
   3750             0,
   3751             0,
   3752             0,
   3753             0,
   3754             0,
   3755             0,
   3756             0,
   3757             0,
   3758             // `y`.
   3759             cbor::NEG_THREE,
   3760             cbor::BYTES_INFO_24,
   3761             48,
   3762             // y-coordinate. This will be overwritten later.
   3763             0,
   3764             0,
   3765             0,
   3766             0,
   3767             0,
   3768             0,
   3769             0,
   3770             0,
   3771             0,
   3772             0,
   3773             0,
   3774             0,
   3775             0,
   3776             0,
   3777             0,
   3778             0,
   3779             0,
   3780             0,
   3781             0,
   3782             0,
   3783             0,
   3784             0,
   3785             0,
   3786             0,
   3787             0,
   3788             0,
   3789             0,
   3790             0,
   3791             0,
   3792             0,
   3793             0,
   3794             0,
   3795             0,
   3796             0,
   3797             0,
   3798             0,
   3799             0,
   3800             0,
   3801             0,
   3802             0,
   3803             0,
   3804             0,
   3805             0,
   3806             0,
   3807             0,
   3808             0,
   3809             0,
   3810             0,
   3811         ];
   3812         let key = P384Key::from_bytes(
   3813             &[
   3814                 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232,
   3815                 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1,
   3816                 32, 86, 220, 68, 182, 11, 105, 223, 75, 70,
   3817             ]
   3818             .into(),
   3819         )
   3820         .unwrap()
   3821         .public_key();
   3822         let enc_key = key.to_encoded_point(false);
   3823         let pub_key = key.to_public_key_der().unwrap();
   3824         let att_obj_len = att_obj.len();
   3825         let x_start = att_obj_len - 99;
   3826         let y_meta_start = x_start + 48;
   3827         let y_start = y_meta_start + 3;
   3828         att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap());
   3829         att_obj[y_start..].copy_from_slice(enc_key.y().unwrap());
   3830         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   3831         let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 181..]);
   3832         let b64_key = base64url_nopad::encode(pub_key.as_bytes());
   3833         let b64_aobj = base64url_nopad::encode(att_obj.as_slice());
   3834         // Base case is valid.
   3835         assert!(
   3836             serde_json::from_str::<RegistrationRelaxed>(
   3837                 serde_json::json!({
   3838                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3839                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3840                     "response": {
   3841                         "clientDataJSON": b64_cdata_json,
   3842                         "authenticatorData": b64_adata,
   3843                         "transports": [],
   3844                         "publicKey": b64_key,
   3845                         "publicKeyAlgorithm": -35i8,
   3846                         "attestationObject": b64_aobj,
   3847                     },
   3848                     "clientExtensionResults": {},
   3849                     "type": "public-key"
   3850                 })
   3851                 .to_string()
   3852                 .as_str()
   3853             )
   3854             .is_ok_and(
   3855                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   3856                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   3857                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   3858                         == *Sha256::digest(c_data_json.as_bytes())
   3859                     && reg.0.response.transports.is_empty()
   3860                     && matches!(
   3861                         reg.0.authenticator_attachment,
   3862                         AuthenticatorAttachment::None
   3863                     )
   3864                     && reg.0.client_extension_results.cred_props.is_none()
   3865                     && reg.0.client_extension_results.prf.is_none()
   3866             )
   3867         );
   3868         // `publicKeyAlgorithm` mismatch.
   3869         let mut err = Error::invalid_value(
   3870             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()),
   3871             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str()
   3872         )
   3873         .to_string().into_bytes();
   3874         assert_eq!(
   3875             serde_json::from_str::<RegistrationRelaxed>(
   3876                 serde_json::json!({
   3877                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3878                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3879                     "response": {
   3880                         "clientDataJSON": b64_cdata_json,
   3881                         "authenticatorData": b64_adata,
   3882                         "transports": [],
   3883                         "publicKey": b64_key,
   3884                         "publicKeyAlgorithm": -7i8,
   3885                         "attestationObject": b64_aobj,
   3886                     },
   3887                     "clientExtensionResults": {},
   3888                     "type": "public-key"
   3889                 })
   3890                 .to_string()
   3891                 .as_str()
   3892             )
   3893             .unwrap_err()
   3894             .to_string()
   3895             .into_bytes()
   3896             .get(..err.len()),
   3897             Some(err.as_slice())
   3898         );
   3899         // Missing `publicKeyAlgorithm`.
   3900         drop(
   3901             serde_json::from_str::<RegistrationRelaxed>(
   3902                 serde_json::json!({
   3903                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3904                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3905                     "response": {
   3906                         "clientDataJSON": b64_cdata_json,
   3907                         "authenticatorData": b64_adata,
   3908                         "transports": [],
   3909                         "publicKey": b64_key,
   3910                         "attestationObject": b64_aobj,
   3911                     },
   3912                     "clientExtensionResults": {},
   3913                     "type": "public-key"
   3914                 })
   3915                 .to_string()
   3916                 .as_str(),
   3917             )
   3918             .unwrap(),
   3919         );
   3920         // `null` `publicKeyAlgorithm`.
   3921         drop(
   3922             serde_json::from_str::<RegistrationRelaxed>(
   3923                 serde_json::json!({
   3924                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3925                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3926                     "response": {
   3927                         "clientDataJSON": b64_cdata_json,
   3928                         "authenticatorData": b64_adata,
   3929                         "transports": [],
   3930                         "publicKey": b64_key,
   3931                         "publicKeyAlgorithm": null,
   3932                         "attestationObject": b64_aobj,
   3933                     },
   3934                     "clientExtensionResults": {},
   3935                     "type": "public-key"
   3936                 })
   3937                 .to_string()
   3938                 .as_str(),
   3939             )
   3940             .unwrap(),
   3941         );
   3942         // `publicKey` mismatch.
   3943         let bad_pub_key = P384PubKey::from_encoded_point(&P384Pt::from_affine_coordinates(
   3944             &[
   3945                 192, 10, 27, 46, 66, 67, 80, 98, 33, 230, 156, 95, 1, 135, 150, 110, 64, 243, 22,
   3946                 118, 5, 255, 107, 44, 234, 111, 217, 105, 125, 114, 39, 7, 126, 2, 191, 111, 48,
   3947                 93, 234, 175, 18, 172, 59, 28, 97, 106, 178, 152,
   3948             ]
   3949             .into(),
   3950             &[
   3951                 57, 36, 196, 12, 109, 129, 253, 115, 88, 154, 6, 43, 195, 85, 169, 5, 230, 51, 28,
   3952                 205, 142, 28, 150, 35, 24, 222, 170, 253, 14, 248, 84, 151, 109, 191, 152, 111,
   3953                 222, 70, 134, 247, 109, 171, 211, 33, 214, 217, 200, 111,
   3954             ]
   3955             .into(),
   3956             false,
   3957         ))
   3958         .unwrap();
   3959         err = Error::invalid_value(
   3960             Unexpected::Bytes([0; 32].as_slice()),
   3961             &format!(
   3962                 "DER-encoded public key to match the public key within the attestation object: P384(UncompressedP384PubKey({:?}, {:?}))",
   3963                 &att_obj[x_start..y_meta_start],
   3964                 &att_obj[y_start..],
   3965             )
   3966             .as_str(),
   3967         )
   3968         .to_string().into_bytes();
   3969         assert_eq!(serde_json::from_str::<RegistrationRelaxed>(
   3970             serde_json::json!({
   3971                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3972                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3973                 "response": {
   3974                     "clientDataJSON": b64_cdata_json,
   3975                     "authenticatorData": b64_adata,
   3976                     "transports": [],
   3977                     "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
   3978                     "publicKeyAlgorithm": -35i8,
   3979                     "attestationObject": b64_aobj,
   3980                 },
   3981                 "clientExtensionResults": {},
   3982                 "type": "public-key"
   3983             }).to_string().as_str()
   3984         ).unwrap_err().to_string().into_bytes().get(..err.len()), Some(err.as_slice()));
   3985         // Missing `publicKey`.
   3986         drop(
   3987             serde_json::from_str::<RegistrationRelaxed>(
   3988                 serde_json::json!({
   3989                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   3990                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   3991                     "response": {
   3992                         "clientDataJSON": b64_cdata_json,
   3993                         "authenticatorData": b64_adata,
   3994                         "transports": [],
   3995                         "publicKeyAlgorithm": -35i8,
   3996                         "attestationObject": b64_aobj,
   3997                     },
   3998                     "clientExtensionResults": {},
   3999                     "type": "public-key"
   4000                 })
   4001                 .to_string()
   4002                 .as_str(),
   4003             )
   4004             .unwrap(),
   4005         );
   4006         // `publicKeyAlgorithm` mismatch when `publicKey` does not exist.
   4007         err = Error::invalid_value(
   4008             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()),
   4009             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str()
   4010         )
   4011         .to_string().into_bytes();
   4012         assert_eq!(
   4013             serde_json::from_str::<RegistrationRelaxed>(
   4014                 serde_json::json!({
   4015                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4016                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4017                     "response": {
   4018                         "clientDataJSON": b64_cdata_json,
   4019                         "authenticatorData": b64_adata,
   4020                         "transports": [],
   4021                         "publicKeyAlgorithm": -8i8,
   4022                         "attestationObject": b64_aobj,
   4023                     },
   4024                     "clientExtensionResults": {},
   4025                     "type": "public-key"
   4026                 })
   4027                 .to_string()
   4028                 .as_str()
   4029             )
   4030             .unwrap_err()
   4031             .to_string()
   4032             .into_bytes()
   4033             .get(..err.len()),
   4034             Some(err.as_slice())
   4035         );
   4036         // `null` `publicKey`.
   4037         drop(
   4038             serde_json::from_str::<RegistrationRelaxed>(
   4039                 serde_json::json!({
   4040                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4041                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4042                     "response": {
   4043                         "clientDataJSON": b64_cdata_json,
   4044                         "authenticatorData": b64_adata,
   4045                         "transports": [],
   4046                         "publicKey": null,
   4047                         "publicKeyAlgorithm": -35i8,
   4048                         "attestationObject": b64_aobj,
   4049                     },
   4050                     "clientExtensionResults": {},
   4051                     "type": "public-key"
   4052                 })
   4053                 .to_string()
   4054                 .as_str(),
   4055             )
   4056             .unwrap(),
   4057         );
   4058         // `publicKeyAlgorithm` mismatch when `publicKey` is null.
   4059         err = Error::invalid_value(
   4060             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()),
   4061             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str()
   4062         )
   4063         .to_string().into_bytes();
   4064         assert_eq!(
   4065             serde_json::from_str::<RegistrationRelaxed>(
   4066                 serde_json::json!({
   4067                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4068                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4069                     "response": {
   4070                         "clientDataJSON": b64_cdata_json,
   4071                         "authenticatorData": b64_adata,
   4072                         "transports": [],
   4073                         "publicKey": null,
   4074                         "publicKeyAlgorithm": -8i8,
   4075                         "attestationObject": b64_aobj,
   4076                     },
   4077                     "clientExtensionResults": {},
   4078                     "type": "public-key"
   4079                 })
   4080                 .to_string()
   4081                 .as_str()
   4082             )
   4083             .unwrap_err()
   4084             .to_string()
   4085             .into_bytes()
   4086             .get(..err.len()),
   4087             Some(err.as_slice())
   4088         );
   4089         // Base case is valid.
   4090         assert!(
   4091             serde_json::from_str::<CustomRegistration>(
   4092                 serde_json::json!({
   4093                     "clientDataJSON": b64_cdata_json,
   4094                     "transports": [],
   4095                     "attestationObject": b64_aobj,
   4096                     "clientExtensionResults": {},
   4097                     "type": "public-key"
   4098                 })
   4099                 .to_string()
   4100                 .as_str()
   4101             )
   4102             .is_ok_and(
   4103                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   4104                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   4105                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   4106                         == *Sha256::digest(c_data_json.as_bytes())
   4107                     && reg.0.response.transports.is_empty()
   4108                     && matches!(
   4109                         reg.0.authenticator_attachment,
   4110                         AuthenticatorAttachment::None
   4111                     )
   4112                     && reg.0.client_extension_results.cred_props.is_none()
   4113                     && reg.0.client_extension_results.prf.is_none()
   4114             )
   4115         );
   4116     }
   4117     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   4118     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   4119     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   4120     #[test]
   4121     fn rs256_registration_deserialize_data_mismatch() {
   4122         let c_data_json = serde_json::json!({}).to_string();
   4123         let mut att_obj: [u8; 374] = [
   4124             cbor::MAP_3,
   4125             cbor::TEXT_3,
   4126             b'f',
   4127             b'm',
   4128             b't',
   4129             cbor::TEXT_4,
   4130             b'n',
   4131             b'o',
   4132             b'n',
   4133             b'e',
   4134             cbor::TEXT_7,
   4135             b'a',
   4136             b't',
   4137             b't',
   4138             b'S',
   4139             b't',
   4140             b'm',
   4141             b't',
   4142             cbor::MAP_0,
   4143             cbor::TEXT_8,
   4144             b'a',
   4145             b'u',
   4146             b't',
   4147             b'h',
   4148             b'D',
   4149             b'a',
   4150             b't',
   4151             b'a',
   4152             cbor::BYTES_INFO_25,
   4153             1,
   4154             87,
   4155             // `rpIdHash`.
   4156             0,
   4157             0,
   4158             0,
   4159             0,
   4160             0,
   4161             0,
   4162             0,
   4163             0,
   4164             0,
   4165             0,
   4166             0,
   4167             0,
   4168             0,
   4169             0,
   4170             0,
   4171             0,
   4172             0,
   4173             0,
   4174             0,
   4175             0,
   4176             0,
   4177             0,
   4178             0,
   4179             0,
   4180             0,
   4181             0,
   4182             0,
   4183             0,
   4184             0,
   4185             0,
   4186             0,
   4187             0,
   4188             // `flags`.
   4189             0b0100_0101,
   4190             // `signCount`.
   4191             0,
   4192             0,
   4193             0,
   4194             0,
   4195             // `aaguid`.
   4196             0,
   4197             0,
   4198             0,
   4199             0,
   4200             0,
   4201             0,
   4202             0,
   4203             0,
   4204             0,
   4205             0,
   4206             0,
   4207             0,
   4208             0,
   4209             0,
   4210             0,
   4211             0,
   4212             // `credentialIdLength`.
   4213             0,
   4214             16,
   4215             // `credentialId`.
   4216             0,
   4217             0,
   4218             0,
   4219             0,
   4220             0,
   4221             0,
   4222             0,
   4223             0,
   4224             0,
   4225             0,
   4226             0,
   4227             0,
   4228             0,
   4229             0,
   4230             0,
   4231             0,
   4232             // RSA COSE key.
   4233             cbor::MAP_4,
   4234             KTY,
   4235             RSA,
   4236             ALG,
   4237             cbor::NEG_INFO_25,
   4238             // RS256.
   4239             1,
   4240             0,
   4241             // `n`.
   4242             cbor::NEG_ONE,
   4243             cbor::BYTES_INFO_25,
   4244             1,
   4245             0,
   4246             // n. This will be overwritten later.
   4247             0,
   4248             0,
   4249             0,
   4250             0,
   4251             0,
   4252             0,
   4253             0,
   4254             0,
   4255             0,
   4256             0,
   4257             0,
   4258             0,
   4259             0,
   4260             0,
   4261             0,
   4262             0,
   4263             0,
   4264             0,
   4265             0,
   4266             0,
   4267             0,
   4268             0,
   4269             0,
   4270             0,
   4271             0,
   4272             0,
   4273             0,
   4274             0,
   4275             0,
   4276             0,
   4277             0,
   4278             0,
   4279             0,
   4280             0,
   4281             0,
   4282             0,
   4283             0,
   4284             0,
   4285             0,
   4286             0,
   4287             0,
   4288             0,
   4289             0,
   4290             0,
   4291             0,
   4292             0,
   4293             0,
   4294             0,
   4295             0,
   4296             0,
   4297             0,
   4298             0,
   4299             0,
   4300             0,
   4301             0,
   4302             0,
   4303             0,
   4304             0,
   4305             0,
   4306             0,
   4307             0,
   4308             0,
   4309             0,
   4310             0,
   4311             0,
   4312             0,
   4313             0,
   4314             0,
   4315             0,
   4316             0,
   4317             0,
   4318             0,
   4319             0,
   4320             0,
   4321             0,
   4322             0,
   4323             0,
   4324             0,
   4325             0,
   4326             0,
   4327             0,
   4328             0,
   4329             0,
   4330             0,
   4331             0,
   4332             0,
   4333             0,
   4334             0,
   4335             0,
   4336             0,
   4337             0,
   4338             0,
   4339             0,
   4340             0,
   4341             0,
   4342             0,
   4343             0,
   4344             0,
   4345             0,
   4346             0,
   4347             0,
   4348             0,
   4349             0,
   4350             0,
   4351             0,
   4352             0,
   4353             0,
   4354             0,
   4355             0,
   4356             0,
   4357             0,
   4358             0,
   4359             0,
   4360             0,
   4361             0,
   4362             0,
   4363             0,
   4364             0,
   4365             0,
   4366             0,
   4367             0,
   4368             0,
   4369             0,
   4370             0,
   4371             0,
   4372             0,
   4373             0,
   4374             0,
   4375             0,
   4376             0,
   4377             0,
   4378             0,
   4379             0,
   4380             0,
   4381             0,
   4382             0,
   4383             0,
   4384             0,
   4385             0,
   4386             0,
   4387             0,
   4388             0,
   4389             0,
   4390             0,
   4391             0,
   4392             0,
   4393             0,
   4394             0,
   4395             0,
   4396             0,
   4397             0,
   4398             0,
   4399             0,
   4400             0,
   4401             0,
   4402             0,
   4403             0,
   4404             0,
   4405             0,
   4406             0,
   4407             0,
   4408             0,
   4409             0,
   4410             0,
   4411             0,
   4412             0,
   4413             0,
   4414             0,
   4415             0,
   4416             0,
   4417             0,
   4418             0,
   4419             0,
   4420             0,
   4421             0,
   4422             0,
   4423             0,
   4424             0,
   4425             0,
   4426             0,
   4427             0,
   4428             0,
   4429             0,
   4430             0,
   4431             0,
   4432             0,
   4433             0,
   4434             0,
   4435             0,
   4436             0,
   4437             0,
   4438             0,
   4439             0,
   4440             0,
   4441             0,
   4442             0,
   4443             0,
   4444             0,
   4445             0,
   4446             0,
   4447             0,
   4448             0,
   4449             0,
   4450             0,
   4451             0,
   4452             0,
   4453             0,
   4454             0,
   4455             0,
   4456             0,
   4457             0,
   4458             0,
   4459             0,
   4460             0,
   4461             0,
   4462             0,
   4463             0,
   4464             0,
   4465             0,
   4466             0,
   4467             0,
   4468             0,
   4469             0,
   4470             0,
   4471             0,
   4472             0,
   4473             0,
   4474             0,
   4475             0,
   4476             0,
   4477             0,
   4478             0,
   4479             0,
   4480             0,
   4481             0,
   4482             0,
   4483             0,
   4484             0,
   4485             0,
   4486             0,
   4487             0,
   4488             0,
   4489             0,
   4490             0,
   4491             0,
   4492             0,
   4493             0,
   4494             0,
   4495             0,
   4496             0,
   4497             0,
   4498             0,
   4499             0,
   4500             0,
   4501             0,
   4502             0,
   4503             // `e`.
   4504             cbor::NEG_TWO,
   4505             cbor::BYTES | 3,
   4506             // e.
   4507             1,
   4508             0,
   4509             1,
   4510         ];
   4511         let n = [
   4512             111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107,
   4513             195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206,
   4514             185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165,
   4515             100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204,
   4516             145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64,
   4517             87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105,
   4518             81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55,
   4519             117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21,
   4520             254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157,
   4521             108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188,
   4522             42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56,
   4523             104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13,
   4524             72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132,
   4525             153, 79, 0, 133, 78, 7, 218, 165, 241,
   4526         ];
   4527         let e = 0x0001_0001u32;
   4528         let d = [
   4529             145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0,
   4530             132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168,
   4531             35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108,
   4532             154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102,
   4533             6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247,
   4534             82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166,
   4535             216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111,
   4536             215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242,
   4537             140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212,
   4538             249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83,
   4539             31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153,
   4540             162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142,
   4541             24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45,
   4542             50, 23, 3, 25, 253, 87, 227, 79, 119, 161,
   4543         ];
   4544         let p = BigUint::from_bytes_le(
   4545             [
   4546                 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60,
   4547                 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229,
   4548                 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161,
   4549                 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16,
   4550                 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79,
   4551                 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191,
   4552                 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188,
   4553                 24, 247,
   4554             ]
   4555             .as_slice(),
   4556         );
   4557         let p_2 = BigUint::from_bytes_le(
   4558             [
   4559                 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78,
   4560                 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177,
   4561                 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29,
   4562                 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11,
   4563                 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252,
   4564                 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214,
   4565                 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90,
   4566                 250,
   4567             ]
   4568             .as_slice(),
   4569         );
   4570         let key = RsaPrivateKey::from_components(
   4571             BigUint::from_bytes_le(n.as_slice()),
   4572             e.into(),
   4573             BigUint::from_bytes_le(d.as_slice()),
   4574             vec![p, p_2],
   4575         )
   4576         .unwrap()
   4577         .to_public_key();
   4578         let pub_key = key.to_public_key_der().unwrap();
   4579         let att_obj_len = att_obj.len();
   4580         let n_start = att_obj_len - 261;
   4581         let e_start = n_start + 256;
   4582         att_obj[n_start..e_start].copy_from_slice(key.n().to_bytes_be().as_slice());
   4583         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   4584         let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 343..]);
   4585         let b64_key = base64url_nopad::encode(pub_key.as_bytes());
   4586         let b64_aobj = base64url_nopad::encode(att_obj.as_slice());
   4587         // Base case is valid.
   4588         assert!(
   4589             serde_json::from_str::<RegistrationRelaxed>(
   4590                 serde_json::json!({
   4591                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4592                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4593                     "response": {
   4594                         "clientDataJSON": b64_cdata_json,
   4595                         "authenticatorData": b64_adata,
   4596                         "transports": [],
   4597                         "publicKey": b64_key,
   4598                         "publicKeyAlgorithm": -257i16,
   4599                         "attestationObject": b64_aobj,
   4600                     },
   4601                     "clientExtensionResults": {},
   4602                     "type": "public-key"
   4603                 })
   4604                 .to_string()
   4605                 .as_str()
   4606             )
   4607             .is_ok_and(
   4608                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   4609                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   4610                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   4611                         == *Sha256::digest(c_data_json.as_bytes())
   4612                     && reg.0.response.transports.is_empty()
   4613                     && matches!(
   4614                         reg.0.authenticator_attachment,
   4615                         AuthenticatorAttachment::None
   4616                     )
   4617                     && reg.0.client_extension_results.cred_props.is_none()
   4618                     && reg.0.client_extension_results.prf.is_none()
   4619             )
   4620         );
   4621         // `publicKeyAlgorithm` mismatch.
   4622         let mut err = Error::invalid_value(
   4623             Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()),
   4624             &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Rs256).as_str()
   4625         )
   4626         .to_string().into_bytes();
   4627         assert_eq!(
   4628             serde_json::from_str::<RegistrationRelaxed>(
   4629                 serde_json::json!({
   4630                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4631                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4632                     "response": {
   4633                         "clientDataJSON": b64_cdata_json,
   4634                         "authenticatorData": b64_adata,
   4635                         "transports": [],
   4636                         "publicKey": b64_key,
   4637                         "publicKeyAlgorithm": -8i8,
   4638                         "attestationObject": b64_aobj,
   4639                     },
   4640                     "clientExtensionResults": {},
   4641                     "type": "public-key"
   4642                 })
   4643                 .to_string()
   4644                 .as_str()
   4645             )
   4646             .unwrap_err()
   4647             .to_string()
   4648             .into_bytes()
   4649             .get(..err.len()),
   4650             Some(err.as_slice())
   4651         );
   4652         // Missing `publicKeyAlgorithm`.
   4653         drop(
   4654             serde_json::from_str::<RegistrationRelaxed>(
   4655                 serde_json::json!({
   4656                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4657                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4658                     "response": {
   4659                         "clientDataJSON": b64_cdata_json,
   4660                         "authenticatorData": b64_adata,
   4661                         "transports": [],
   4662                         "publicKey": b64_key,
   4663                         "attestationObject": b64_aobj,
   4664                     },
   4665                     "clientExtensionResults": {},
   4666                     "type": "public-key"
   4667                 })
   4668                 .to_string()
   4669                 .as_str(),
   4670             )
   4671             .unwrap(),
   4672         );
   4673         // `null` `publicKeyAlgorithm`.
   4674         drop(
   4675             serde_json::from_str::<RegistrationRelaxed>(
   4676                 serde_json::json!({
   4677                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4678                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4679                     "response": {
   4680                         "clientDataJSON": b64_cdata_json,
   4681                         "authenticatorData": b64_adata,
   4682                         "transports": [],
   4683                         "publicKey": b64_key,
   4684                         "publicKeyAlgorithm": null,
   4685                         "attestationObject": b64_aobj,
   4686                     },
   4687                     "clientExtensionResults": {},
   4688                     "type": "public-key"
   4689                 })
   4690                 .to_string()
   4691                 .as_str(),
   4692             )
   4693             .unwrap(),
   4694         );
   4695         // `publicKey` mismatch.
   4696         let bad_pub_key = RsaPrivateKey::from_components(
   4697             BigUint::from_bytes_le(
   4698                 [
   4699                     175, 161, 161, 75, 52, 244, 72, 168, 29, 119, 33, 120, 3, 222, 231, 152, 222,
   4700                     119, 112, 83, 221, 237, 74, 174, 79, 216, 147, 251, 245, 94, 234, 114, 254, 21,
   4701                     17, 254, 8, 115, 75, 127, 150, 87, 59, 109, 230, 116, 85, 90, 11, 160, 63, 217,
   4702                     9, 38, 187, 250, 226, 183, 38, 164, 182, 218, 22, 19, 58, 189, 83, 219, 11,
   4703                     144, 15, 99, 151, 166, 46, 57, 17, 111, 189, 131, 142, 113, 85, 122, 188, 238,
   4704                     52, 21, 116, 125, 102, 195, 182, 165, 29, 156, 213, 182, 125, 156, 88, 56, 221,
   4705                     2, 98, 43, 210, 115, 32, 4, 105, 88, 181, 158, 207, 236, 162, 250, 253, 240,
   4706                     72, 8, 253, 50, 220, 247, 76, 170, 143, 68, 225, 231, 113, 64, 244, 17, 138,
   4707                     162, 233, 33, 2, 67, 11, 223, 188, 232, 152, 193, 20, 32, 243, 52, 64, 43, 2,
   4708                     243, 8, 77, 150, 232, 109, 148, 95, 127, 55, 71, 162, 34, 54, 83, 135, 52, 172,
   4709                     191, 32, 42, 106, 43, 211, 206, 100, 104, 110, 232, 5, 43, 120, 180, 166, 40,
   4710                     144, 233, 239, 103, 134, 103, 255, 224, 138, 184, 208, 137, 127, 36, 189, 143,
   4711                     248, 201, 2, 218, 51, 232, 96, 30, 83, 124, 109, 241, 23, 179, 247, 151, 238,
   4712                     212, 204, 44, 43, 223, 148, 241, 172, 10, 235, 155, 94, 68, 116, 24, 116, 191,
   4713                     86, 53, 127, 35, 133, 198, 204, 59, 76, 110, 16, 1, 15, 148, 135, 157,
   4714                 ]
   4715                 .as_slice(),
   4716             ),
   4717             0x0001_0001u32.into(),
   4718             BigUint::from_bytes_le(
   4719                 [
   4720                     129, 93, 123, 251, 104, 29, 84, 203, 116, 100, 75, 237, 111, 160, 12, 100, 172,
   4721                     76, 57, 178, 144, 235, 81, 61, 115, 243, 28, 40, 183, 22, 56, 150, 68, 38, 220,
   4722                     62, 233, 110, 48, 174, 35, 197, 244, 109, 148, 109, 36, 69, 69, 82, 225, 113,
   4723                     175, 6, 239, 27, 193, 101, 50, 239, 122, 102, 7, 46, 98, 79, 195, 116, 155,
   4724                     158, 138, 147, 51, 93, 24, 237, 246, 82, 14, 109, 144, 250, 239, 93, 63, 214,
   4725                     96, 130, 226, 134, 198, 145, 161, 11, 231, 97, 214, 180, 255, 95, 158, 88, 108,
   4726                     254, 243, 177, 133, 184, 92, 95, 148, 88, 55, 124, 245, 244, 84, 86, 4, 121,
   4727                     44, 231, 97, 176, 190, 29, 155, 40, 57, 69, 165, 80, 168, 9, 56, 43, 233, 6,
   4728                     14, 157, 112, 223, 64, 88, 141, 7, 65, 23, 64, 208, 6, 83, 61, 8, 182, 248,
   4729                     126, 84, 179, 163, 80, 238, 90, 133, 4, 14, 71, 177, 175, 27, 29, 151, 211,
   4730                     108, 162, 195, 7, 157, 167, 86, 169, 3, 87, 235, 89, 158, 237, 216, 31, 243,
   4731                     197, 62, 5, 84, 131, 230, 186, 248, 49, 12, 93, 244, 61, 135, 180, 17, 162,
   4732                     241, 13, 115, 241, 138, 219, 98, 155, 166, 191, 63, 12, 37, 1, 165, 178, 84,
   4733                     200, 72, 80, 41, 77, 136, 217, 141, 246, 209, 31, 243, 159, 71, 43, 246, 159,
   4734                     182, 171, 116, 12, 3, 142, 235, 218, 164, 70, 90, 147, 238, 42, 75,
   4735                 ]
   4736                 .as_slice(),
   4737             ),
   4738             vec![
   4739                 BigUint::from_bytes_le(
   4740                     [
   4741                         215, 199, 110, 28, 64, 16, 16, 109, 106, 152, 150, 124, 52, 166, 121, 92,
   4742                         242, 13, 0, 69, 7, 152, 72, 172, 118, 63, 156, 180, 140, 39, 53, 29, 197,
   4743                         224, 177, 48, 41, 221, 102, 65, 17, 185, 55, 62, 219, 152, 227, 7, 78, 219,
   4744                         14, 139, 71, 204, 144, 152, 14, 39, 247, 244, 165, 224, 234, 60, 213, 74,
   4745                         237, 30, 102, 177, 242, 138, 168, 31, 122, 47, 206, 155, 225, 113, 103,
   4746                         175, 152, 244, 27, 233, 112, 223, 248, 38, 215, 178, 20, 244, 8, 121, 26,
   4747                         11, 70, 122, 16, 85, 167, 87, 64, 216, 228, 227, 173, 57, 250, 8, 221, 38,
   4748                         12, 203, 212, 1, 112, 43, 72, 91, 225, 97, 228, 57, 154, 193,
   4749                     ]
   4750                     .as_slice(),
   4751                 ),
   4752                 BigUint::from_bytes_le(
   4753                     [
   4754                         233, 89, 204, 152, 31, 242, 8, 110, 38, 190, 111, 159, 105, 105, 45, 85,
   4755                         15, 244, 30, 250, 174, 226, 219, 111, 107, 191, 196, 135, 17, 123, 186,
   4756                         167, 85, 13, 120, 197, 159, 129, 78, 237, 152, 31, 230, 26, 229, 253, 197,
   4757                         211, 105, 204, 126, 142, 250, 55, 26, 172, 65, 160, 45, 6, 99, 86, 66, 238,
   4758                         107, 6, 98, 171, 93, 224, 201, 160, 31, 204, 82, 120, 228, 158, 238, 6,
   4759                         190, 12, 150, 153, 239, 95, 57, 71, 100, 239, 235, 155, 73, 200, 5, 225,
   4760                         127, 185, 46, 48, 243, 84, 33, 142, 17, 19, 20, 23, 215, 16, 114, 58, 211,
   4761                         14, 73, 148, 168, 252, 159, 252, 125, 57, 101, 211, 188, 12, 77, 208,
   4762                     ]
   4763                     .as_slice(),
   4764                 ),
   4765             ],
   4766         )
   4767         .unwrap()
   4768         .to_public_key();
   4769         err = Error::invalid_value(
   4770             Unexpected::Bytes([0; 32].as_slice()),
   4771             &format!(
   4772                 "DER-encoded public key to match the public key within the attestation object: Rsa(RsaPubKey({:?}, 65537))",
   4773                 &att_obj[n_start..e_start],
   4774             )
   4775             .as_str(),
   4776         )
   4777         .to_string().into_bytes();
   4778         assert_eq!(serde_json::from_str::<RegistrationRelaxed>(
   4779             serde_json::json!({
   4780                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4781                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4782                 "response": {
   4783                     "clientDataJSON": b64_cdata_json,
   4784                     "authenticatorData": b64_adata,
   4785                     "transports": [],
   4786                     "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
   4787                     "publicKeyAlgorithm": -257i16,
   4788                     "attestationObject": b64_aobj,
   4789                 },
   4790                 "clientExtensionResults": {},
   4791                 "type": "public-key"
   4792             })
   4793             .to_string()
   4794             .as_str()
   4795             )
   4796            .unwrap_err().to_string().into_bytes().get(..err.len()),
   4797             Some(err.as_slice())
   4798         );
   4799         // Missing `publicKey`.
   4800         drop(
   4801             serde_json::from_str::<RegistrationRelaxed>(
   4802                 serde_json::json!({
   4803                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4804                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4805                     "response": {
   4806                         "clientDataJSON": b64_cdata_json,
   4807                         "authenticatorData": b64_adata,
   4808                         "transports": [],
   4809                         "publicKeyAlgorithm": -257i16,
   4810                         "attestationObject": b64_aobj,
   4811                     },
   4812                     "clientExtensionResults": {},
   4813                     "type": "public-key"
   4814                 })
   4815                 .to_string()
   4816                 .as_str(),
   4817             )
   4818             .unwrap(),
   4819         );
   4820         // `null` `publicKey`.
   4821         drop(
   4822             serde_json::from_str::<RegistrationRelaxed>(
   4823                 serde_json::json!({
   4824                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   4825                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   4826                     "response": {
   4827                         "clientDataJSON": b64_cdata_json,
   4828                         "authenticatorData": b64_adata,
   4829                         "transports": [],
   4830                         "publicKey": null,
   4831                         "publicKeyAlgorithm": -257i16,
   4832                         "attestationObject": b64_aobj,
   4833                     },
   4834                     "clientExtensionResults": {},
   4835                     "type": "public-key"
   4836                 })
   4837                 .to_string()
   4838                 .as_str(),
   4839             )
   4840             .unwrap(),
   4841         );
   4842         // Base case is valid.
   4843         assert!(
   4844             serde_json::from_str::<CustomRegistration>(
   4845                 serde_json::json!({
   4846                     "clientDataJSON": b64_cdata_json,
   4847                     "transports": [],
   4848                     "attestationObject": b64_aobj,
   4849                     "clientExtensionResults": {},
   4850                     "type": "public-key"
   4851                 })
   4852                 .to_string()
   4853                 .as_str()
   4854             )
   4855             .is_ok_and(
   4856                 |reg| reg.0.response.client_data_json == c_data_json.as_bytes()
   4857                     && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj
   4858                     && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..]
   4859                         == *Sha256::digest(c_data_json.as_bytes())
   4860                     && reg.0.response.transports.is_empty()
   4861                     && matches!(
   4862                         reg.0.authenticator_attachment,
   4863                         AuthenticatorAttachment::None
   4864                     )
   4865                     && reg.0.client_extension_results.cred_props.is_none()
   4866                     && reg.0.client_extension_results.prf.is_none()
   4867             )
   4868         );
   4869     }
   4870 }