webauthn_rp

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

ser_relaxed.rs (86848B)


      1 #[cfg(doc)]
      2 use super::super::{Challenge, CredentialId};
      3 use super::{
      4     super::{
      5         super::request::register::{USER_HANDLE_MAX_LEN, UserHandle},
      6         auth::ser::{
      7             AUTH_ASSERT_FIELDS, AuthData, AuthenticatorAssertionVisitor, ClientExtensionsOutputs,
      8             ClientExtensionsOutputsVisitor, EXT_FIELDS,
      9         },
     10         ser::{
     11             AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions,
     12             PublicKeyCredential, Type,
     13         },
     14         ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed,
     15     },
     16     Authentication, AuthenticatorAssertion, AuthenticatorAttachment,
     17 };
     18 use core::{
     19     fmt::{self, Formatter},
     20     marker::PhantomData,
     21 };
     22 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor};
     23 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation.
     24 struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs);
     25 impl ClientExtensions for ClientExtensionsOutputsRelaxed {
     26     fn empty() -> Self {
     27         Self(ClientExtensionsOutputs::empty())
     28     }
     29 }
     30 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed {
     31     /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored.
     32     ///
     33     /// Note that duplicate keys are still forbidden.
     34     #[inline]
     35     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     36     where
     37         D: Deserializer<'de>,
     38     {
     39         deserializer
     40             .deserialize_struct(
     41                 "ClientExtensionsOutputsRelaxed",
     42                 EXT_FIELDS,
     43                 ClientExtensionsOutputsVisitor::<
     44                     true,
     45                     AuthenticationExtensionsPrfOutputsHelper<
     46                         true,
     47                         false,
     48                         AuthenticationExtensionsPrfValuesRelaxed,
     49                     >,
     50                 >(PhantomData),
     51             )
     52             .map(Self)
     53     }
     54 }
     55 /// `newtype` around `AuthenticatorAssertion` with a "relaxed" [`Self::deserialize`] implementation.
     56 #[derive(Debug)]
     57 pub struct AuthenticatorAssertionRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>(
     58     pub AuthenticatorAssertion<USER_LEN, DISCOVERABLE>,
     59 );
     60 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
     61     for AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE>
     62 where
     63     UserHandle<USER_LEN>: Deserialize<'de>,
     64 {
     65     /// Same as [`AuthenticatorAssertion::deserialize`] except unknown keys are ignored.
     66     ///
     67     /// Note that duplicate keys are still forbidden.
     68     #[inline]
     69     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     70     where
     71         D: Deserializer<'de>,
     72     {
     73         deserializer
     74             .deserialize_struct(
     75                 "AuthenticatorAssertionRelaxed",
     76                 AUTH_ASSERT_FIELDS,
     77                 AuthenticatorAssertionVisitor::<true, USER_LEN, DISCOVERABLE>,
     78             )
     79             .map(Self)
     80     }
     81 }
     82 /// `newtype` around `Authentication` with a "relaxed" [`Self::deserialize`] implementation.
     83 #[derive(Debug)]
     84 pub struct AuthenticationRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>(
     85     pub Authentication<USER_LEN, DISCOVERABLE>,
     86 );
     87 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
     88     for AuthenticationRelaxed<USER_LEN, DISCOVERABLE>
     89 where
     90     UserHandle<USER_LEN>: Deserialize<'de>,
     91 {
     92     /// Same as [`Authentication::deserialize`] except unknown keys are ignored;
     93     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized
     94     /// via [`AuthenticatorAssertionRelaxed::deserialize`];
     95     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults)
     96     /// is deserialized such unknown keys are ignored but duplicate keys are forbidden,
     97     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` or an
     98     /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs)
     99     /// such that unknown keys are allowed but duplicate keys are forbidden,
    100     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled)
    101     /// is forbidden (including being assigned `null`),
    102     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
    103     /// be `null`, or be an
    104     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
    105     /// where unknown keys are ignored, duplicate keys are forbidden,
    106     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but
    107     /// if it exists it must be `null`, and
    108     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
    109     /// must be `null` if so; and only
    110     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and `response` are required.
    111     /// `rawId` and `type` and allowed to not exist. For the other fields, they are allowed to not exist or be `null`.
    112     ///
    113     /// Note that duplicate keys are still forbidden, and data matching still applies when applicable.
    114     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
    115     #[inline]
    116     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    117     where
    118         D: Deserializer<'de>,
    119     {
    120         PublicKeyCredential::<
    121             true,
    122             false,
    123             AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE>,
    124             ClientExtensionsOutputsRelaxed,
    125         >::deserialize(deserializer)
    126         .map(|cred| {
    127             Self(Authentication {
    128                 raw_id: cred.id.unwrap_or_else(|| {
    129                     unreachable!("there is a bug in PublicKeyCredential::deserialize")
    130                 }),
    131                 response: cred.response.0,
    132                 authenticator_attachment: cred.authenticator_attachment,
    133             })
    134         })
    135     }
    136 }
    137 /// `AuthenticationRelaxed` with a required `UserHandle`.
    138 pub type DiscoverableAuthenticationRelaxed<const USER_LEN: usize> =
    139     AuthenticationRelaxed<USER_LEN, true>;
    140 /// `AuthenticationRelaxed` with a required `UserHandle64`.
    141 pub type DiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, true>;
    142 /// `AuthenticationRelaxed` with a required `UserHandle16`.
    143 pub type DiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, true>;
    144 /// `AuthenticationRelaxed` with an optional `UserHandle`.
    145 pub type NonDiscoverableAuthenticationRelaxed<const USER_LEN: usize> =
    146     AuthenticationRelaxed<USER_LEN, false>;
    147 /// `AuthenticationRelaxed` with an optional `UserHandle64`.
    148 pub type NonDiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, false>;
    149 /// `AuthenticationRelaxed` with an optional `UserHandle16`.
    150 pub type NonDiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, false>;
    151 /// `newtype` around `Authentication` with a custom [`Self::deserialize`] implementation.
    152 #[derive(Debug)]
    153 pub struct CustomAuthentication<const USER_LEN: usize, const DISCOVERABLE: bool>(
    154     pub Authentication<USER_LEN, DISCOVERABLE>,
    155 );
    156 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    157     for CustomAuthentication<USER_LEN, DISCOVERABLE>
    158 where
    159     UserHandle<USER_LEN>: Deserialize<'de>,
    160 {
    161     /// Despite the spec having a
    162     /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson) that clients
    163     /// can follow, the downside is the superfluous data it contains.
    164     ///
    165     /// There simply is no reason to send the [`CredentialId`] twice. This redundant data puts RPs in
    166     /// a position where they either ignore the data or parse the data to ensure no contradictions exist
    167     /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)).
    168     ///
    169     /// While [`Authentication::deserialize`] _strictly_ adheres to the JSON definition, this implementation
    170     /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate
    171     /// and unknown keys are disallowed:
    172     ///
    173     /// ```json
    174     /// {
    175     ///   "authenticatorAttachment": null | "platform" | "cross-platform",
    176     ///   "authenticatorData": <base64url string>,
    177     ///   "clientDataJSON": <base64url string>,
    178     ///   "clientExtensionResults": {
    179     ///     "prf": null | PRFJSON
    180     ///   },
    181     ///   "id": <see CredentialId::deserialize>,
    182     ///   "signature": <base64url string>,
    183     ///   "type": "public-key",
    184     ///   "userHandle": null | <see UserHandle::deserialize>
    185     /// }
    186     /// // PRFJSON:
    187     /// {
    188     ///   "results": null | PRFOutputsJSON
    189     /// }
    190     /// // PRFOutputsJSON:
    191     /// {
    192     ///   "first": null,
    193     ///   "second": null
    194     /// }
    195     /// ```
    196     ///
    197     /// `"userHandle"` is required to exist and not be `null` iff `DISCOVERABLE`. When it does exist and
    198     /// is not `null`, then it is deserialized via [`UserHandle::deserialize`]. All of the remaining keys are
    199     /// required with the exceptions of `"authenticatorAttachment"` and `"type"`. `"prf"` is not required in the
    200     /// `clientExtensionResults` object, `"results"` is required in the `PRFJSON` object, and `"first"`
    201     /// (but not `"second"`) is required in `PRFOutputsJSON`.
    202     ///
    203     /// # Examples
    204     ///
    205     /// ```
    206     /// # use webauthn_rp::{request::register::{UserHandle, USER_HANDLE_MIN_LEN}, response::auth::ser_relaxed::CustomAuthentication};
    207     /// assert!(
    208     ///     // The below payload is technically valid, but `AuthenticationServerState::verify` will fail
    209     ///     // since the authenticatorData is not valid. This is true for `Authentication::deserialize`
    210     ///     // as well since authenticatorData parsing is always deferred.
    211     ///     serde_json::from_str::<CustomAuthentication<USER_HANDLE_MIN_LEN, true>>(
    212     ///         r#"{
    213     ///             "authenticatorData": "AA",
    214     ///             "authenticatorAttachment": "cross-platform",
    215     ///             "clientExtensionResults": {},
    216     ///             "clientDataJSON": "AA",
    217     ///             "id": "AAAAAAAAAAAAAAAAAAAAAA",
    218     ///             "signature": "AA",
    219     ///             "type": "public-key",
    220     ///             "userHandle": "AA"
    221     ///         }"#
    222     ///     ).is_ok());
    223     /// ```
    224     #[expect(
    225         clippy::too_many_lines,
    226         reason = "want to hide; thus don't put in outer scope"
    227     )]
    228     #[inline]
    229     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    230     where
    231         D: Deserializer<'de>,
    232     {
    233         /// `Visitor` for `CustomAuthentication`.
    234         struct CustomAuthenticationVisitor<const LEN: usize, const DISC: bool>;
    235         impl<'d, const LEN: usize, const DISC: bool> Visitor<'d> for CustomAuthenticationVisitor<LEN, DISC>
    236         where
    237             UserHandle<LEN>: Deserialize<'d>,
    238         {
    239             type Value = CustomAuthentication<LEN, DISC>;
    240             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    241                 formatter.write_str("CustomAuthentication")
    242             }
    243             #[expect(
    244                 clippy::too_many_lines,
    245                 reason = "want to hide; thus don't put in outer scope"
    246             )]
    247             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    248             where
    249                 A: MapAccess<'d>,
    250             {
    251                 /// Fields in the JSON.
    252                 enum Field {
    253                     /// `authenticatorAttachment` key.
    254                     AuthenticatorAttachment,
    255                     /// `authenticatorData` key.
    256                     AuthenticatorData,
    257                     /// `clientDataJSON` key.
    258                     ClientDataJson,
    259                     /// `clientExtensionResults` key.
    260                     ClientExtensionResults,
    261                     /// `id` key.
    262                     Id,
    263                     /// `signature` key.
    264                     Signature,
    265                     /// `type` key.
    266                     Type,
    267                     /// `userHandle` key.
    268                     UserHandle,
    269                 }
    270                 impl<'e> Deserialize<'e> for Field {
    271                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    272                     where
    273                         D: Deserializer<'e>,
    274                     {
    275                         /// `Visitor` for `Field`.
    276                         struct FieldVisitor;
    277                         impl Visitor<'_> for FieldVisitor {
    278                             type Value = Field;
    279                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    280                                 write!(
    281                                     formatter,
    282                                     "'{AUTHENTICATOR_ATTACHMENT}', '{AUTHENTICATOR_DATA}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{ID}', '{SIGNATURE}', '{TYPE}', or '{USER_HANDLE}'"
    283                                 )
    284                             }
    285                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    286                             where
    287                                 E: Error,
    288                             {
    289                                 match v {
    290                                     AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment),
    291                                     AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData),
    292                                     CLIENT_DATA_JSON => Ok(Field::ClientDataJson),
    293                                     CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults),
    294                                     ID => Ok(Field::Id),
    295                                     SIGNATURE => Ok(Field::Signature),
    296                                     TYPE => Ok(Field::Type),
    297                                     USER_HANDLE => Ok(Field::UserHandle),
    298                                     _ => Err(E::unknown_field(v, FIELDS)),
    299                                 }
    300                             }
    301                         }
    302                         deserializer.deserialize_identifier(FieldVisitor)
    303                     }
    304                 }
    305                 let mut authenticator_attachment = None;
    306                 let mut authenticator_data = None;
    307                 let mut client_data_json = None;
    308                 let mut ext = false;
    309                 let mut id = None;
    310                 let mut signature = None;
    311                 let mut typ = false;
    312                 let mut user_handle = None;
    313                 while let Some(key) = map.next_key()? {
    314                     match key {
    315                         Field::AuthenticatorAttachment => {
    316                             if authenticator_attachment.is_some() {
    317                                 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT));
    318                             }
    319                             authenticator_attachment = map.next_value::<Option<_>>().map(Some)?;
    320                         }
    321                         Field::AuthenticatorData => {
    322                             if authenticator_data.is_some() {
    323                                 return Err(Error::duplicate_field(AUTHENTICATOR_DATA));
    324                             }
    325                             authenticator_data =
    326                                 map.next_value::<AuthData>().map(|val| Some(val.0))?;
    327                         }
    328                         Field::ClientDataJson => {
    329                             if client_data_json.is_some() {
    330                                 return Err(Error::duplicate_field(CLIENT_DATA_JSON));
    331                             }
    332                             client_data_json = map
    333                                 .next_value::<Base64DecodedVal>()
    334                                 .map(|val| Some(val.0))?;
    335                         }
    336                         Field::ClientExtensionResults => {
    337                             if ext {
    338                                 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS));
    339                             }
    340                             ext = map.next_value::<ClientExtensionsOutputs>().map(|_| true)?;
    341                         }
    342                         Field::Id => {
    343                             if id.is_some() {
    344                                 return Err(Error::duplicate_field(ID));
    345                             }
    346                             id = map.next_value().map(Some)?;
    347                         }
    348                         Field::Signature => {
    349                             if signature.is_some() {
    350                                 return Err(Error::duplicate_field(SIGNATURE));
    351                             }
    352                             signature = map
    353                                 .next_value::<Base64DecodedVal>()
    354                                 .map(|val| Some(val.0))?;
    355                         }
    356                         Field::Type => {
    357                             if typ {
    358                                 return Err(Error::duplicate_field(TYPE));
    359                             }
    360                             typ = map.next_value::<Type>().map(|_| true)?;
    361                         }
    362                         Field::UserHandle => {
    363                             if user_handle.is_some() {
    364                                 return Err(Error::duplicate_field(USER_HANDLE));
    365                             }
    366                             user_handle = map.next_value().map(Some)?;
    367                         }
    368                     }
    369                 }
    370                 authenticator_data
    371                     .ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA))
    372                     .and_then(|auth_data| {
    373                         client_data_json
    374                             .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON))
    375                             .and_then(|c_data| {
    376                                 id.ok_or_else(|| Error::missing_field(ID))
    377                                     .and_then(|raw_id| {
    378                                         signature
    379                                             .ok_or_else(|| Error::missing_field(SIGNATURE))
    380                                             .and_then(|sig| {
    381                                                 if ext {
    382                                                     if DISC {
    383                                                         user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE))
    384                                                     } else {
    385                                                         user_handle.map_or_else(|| Ok(None), Ok)
    386                                                     }.map(|user| {
    387                                                         CustomAuthentication(Authentication {
    388                                                             response: AuthenticatorAssertion::new_inner(
    389                                                                 c_data,
    390                                                                 auth_data,
    391                                                                 sig,
    392                                                                 user,
    393                                                             ),
    394                                                             authenticator_attachment:
    395                                                                 authenticator_attachment.map_or(
    396                                                                     AuthenticatorAttachment::None,
    397                                                                     |auth_attach| {
    398                                                                         auth_attach.unwrap_or(
    399                                                                         AuthenticatorAttachment::None,
    400                                                                     )
    401                                                                     },
    402                                                                 ),
    403                                                             raw_id,
    404                                                         })
    405                                                     })
    406                                                 } else {
    407                                                     Err(Error::missing_field(
    408                                                         CLIENT_EXTENSION_RESULTS,
    409                                                     ))
    410                                                 }
    411                                             })
    412                                     })
    413                             })
    414                     })
    415             }
    416         }
    417         /// `authenticatorAttachment` key.
    418         const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment";
    419         /// `authenticatorData` key.
    420         const AUTHENTICATOR_DATA: &str = "authenticatorData";
    421         /// `clientDataJSON` key.
    422         const CLIENT_DATA_JSON: &str = "clientDataJSON";
    423         /// `clientExtensionResults` key.
    424         const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults";
    425         /// `id` key.
    426         const ID: &str = "id";
    427         /// `signature` key.
    428         const SIGNATURE: &str = "signature";
    429         /// `type` key.
    430         const TYPE: &str = "type";
    431         /// `userHandle` key.
    432         const USER_HANDLE: &str = "userHandle";
    433         /// Fields.
    434         const FIELDS: &[&str; 8] = &[
    435             AUTHENTICATOR_ATTACHMENT,
    436             AUTHENTICATOR_DATA,
    437             CLIENT_DATA_JSON,
    438             CLIENT_EXTENSION_RESULTS,
    439             ID,
    440             SIGNATURE,
    441             TYPE,
    442             USER_HANDLE,
    443         ];
    444         deserializer.deserialize_struct("CustomAuthentication", FIELDS, CustomAuthenticationVisitor)
    445     }
    446 }
    447 /// `CustomAuthentication` with a required `UserHandle`.
    448 pub type DiscoverableCustomAuthentication<const USER_LEN: usize> =
    449     CustomAuthentication<USER_LEN, true>;
    450 /// `CustomAuthentication` with a required `UserHandle64`.
    451 pub type DiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, true>;
    452 /// `CustomAuthentication` with a required `UserHandle16`.
    453 pub type DiscoverableCustomAuthentication16 = CustomAuthentication<16, true>;
    454 /// `CustomAuthentication` with an optional `UserHandle`.
    455 pub type NonDiscoverableCustomAuthentication<const USER_LEN: usize> =
    456     CustomAuthentication<USER_LEN, false>;
    457 /// `CustomAuthentication` with an optional `UserHandle64`.
    458 pub type NonDiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, false>;
    459 /// `CustomAuthentication` with an optional `UserHandle16`.
    460 pub type NonDiscoverableCustomAuthentication16 = CustomAuthentication<16, false>;
    461 #[cfg(test)]
    462 mod tests {
    463     use super::{
    464         super::{super::super::request::register::USER_HANDLE_MIN_LEN, AuthenticatorAttachment},
    465         DiscoverableAuthenticationRelaxed, DiscoverableCustomAuthentication,
    466         NonDiscoverableAuthenticationRelaxed, NonDiscoverableCustomAuthentication,
    467     };
    468     use rsa::sha2::{Digest as _, Sha256};
    469     use serde::de::{Error as _, Unexpected};
    470     use serde_json::Error;
    471     #[expect(clippy::unwrap_used, reason = "OK in tests")]
    472     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
    473     #[expect(
    474         clippy::cognitive_complexity,
    475         clippy::too_many_lines,
    476         reason = "a lot to test"
    477     )]
    478     #[test]
    479     fn eddsa_authentication_deserialize_data_mismatch() {
    480         let c_data_json = serde_json::json!({}).to_string();
    481         let auth_data: [u8; 37] = [
    482             // `rpIdHash`.
    483             0,
    484             0,
    485             0,
    486             0,
    487             0,
    488             0,
    489             0,
    490             0,
    491             0,
    492             0,
    493             0,
    494             0,
    495             0,
    496             0,
    497             0,
    498             0,
    499             0,
    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             // `flags`.
    516             0b0000_0101,
    517             // `signCount`.
    518             0,
    519             0,
    520             0,
    521             0,
    522         ];
    523         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
    524         let b64_adata = base64url_nopad::encode(auth_data.as_slice());
    525         let b64_sig = base64url_nopad::encode([].as_slice());
    526         let b64_user = base64url_nopad::encode(b"\x00".as_slice());
    527         // Base case is valid.
    528         assert!(
    529             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    530                 serde_json::json!({
    531                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    532                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    533                     "response": {
    534                         "clientDataJSON": b64_cdata_json,
    535                         "authenticatorData": b64_adata,
    536                         "signature": b64_sig,
    537                         "userHandle": b64_user,
    538                     },
    539                     "authenticatorAttachment": "cross-platform",
    540                     "clientExtensionResults": {},
    541                     "type": "public-key"
    542                 })
    543                 .to_string()
    544                 .as_str()
    545             )
    546             .is_ok_and(|auth| auth.0.response.client_data_json
    547                 == c_data_json.as_bytes()
    548                 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
    549                 && auth.0.response.authenticator_data_and_c_data_hash[37..]
    550                     == *Sha256::digest(c_data_json.as_bytes())
    551                 && matches!(
    552                     auth.0.authenticator_attachment,
    553                     AuthenticatorAttachment::CrossPlatform
    554                 ))
    555         );
    556         // `id` and `rawId` mismatch.
    557         let mut err = Error::invalid_value(
    558             Unexpected::Bytes(
    559                 base64url_nopad::decode(b"ABABABABABABABABABABAA")
    560                     .unwrap()
    561                     .as_slice(),
    562             ),
    563             &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(),
    564         )
    565         .to_string()
    566         .into_bytes();
    567         assert_eq!(
    568             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    569                 serde_json::json!({
    570                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    571                     "rawId": "ABABABABABABABABABABAA",
    572                     "response": {
    573                         "clientDataJSON": b64_cdata_json,
    574                         "authenticatorData": b64_adata,
    575                         "signature": b64_sig,
    576                         "userHandle": b64_user,
    577                     },
    578                     "authenticatorAttachment": "cross-platform",
    579                     "clientExtensionResults": {},
    580                     "type": "public-key"
    581                 })
    582                 .to_string()
    583                 .as_str()
    584             )
    585             .unwrap_err()
    586             .to_string()
    587             .into_bytes()
    588             .get(..err.len()),
    589             Some(err.as_slice())
    590         );
    591         // missing `id`.
    592         err = Error::missing_field("id").to_string().into_bytes();
    593         assert_eq!(
    594             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    595                 serde_json::json!({
    596                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    597                     "response": {
    598                         "clientDataJSON": b64_cdata_json,
    599                         "authenticatorData": b64_adata,
    600                         "signature": b64_sig,
    601                         "userHandle": b64_user,
    602                     },
    603                     "authenticatorAttachment": "cross-platform",
    604                     "clientExtensionResults": {},
    605                     "type": "public-key"
    606                 })
    607                 .to_string()
    608                 .as_str()
    609             )
    610             .unwrap_err()
    611             .to_string()
    612             .into_bytes()
    613             .get(..err.len()),
    614             Some(err.as_slice())
    615         );
    616         // `null` `id`.
    617         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    618             .to_string()
    619             .into_bytes();
    620         assert_eq!(
    621             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    622                 serde_json::json!({
    623                     "id": null,
    624                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    625                     "response": {
    626                         "clientDataJSON": b64_cdata_json,
    627                         "authenticatorData": b64_adata,
    628                         "signature": b64_sig,
    629                         "userHandle": b64_user,
    630                     },
    631                     "clientExtensionResults": {},
    632                     "type": "public-key"
    633                 })
    634                 .to_string()
    635                 .as_str()
    636             )
    637             .unwrap_err()
    638             .to_string()
    639             .into_bytes()
    640             .get(..err.len()),
    641             Some(err.as_slice())
    642         );
    643         // missing `rawId`.
    644         drop(
    645             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    646                 serde_json::json!({
    647                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    648                     "response": {
    649                         "clientDataJSON": b64_cdata_json,
    650                         "authenticatorData": b64_adata,
    651                         "signature": b64_sig,
    652                         "userHandle": b64_user,
    653                     },
    654                     "clientExtensionResults": {},
    655                     "type": "public-key"
    656                 })
    657                 .to_string()
    658                 .as_str(),
    659             )
    660             .unwrap(),
    661         );
    662         // `null` `rawId`.
    663         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    664             .to_string()
    665             .into_bytes();
    666         assert_eq!(
    667             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    668                 serde_json::json!({
    669                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    670                     "rawId": null,
    671                     "response": {
    672                         "clientDataJSON": b64_cdata_json,
    673                         "authenticatorData": b64_adata,
    674                         "signature": b64_sig,
    675                         "userHandle": b64_user,
    676                     },
    677                     "clientExtensionResults": {},
    678                     "type": "public-key"
    679                 })
    680                 .to_string()
    681                 .as_str()
    682             )
    683             .unwrap_err()
    684             .to_string()
    685             .into_bytes()
    686             .get(..err.len()),
    687             Some(err.as_slice())
    688         );
    689         // Missing `authenticatorData`.
    690         err = Error::missing_field("authenticatorData")
    691             .to_string()
    692             .into_bytes();
    693         assert_eq!(
    694             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    695                 serde_json::json!({
    696                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    697                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    698                     "response": {
    699                         "clientDataJSON": b64_cdata_json,
    700                         "signature": b64_sig,
    701                         "userHandle": b64_user,
    702                     },
    703                     "clientExtensionResults": {},
    704                     "type": "public-key"
    705                 })
    706                 .to_string()
    707                 .as_str()
    708             )
    709             .unwrap_err()
    710             .to_string()
    711             .into_bytes()
    712             .get(..err.len()),
    713             Some(err.as_slice())
    714         );
    715         // `null` `authenticatorData`.
    716         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData")
    717             .to_string()
    718             .into_bytes();
    719         assert_eq!(
    720             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    721                 serde_json::json!({
    722                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    723                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    724                     "response": {
    725                         "clientDataJSON": b64_cdata_json,
    726                         "authenticatorData": null,
    727                         "signature": b64_sig,
    728                         "userHandle": b64_user,
    729                     },
    730                     "clientExtensionResults": {},
    731                     "type": "public-key"
    732                 })
    733                 .to_string()
    734                 .as_str()
    735             )
    736             .unwrap_err()
    737             .to_string()
    738             .into_bytes()
    739             .get(..err.len()),
    740             Some(err.as_slice())
    741         );
    742         // Missing `signature`.
    743         err = Error::missing_field("signature").to_string().into_bytes();
    744         assert_eq!(
    745             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    746                 serde_json::json!({
    747                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    748                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    749                     "response": {
    750                         "clientDataJSON": b64_cdata_json,
    751                         "authenticatorData": b64_adata,
    752                         "userHandle": b64_user,
    753                     },
    754                     "clientExtensionResults": {},
    755                     "type": "public-key"
    756                 })
    757                 .to_string()
    758                 .as_str()
    759             )
    760             .unwrap_err()
    761             .to_string()
    762             .into_bytes()
    763             .get(..err.len()),
    764             Some(err.as_slice())
    765         );
    766         // `null` `signature`.
    767         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    768             .to_string()
    769             .into_bytes();
    770         assert_eq!(
    771             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    772                 serde_json::json!({
    773                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    774                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    775                     "response": {
    776                         "clientDataJSON": b64_cdata_json,
    777                         "authenticatorData": b64_adata,
    778                         "signature": null,
    779                         "userHandle": b64_user,
    780                     },
    781                     "clientExtensionResults": {},
    782                     "type": "public-key"
    783                 })
    784                 .to_string()
    785                 .as_str()
    786             )
    787             .unwrap_err()
    788             .to_string()
    789             .into_bytes()
    790             .get(..err.len()),
    791             Some(err.as_slice())
    792         );
    793         // Missing `userHandle`.
    794         drop(
    795             serde_json::from_str::<NonDiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    796                 serde_json::json!({
    797                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    798                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    799                     "response": {
    800                         "clientDataJSON": b64_cdata_json,
    801                         "authenticatorData": b64_adata,
    802                         "signature": b64_sig,
    803                     },
    804                     "clientExtensionResults": {},
    805                     "type": "public-key"
    806                 })
    807                 .to_string()
    808                 .as_str(),
    809             )
    810             .unwrap(),
    811         );
    812         // `null` `userHandle`.
    813         drop(
    814             serde_json::from_str::<NonDiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    815                 serde_json::json!({
    816                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    817                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    818                     "response": {
    819                         "clientDataJSON": b64_cdata_json,
    820                         "authenticatorData": b64_adata,
    821                         "signature": b64_sig,
    822                         "userHandle": null,
    823                     },
    824                     "clientExtensionResults": {},
    825                     "type": "public-key"
    826                 })
    827                 .to_string()
    828                 .as_str(),
    829             )
    830             .unwrap(),
    831         );
    832         // `null` `authenticatorAttachment`.
    833         assert!(
    834             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    835                 serde_json::json!({
    836                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    837                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    838                     "response": {
    839                         "clientDataJSON": b64_cdata_json,
    840                         "authenticatorData": b64_adata,
    841                         "signature": b64_sig,
    842                         "userHandle": b64_user,
    843                     },
    844                     "authenticatorAttachment": null,
    845                     "clientExtensionResults": {},
    846                     "type": "public-key"
    847                 })
    848                 .to_string()
    849                 .as_str()
    850             )
    851             .is_ok_and(|auth| matches!(
    852                 auth.0.authenticator_attachment,
    853                 AuthenticatorAttachment::None
    854             ))
    855         );
    856         // Unknown `authenticatorAttachment`.
    857         err = Error::invalid_value(
    858             Unexpected::Str("Platform"),
    859             &"'platform' or 'cross-platform'",
    860         )
    861         .to_string()
    862         .into_bytes();
    863         assert_eq!(
    864             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    865                 serde_json::json!({
    866                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    867                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    868                     "response": {
    869                         "clientDataJSON": b64_cdata_json,
    870                         "authenticatorData": b64_adata,
    871                         "signature": b64_sig,
    872                         "userHandle": b64_user,
    873                     },
    874                     "authenticatorAttachment": "Platform",
    875                     "clientExtensionResults": {},
    876                     "type": "public-key"
    877                 })
    878                 .to_string()
    879                 .as_str()
    880             )
    881             .unwrap_err()
    882             .to_string()
    883             .into_bytes()
    884             .get(..err.len()),
    885             Some(err.as_slice())
    886         );
    887         // Missing `clientDataJSON`.
    888         err = Error::missing_field("clientDataJSON")
    889             .to_string()
    890             .into_bytes();
    891         assert_eq!(
    892             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    893                 serde_json::json!({
    894                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    895                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    896                     "response": {
    897                         "authenticatorData": b64_adata,
    898                         "signature": b64_sig,
    899                         "userHandle": b64_user,
    900                     },
    901                     "clientExtensionResults": {},
    902                     "type": "public-key"
    903                 })
    904                 .to_string()
    905                 .as_str()
    906             )
    907             .unwrap_err()
    908             .to_string()
    909             .into_bytes()
    910             .get(..err.len()),
    911             Some(err.as_slice())
    912         );
    913         // `null` `clientDataJSON`.
    914         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    915             .to_string()
    916             .into_bytes();
    917         assert_eq!(
    918             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    919                 serde_json::json!({
    920                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    921                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    922                     "response": {
    923                         "clientDataJSON": null,
    924                         "authenticatorData": b64_adata,
    925                         "signature": b64_sig,
    926                         "userHandle": b64_user,
    927                     },
    928                     "clientExtensionResults": {},
    929                     "type": "public-key"
    930                 })
    931                 .to_string()
    932                 .as_str()
    933             )
    934             .unwrap_err()
    935             .to_string()
    936             .into_bytes()
    937             .get(..err.len()),
    938             Some(err.as_slice())
    939         );
    940         // Missing `response`.
    941         err = Error::missing_field("response").to_string().into_bytes();
    942         assert_eq!(
    943             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    944                 serde_json::json!({
    945                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    946                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    947                     "clientExtensionResults": {},
    948                     "type": "public-key"
    949                 })
    950                 .to_string()
    951                 .as_str()
    952             )
    953             .unwrap_err()
    954             .to_string()
    955             .into_bytes()
    956             .get(..err.len()),
    957             Some(err.as_slice())
    958         );
    959         // `null` `response`.
    960         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion")
    961             .to_string()
    962             .into_bytes();
    963         assert_eq!(
    964             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    965                 serde_json::json!({
    966                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    967                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    968                     "response": null,
    969                     "clientExtensionResults": {},
    970                     "type": "public-key"
    971                 })
    972                 .to_string()
    973                 .as_str()
    974             )
    975             .unwrap_err()
    976             .to_string()
    977             .into_bytes()
    978             .get(..err.len()),
    979             Some(err.as_slice())
    980         );
    981         // Empty `response`.
    982         err = Error::missing_field("clientDataJSON")
    983             .to_string()
    984             .into_bytes();
    985         assert_eq!(
    986             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
    987                 serde_json::json!({
    988                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    989                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    990                     "response": {},
    991                     "clientExtensionResults": {},
    992                     "type": "public-key"
    993                 })
    994                 .to_string()
    995                 .as_str()
    996             )
    997             .unwrap_err()
    998             .to_string()
    999             .into_bytes()
   1000             .get(..err.len()),
   1001             Some(err.as_slice())
   1002         );
   1003         // Missing `clientExtensionResults`.
   1004         drop(
   1005             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1006                 serde_json::json!({
   1007                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1008                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1009                     "response": {
   1010                         "clientDataJSON": b64_cdata_json,
   1011                         "authenticatorData": b64_adata,
   1012                         "signature": b64_sig,
   1013                         "userHandle": b64_user,
   1014                     },
   1015                     "type": "public-key"
   1016                 })
   1017                 .to_string()
   1018                 .as_str(),
   1019             )
   1020             .unwrap(),
   1021         );
   1022         // `null` `clientExtensionResults`.
   1023         drop(
   1024             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1025                 serde_json::json!({
   1026                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1027                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1028                     "response": {
   1029                         "clientDataJSON": b64_cdata_json,
   1030                         "authenticatorData": b64_adata,
   1031                         "signature": b64_sig,
   1032                         "userHandle": b64_user,
   1033                     },
   1034                     "clientExtensionResults": null,
   1035                     "type": "public-key"
   1036                 })
   1037                 .to_string()
   1038                 .as_str(),
   1039             )
   1040             .unwrap(),
   1041         );
   1042         // Missing `type`.
   1043         drop(
   1044             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1045                 serde_json::json!({
   1046                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1047                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1048                     "response": {
   1049                         "clientDataJSON": b64_cdata_json,
   1050                         "authenticatorData": b64_adata,
   1051                         "signature": b64_sig,
   1052                         "userHandle": b64_user,
   1053                     },
   1054                     "clientExtensionResults": {},
   1055                 })
   1056                 .to_string()
   1057                 .as_str(),
   1058             )
   1059             .unwrap(),
   1060         );
   1061         // `null` `type`.
   1062         err = Error::invalid_type(Unexpected::Other("null"), &"public-key")
   1063             .to_string()
   1064             .into_bytes();
   1065         assert_eq!(
   1066             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1067                 serde_json::json!({
   1068                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1069                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1070                     "response": {
   1071                         "clientDataJSON": b64_cdata_json,
   1072                         "authenticatorData": b64_adata,
   1073                         "signature": b64_sig,
   1074                         "userHandle": b64_user,
   1075                     },
   1076                     "clientExtensionResults": {},
   1077                     "type": null
   1078                 })
   1079                 .to_string()
   1080                 .as_str()
   1081             )
   1082             .unwrap_err()
   1083             .to_string()
   1084             .into_bytes()
   1085             .get(..err.len()),
   1086             Some(err.as_slice())
   1087         );
   1088         // Not exactly `public-type` `type`.
   1089         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
   1090             .to_string()
   1091             .into_bytes();
   1092         assert_eq!(
   1093             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1094                 serde_json::json!({
   1095                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1096                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1097                     "response": {
   1098                         "clientDataJSON": b64_cdata_json,
   1099                         "authenticatorData": b64_adata,
   1100                         "signature": b64_sig,
   1101                         "userHandle": b64_user,
   1102                     },
   1103                     "clientExtensionResults": {},
   1104                     "type": "Public-key"
   1105                 })
   1106                 .to_string()
   1107                 .as_str()
   1108             )
   1109             .unwrap_err()
   1110             .to_string()
   1111             .into_bytes()
   1112             .get(..err.len()),
   1113             Some(err.as_slice())
   1114         );
   1115         // `null`.
   1116         err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential")
   1117             .to_string()
   1118             .into_bytes();
   1119         assert_eq!(
   1120             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1121                 serde_json::json!(null).to_string().as_str()
   1122             )
   1123             .unwrap_err()
   1124             .to_string()
   1125             .into_bytes()
   1126             .get(..err.len()),
   1127             Some(err.as_slice())
   1128         );
   1129         // Empty.
   1130         err = Error::missing_field("response").to_string().into_bytes();
   1131         assert_eq!(
   1132             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1133                 serde_json::json!({}).to_string().as_str()
   1134             )
   1135             .unwrap_err()
   1136             .to_string()
   1137             .into_bytes()
   1138             .get(..err.len()),
   1139             Some(err.as_slice())
   1140         );
   1141         // Unknown field in `response`.
   1142         drop(
   1143             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1144                 serde_json::json!({
   1145                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1146                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1147                     "response": {
   1148                         "clientDataJSON": b64_cdata_json,
   1149                         "authenticatorData": b64_adata,
   1150                         "signature": b64_sig,
   1151                         "userHandle": b64_user,
   1152                         "foo": true,
   1153                     },
   1154                     "clientExtensionResults": {},
   1155                     "type": "public-key"
   1156                 })
   1157                 .to_string()
   1158                 .as_str(),
   1159             )
   1160             .unwrap(),
   1161         );
   1162         // Duplicate field in `response`.
   1163         err = Error::duplicate_field("userHandle")
   1164             .to_string()
   1165             .into_bytes();
   1166         assert_eq!(
   1167             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1168                 format!(
   1169                     "{{
   1170                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1171                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1172                        \"response\": {{
   1173                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1174                            \"authenticatorData\": \"{b64_adata}\",
   1175                            \"signature\": \"{b64_sig}\",
   1176                            \"userHandle\": \"{b64_user}\",
   1177                            \"userHandle\": \"{b64_user}\"
   1178                        }},
   1179                        \"clientExtensionResults\": {{}},
   1180                        \"type\": \"public-key\"
   1181 
   1182                      }}"
   1183                 )
   1184                 .as_str()
   1185             )
   1186             .unwrap_err()
   1187             .to_string()
   1188             .into_bytes()
   1189             .get(..err.len()),
   1190             Some(err.as_slice())
   1191         );
   1192         // Unknown field in `PublicKeyCredential`.
   1193         drop(
   1194             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1195                 serde_json::json!({
   1196                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1197                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1198                     "response": {
   1199                         "clientDataJSON": b64_cdata_json,
   1200                         "authenticatorData": b64_adata,
   1201                         "signature": b64_sig,
   1202                         "userHandle": b64_user,
   1203                     },
   1204                     "clientExtensionResults": {},
   1205                     "type": "public-key",
   1206                     "foo": true,
   1207                 })
   1208                 .to_string()
   1209                 .as_str(),
   1210             )
   1211             .unwrap(),
   1212         );
   1213         // Duplicate field in `PublicKeyCredential`.
   1214         err = Error::duplicate_field("id").to_string().into_bytes();
   1215         assert_eq!(
   1216             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1217                 format!(
   1218                     "{{
   1219                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1220                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1221                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1222                        \"response\": {{
   1223                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1224                            \"authenticatorData\": \"{b64_adata}\",
   1225                            \"signature\": \"{b64_sig}\",
   1226                            \"userHandle\": \"{b64_user}\"
   1227                        }},
   1228                        \"clientExtensionResults\": {{}},
   1229                        \"type\": \"public-key\"
   1230 
   1231                      }}"
   1232                 )
   1233                 .as_str()
   1234             )
   1235             .unwrap_err()
   1236             .to_string()
   1237             .into_bytes()
   1238             .get(..err.len()),
   1239             Some(err.as_slice())
   1240         );
   1241         // Base case is valid.
   1242         assert!(
   1243             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1244                 serde_json::json!({
   1245                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1246                     "clientDataJSON": b64_cdata_json,
   1247                     "authenticatorData": b64_adata,
   1248                     "signature": b64_sig,
   1249                     "userHandle": b64_user,
   1250                     "authenticatorAttachment": "cross-platform",
   1251                     "clientExtensionResults": {},
   1252                     "type": "public-key"
   1253                 })
   1254                 .to_string()
   1255                 .as_str()
   1256             )
   1257             .is_ok_and(|auth| auth.0.response.client_data_json
   1258                 == c_data_json.as_bytes()
   1259                 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
   1260                 && auth.0.response.authenticator_data_and_c_data_hash[37..]
   1261                     == *Sha256::digest(c_data_json.as_bytes())
   1262                 && matches!(
   1263                     auth.0.authenticator_attachment,
   1264                     AuthenticatorAttachment::CrossPlatform
   1265                 ))
   1266         );
   1267         // missing `id`.
   1268         err = Error::missing_field("id").to_string().into_bytes();
   1269         assert_eq!(
   1270             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1271                 serde_json::json!({
   1272                     "clientDataJSON": b64_cdata_json,
   1273                     "authenticatorData": b64_adata,
   1274                     "signature": b64_sig,
   1275                     "userHandle": b64_user,
   1276                     "authenticatorAttachment": "cross-platform",
   1277                     "clientExtensionResults": {},
   1278                     "type": "public-key"
   1279                 })
   1280                 .to_string()
   1281                 .as_str()
   1282             )
   1283             .unwrap_err()
   1284             .to_string()
   1285             .into_bytes()
   1286             .get(..err.len()),
   1287             Some(err.as_slice())
   1288         );
   1289         // `null` `id`.
   1290         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
   1291             .to_string()
   1292             .into_bytes();
   1293         assert_eq!(
   1294             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1295                 serde_json::json!({
   1296                     "id": null,
   1297                     "clientDataJSON": b64_cdata_json,
   1298                     "authenticatorData": b64_adata,
   1299                     "signature": b64_sig,
   1300                     "userHandle": b64_user,
   1301                     "clientExtensionResults": {},
   1302                     "type": "public-key"
   1303                 })
   1304                 .to_string()
   1305                 .as_str()
   1306             )
   1307             .unwrap_err()
   1308             .to_string()
   1309             .into_bytes()
   1310             .get(..err.len()),
   1311             Some(err.as_slice())
   1312         );
   1313         // Missing `authenticatorData`.
   1314         err = Error::missing_field("authenticatorData")
   1315             .to_string()
   1316             .into_bytes();
   1317         assert_eq!(
   1318             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1319                 serde_json::json!({
   1320                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1321                     "clientDataJSON": b64_cdata_json,
   1322                     "signature": b64_sig,
   1323                     "userHandle": b64_user,
   1324                     "clientExtensionResults": {},
   1325                     "type": "public-key"
   1326                 })
   1327                 .to_string()
   1328                 .as_str()
   1329             )
   1330             .unwrap_err()
   1331             .to_string()
   1332             .into_bytes()
   1333             .get(..err.len()),
   1334             Some(err.as_slice())
   1335         );
   1336         // `null` `authenticatorData`.
   1337         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData")
   1338             .to_string()
   1339             .into_bytes();
   1340         assert_eq!(
   1341             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1342                 serde_json::json!({
   1343                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1344                     "clientDataJSON": b64_cdata_json,
   1345                     "authenticatorData": null,
   1346                     "signature": b64_sig,
   1347                     "userHandle": b64_user,
   1348                     "clientExtensionResults": {},
   1349                     "type": "public-key"
   1350                 })
   1351                 .to_string()
   1352                 .as_str()
   1353             )
   1354             .unwrap_err()
   1355             .to_string()
   1356             .into_bytes()
   1357             .get(..err.len()),
   1358             Some(err.as_slice())
   1359         );
   1360         // Missing `signature`.
   1361         err = Error::missing_field("signature").to_string().into_bytes();
   1362         assert_eq!(
   1363             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1364                 serde_json::json!({
   1365                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1366                     "clientDataJSON": b64_cdata_json,
   1367                     "authenticatorData": b64_adata,
   1368                     "userHandle": b64_user,
   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         // `null` `signature`.
   1382         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
   1383             .to_string()
   1384             .into_bytes();
   1385         assert_eq!(
   1386             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1387                 serde_json::json!({
   1388                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1389                     "clientDataJSON": b64_cdata_json,
   1390                     "authenticatorData": b64_adata,
   1391                     "signature": null,
   1392                     "userHandle": b64_user,
   1393                     "clientExtensionResults": {},
   1394                     "type": "public-key"
   1395                 })
   1396                 .to_string()
   1397                 .as_str()
   1398             )
   1399             .unwrap_err()
   1400             .to_string()
   1401             .into_bytes()
   1402             .get(..err.len()),
   1403             Some(err.as_slice())
   1404         );
   1405         // Missing `userHandle`.
   1406         drop(
   1407             serde_json::from_str::<NonDiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1408                 serde_json::json!({
   1409                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1410                     "clientDataJSON": b64_cdata_json,
   1411                     "authenticatorData": b64_adata,
   1412                     "signature": b64_sig,
   1413                     "clientExtensionResults": {},
   1414                     "type": "public-key"
   1415                 })
   1416                 .to_string()
   1417                 .as_str(),
   1418             )
   1419             .unwrap(),
   1420         );
   1421         // `null` `userHandle`.
   1422         drop(
   1423             serde_json::from_str::<NonDiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1424                 serde_json::json!({
   1425                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1426                     "clientDataJSON": b64_cdata_json,
   1427                     "authenticatorData": b64_adata,
   1428                     "signature": b64_sig,
   1429                     "userHandle": null,
   1430                     "clientExtensionResults": {},
   1431                     "type": "public-key"
   1432                 })
   1433                 .to_string()
   1434                 .as_str(),
   1435             )
   1436             .unwrap(),
   1437         );
   1438         // `null` `authenticatorAttachment`.
   1439         assert!(
   1440             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1441                 serde_json::json!({
   1442                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1443                     "clientDataJSON": b64_cdata_json,
   1444                     "authenticatorData": b64_adata,
   1445                     "signature": b64_sig,
   1446                     "userHandle": b64_user,
   1447                     "authenticatorAttachment": null,
   1448                     "clientExtensionResults": {},
   1449                     "type": "public-key"
   1450                 })
   1451                 .to_string()
   1452                 .as_str()
   1453             )
   1454             .is_ok_and(|auth| matches!(
   1455                 auth.0.authenticator_attachment,
   1456                 AuthenticatorAttachment::None
   1457             ))
   1458         );
   1459         // Unknown `authenticatorAttachment`.
   1460         err = Error::invalid_value(
   1461             Unexpected::Str("Platform"),
   1462             &"'platform' or 'cross-platform'",
   1463         )
   1464         .to_string()
   1465         .into_bytes();
   1466         assert_eq!(
   1467             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1468                 serde_json::json!({
   1469                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1470                     "clientDataJSON": b64_cdata_json,
   1471                     "authenticatorData": b64_adata,
   1472                     "signature": b64_sig,
   1473                     "userHandle": b64_user,
   1474                     "authenticatorAttachment": "Platform",
   1475                     "clientExtensionResults": {},
   1476                     "type": "public-key"
   1477                 })
   1478                 .to_string()
   1479                 .as_str()
   1480             )
   1481             .unwrap_err()
   1482             .to_string()
   1483             .into_bytes()
   1484             .get(..err.len()),
   1485             Some(err.as_slice())
   1486         );
   1487         // Missing `clientDataJSON`.
   1488         err = Error::missing_field("clientDataJSON")
   1489             .to_string()
   1490             .into_bytes();
   1491         assert_eq!(
   1492             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1493                 serde_json::json!({
   1494                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1495                     "authenticatorData": b64_adata,
   1496                     "signature": b64_sig,
   1497                     "userHandle": b64_user,
   1498                     "clientExtensionResults": {},
   1499                     "type": "public-key"
   1500                 })
   1501                 .to_string()
   1502                 .as_str()
   1503             )
   1504             .unwrap_err()
   1505             .to_string()
   1506             .into_bytes()
   1507             .get(..err.len()),
   1508             Some(err.as_slice())
   1509         );
   1510         // `null` `clientDataJSON`.
   1511         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
   1512             .to_string()
   1513             .into_bytes();
   1514         assert_eq!(
   1515             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1516                 serde_json::json!({
   1517                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1518                     "clientDataJSON": null,
   1519                     "authenticatorData": b64_adata,
   1520                     "signature": b64_sig,
   1521                     "userHandle": b64_user,
   1522                     "clientExtensionResults": {},
   1523                     "type": "public-key"
   1524                 })
   1525                 .to_string()
   1526                 .as_str()
   1527             )
   1528             .unwrap_err()
   1529             .to_string()
   1530             .into_bytes()
   1531             .get(..err.len()),
   1532             Some(err.as_slice())
   1533         );
   1534         // Empty.
   1535         err = Error::missing_field("authenticatorData")
   1536             .to_string()
   1537             .into_bytes();
   1538         assert_eq!(
   1539             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1540                 serde_json::json!({}).to_string().as_str()
   1541             )
   1542             .unwrap_err()
   1543             .to_string()
   1544             .into_bytes()
   1545             .get(..err.len()),
   1546             Some(err.as_slice())
   1547         );
   1548         // Missing `clientExtensionResults`.
   1549         err = Error::missing_field("clientExtensionResults")
   1550             .to_string()
   1551             .into_bytes();
   1552         assert_eq!(
   1553             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1554                 serde_json::json!({
   1555                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1556                     "clientDataJSON": b64_cdata_json,
   1557                     "authenticatorData": b64_adata,
   1558                     "signature": b64_sig,
   1559                     "userHandle": b64_user,
   1560                     "type": "public-key"
   1561                 })
   1562                 .to_string()
   1563                 .as_str()
   1564             )
   1565             .unwrap_err()
   1566             .to_string()
   1567             .into_bytes()
   1568             .get(..err.len()),
   1569             Some(err.as_slice())
   1570         );
   1571         // `null` `clientExtensionResults`.
   1572         err = Error::invalid_type(Unexpected::Other("null"), &"ClientExtensionsOutputs")
   1573             .to_string()
   1574             .into_bytes();
   1575         assert_eq!(
   1576             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1577                 serde_json::json!({
   1578                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1579                     "clientDataJSON": b64_cdata_json,
   1580                     "authenticatorData": b64_adata,
   1581                     "signature": b64_sig,
   1582                     "userHandle": b64_user,
   1583                     "clientExtensionResults": null,
   1584                     "type": "public-key"
   1585                 })
   1586                 .to_string()
   1587                 .as_str()
   1588             )
   1589             .unwrap_err()
   1590             .to_string()
   1591             .into_bytes()
   1592             .get(..err.len()),
   1593             Some(err.as_slice())
   1594         );
   1595         drop(
   1596             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1597                 serde_json::json!({
   1598                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1599                     "clientDataJSON": b64_cdata_json,
   1600                     "authenticatorData": b64_adata,
   1601                     "signature": b64_sig,
   1602                     "userHandle": b64_user,
   1603                     "clientExtensionResults": {},
   1604                 })
   1605                 .to_string()
   1606                 .as_str(),
   1607             )
   1608             .unwrap(),
   1609         );
   1610         // `null` `type`.
   1611         err = Error::invalid_type(Unexpected::Other("null"), &"public-key")
   1612             .to_string()
   1613             .into_bytes();
   1614         assert_eq!(
   1615             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1616                 serde_json::json!({
   1617                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1618                     "clientDataJSON": b64_cdata_json,
   1619                     "authenticatorData": b64_adata,
   1620                     "signature": b64_sig,
   1621                     "userHandle": b64_user,
   1622                     "clientExtensionResults": {},
   1623                     "type": null
   1624                 })
   1625                 .to_string()
   1626                 .as_str()
   1627             )
   1628             .unwrap_err()
   1629             .to_string()
   1630             .into_bytes()
   1631             .get(..err.len()),
   1632             Some(err.as_slice())
   1633         );
   1634         // Not exactly `public-type` `type`.
   1635         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
   1636             .to_string()
   1637             .into_bytes();
   1638         assert_eq!(
   1639             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1640                 serde_json::json!({
   1641                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1642                     "clientDataJSON": b64_cdata_json,
   1643                     "authenticatorData": b64_adata,
   1644                     "signature": b64_sig,
   1645                     "userHandle": b64_user,
   1646                     "clientExtensionResults": {},
   1647                     "type": "Public-key"
   1648                 })
   1649                 .to_string()
   1650                 .as_str()
   1651             )
   1652             .unwrap_err()
   1653             .to_string()
   1654             .into_bytes()
   1655             .get(..err.len()),
   1656             Some(err.as_slice())
   1657         );
   1658         // `null`.
   1659         err = Error::invalid_type(Unexpected::Other("null"), &"CustomAuthentication")
   1660             .to_string()
   1661             .into_bytes();
   1662         assert_eq!(
   1663             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1664                 serde_json::json!(null).to_string().as_str()
   1665             )
   1666             .unwrap_err()
   1667             .to_string()
   1668             .into_bytes()
   1669             .get(..err.len()),
   1670             Some(err.as_slice())
   1671         );
   1672         // Unknown field.
   1673         err = Error::unknown_field(
   1674             "foo",
   1675             [
   1676                 "authenticatorAttachment",
   1677                 "authenticatorData",
   1678                 "clientDataJSON",
   1679                 "clientExtensionResults",
   1680                 "id",
   1681                 "signature",
   1682                 "type",
   1683                 "userHandle",
   1684             ]
   1685             .as_slice(),
   1686         )
   1687         .to_string()
   1688         .into_bytes();
   1689         assert_eq!(
   1690             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1691                 serde_json::json!({
   1692                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1693                     "clientDataJSON": b64_cdata_json,
   1694                     "authenticatorData": b64_adata,
   1695                     "signature": b64_sig,
   1696                     "userHandle": b64_user,
   1697                     "foo": true,
   1698                     "clientExtensionResults": {},
   1699                     "type": "public-key"
   1700                 })
   1701                 .to_string()
   1702                 .as_str()
   1703             )
   1704             .unwrap_err()
   1705             .to_string()
   1706             .into_bytes()
   1707             .get(..err.len()),
   1708             Some(err.as_slice())
   1709         );
   1710         // Duplicate field.
   1711         err = Error::duplicate_field("userHandle")
   1712             .to_string()
   1713             .into_bytes();
   1714         assert_eq!(
   1715             serde_json::from_str::<DiscoverableCustomAuthentication<USER_HANDLE_MIN_LEN>>(
   1716                 format!(
   1717                     "{{
   1718                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1719                        \"clientDataJSON\": \"{b64_cdata_json}\",
   1720                        \"authenticatorData\": \"{b64_adata}\",
   1721                        \"signature\": \"{b64_sig}\",
   1722                        \"userHandle\": \"{b64_user}\",
   1723                        \"userHandle\": \"{b64_user}\"
   1724                        \"clientExtensionResults\": {{}},
   1725                        \"type\": \"public-key\"
   1726 
   1727                      }}"
   1728                 )
   1729                 .as_str()
   1730             )
   1731             .unwrap_err()
   1732             .to_string()
   1733             .into_bytes()
   1734             .get(..err.len()),
   1735             Some(err.as_slice())
   1736         );
   1737     }
   1738     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   1739     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1740     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   1741     #[test]
   1742     fn client_extensions() {
   1743         let c_data_json = serde_json::json!({}).to_string();
   1744         let auth_data: [u8; 37] = [
   1745             // `rpIdHash`.
   1746             0,
   1747             0,
   1748             0,
   1749             0,
   1750             0,
   1751             0,
   1752             0,
   1753             0,
   1754             0,
   1755             0,
   1756             0,
   1757             0,
   1758             0,
   1759             0,
   1760             0,
   1761             0,
   1762             0,
   1763             0,
   1764             0,
   1765             0,
   1766             0,
   1767             0,
   1768             0,
   1769             0,
   1770             0,
   1771             0,
   1772             0,
   1773             0,
   1774             0,
   1775             0,
   1776             0,
   1777             0,
   1778             // `flags`.
   1779             0b0000_0101,
   1780             // `signCount`.
   1781             0,
   1782             0,
   1783             0,
   1784             0,
   1785         ];
   1786         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   1787         let b64_adata = base64url_nopad::encode(auth_data.as_slice());
   1788         let b64_sig = base64url_nopad::encode([].as_slice());
   1789         let b64_user = base64url_nopad::encode(b"\x00".as_slice());
   1790         // Base case is valid.
   1791         assert!(
   1792             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1793                 serde_json::json!({
   1794                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1795                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1796                     "response": {
   1797                         "clientDataJSON": b64_cdata_json,
   1798                         "authenticatorData": b64_adata,
   1799                         "signature": b64_sig,
   1800                         "userHandle": b64_user,
   1801                     },
   1802                     "authenticatorAttachment": "cross-platform",
   1803                     "clientExtensionResults": {},
   1804                     "type": "public-key"
   1805                 })
   1806                 .to_string()
   1807                 .as_str()
   1808             )
   1809             .is_ok_and(|auth| auth.0.response.client_data_json
   1810                 == c_data_json.as_bytes()
   1811                 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
   1812                 && auth.0.response.authenticator_data_and_c_data_hash[37..]
   1813                     == *Sha256::digest(c_data_json.as_bytes())
   1814                 && matches!(
   1815                     auth.0.authenticator_attachment,
   1816                     AuthenticatorAttachment::CrossPlatform
   1817                 ))
   1818         );
   1819         // `null` `prf`.
   1820         drop(
   1821             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1822                 serde_json::json!({
   1823                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1824                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1825                     "response": {
   1826                         "clientDataJSON": b64_cdata_json,
   1827                         "authenticatorData": b64_adata,
   1828                         "signature": b64_sig,
   1829                         "userHandle": b64_user,
   1830                     },
   1831                     "clientExtensionResults": {
   1832                         "prf": null
   1833                     },
   1834                     "type": "public-key"
   1835                 })
   1836                 .to_string()
   1837                 .as_str(),
   1838             )
   1839             .unwrap(),
   1840         );
   1841         // Unknown `clientExtensionResults`.
   1842         drop(
   1843             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1844                 serde_json::json!({
   1845                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1846                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1847                     "response": {
   1848                         "clientDataJSON": b64_cdata_json,
   1849                         "authenticatorData": b64_adata,
   1850                         "signature": b64_sig,
   1851                         "userHandle": b64_user,
   1852                     },
   1853                     "clientExtensionResults": {
   1854                         "Prf": null
   1855                     },
   1856                     "type": "public-key"
   1857                 })
   1858                 .to_string()
   1859                 .as_str(),
   1860             )
   1861             .unwrap(),
   1862         );
   1863         // Duplicate field.
   1864         let mut err = Error::duplicate_field("prf").to_string().into_bytes();
   1865         assert_eq!(
   1866             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1867                 format!(
   1868                     "{{
   1869                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1870                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1871                        \"response\": {{
   1872                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1873                            \"authenticatorData\": \"{b64_adata}\",
   1874                            \"signature\": \"{b64_sig}\",
   1875                            \"userHandle\": \"{b64_user}\"
   1876                        }},
   1877                        \"clientExtensionResults\": {{
   1878                            \"prf\": null,
   1879                            \"prf\": null
   1880                        }},
   1881                        \"type\": \"public-key\"
   1882                      }}"
   1883                 )
   1884                 .as_str()
   1885             )
   1886             .unwrap_err()
   1887             .to_string()
   1888             .into_bytes()
   1889             .get(..err.len()),
   1890             Some(err.as_slice())
   1891         );
   1892         // `null` `results`.
   1893         drop(
   1894             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1895                 serde_json::json!({
   1896                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1897                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1898                     "response": {
   1899                         "clientDataJSON": b64_cdata_json,
   1900                         "authenticatorData": b64_adata,
   1901                         "signature": b64_sig,
   1902                         "userHandle": b64_user,
   1903                     },
   1904                     "clientExtensionResults": {
   1905                         "prf": {
   1906                             "results": null,
   1907                         }
   1908                     },
   1909                     "type": "public-key"
   1910                 })
   1911                 .to_string()
   1912                 .as_str(),
   1913             )
   1914             .unwrap(),
   1915         );
   1916         // Duplicate field in `prf`.
   1917         err = Error::duplicate_field("results").to_string().into_bytes();
   1918         assert_eq!(
   1919             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1920                 format!(
   1921                     "{{
   1922                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1923                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1924                        \"response\": {{
   1925                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1926                            \"authenticatorData\": \"{b64_adata}\",
   1927                            \"signature\": \"{b64_sig}\",
   1928                            \"userHandle\": \"{b64_user}\"
   1929                        }},
   1930                        \"clientExtensionResults\": {{
   1931                            \"prf\": {{
   1932                                \"results\": null,
   1933                                \"results\": null
   1934                            }}
   1935                        }},
   1936                        \"type\": \"public-key\"
   1937                      }}"
   1938                 )
   1939                 .as_str()
   1940             )
   1941             .unwrap_err()
   1942             .to_string()
   1943             .into_bytes()
   1944             .get(..err.len()),
   1945             Some(err.as_slice())
   1946         );
   1947         // Missing `first`.
   1948         drop(
   1949             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1950                 serde_json::json!({
   1951                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1952                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1953                     "response": {
   1954                         "clientDataJSON": b64_cdata_json,
   1955                         "authenticatorData": b64_adata,
   1956                         "signature": b64_sig,
   1957                         "userHandle": b64_user,
   1958                     },
   1959                     "clientExtensionResults": {
   1960                         "prf": {
   1961                             "results": {},
   1962                         }
   1963                     },
   1964                     "type": "public-key"
   1965                 })
   1966                 .to_string()
   1967                 .as_str(),
   1968             )
   1969             .unwrap(),
   1970         );
   1971         // `null` `first`.
   1972         drop(
   1973             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   1974                 serde_json::json!({
   1975                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1976                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1977                     "response": {
   1978                         "clientDataJSON": b64_cdata_json,
   1979                         "authenticatorData": b64_adata,
   1980                         "signature": b64_sig,
   1981                         "userHandle": b64_user,
   1982                     },
   1983                     "clientExtensionResults": {
   1984                         "prf": {
   1985                             "results": {
   1986                                 "first": null
   1987                             },
   1988                         }
   1989                     },
   1990                     "type": "public-key"
   1991                 })
   1992                 .to_string()
   1993                 .as_str(),
   1994             )
   1995             .unwrap(),
   1996         );
   1997         // `null` `second`.
   1998         drop(
   1999             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2000                 serde_json::json!({
   2001                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2002                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2003                     "response": {
   2004                         "clientDataJSON": b64_cdata_json,
   2005                         "authenticatorData": b64_adata,
   2006                         "signature": b64_sig,
   2007                         "userHandle": b64_user,
   2008                     },
   2009                     "clientExtensionResults": {
   2010                         "prf": {
   2011                             "results": {
   2012                                 "first": null,
   2013                                 "second": null
   2014                             },
   2015                         }
   2016                     },
   2017                     "type": "public-key"
   2018                 })
   2019                 .to_string()
   2020                 .as_str(),
   2021             )
   2022             .unwrap(),
   2023         );
   2024         // Non-`null` `first`.
   2025         err = Error::invalid_type(Unexpected::Option, &"null")
   2026             .to_string()
   2027             .into_bytes();
   2028         assert_eq!(
   2029             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2030                 serde_json::json!({
   2031                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2032                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2033                     "response": {
   2034                         "clientDataJSON": b64_cdata_json,
   2035                         "authenticatorData": b64_adata,
   2036                         "signature": b64_sig,
   2037                         "userHandle": b64_user,
   2038                     },
   2039                     "clientExtensionResults": {
   2040                         "prf": {
   2041                             "results": {
   2042                                 "first": ""
   2043                             },
   2044                         }
   2045                     },
   2046                     "type": "public-key"
   2047                 })
   2048                 .to_string()
   2049                 .as_str()
   2050             )
   2051             .unwrap_err()
   2052             .to_string()
   2053             .into_bytes()
   2054             .get(..err.len()),
   2055             Some(err.as_slice())
   2056         );
   2057         // Non-`null` `second`.
   2058         err = Error::invalid_type(Unexpected::Option, &"null")
   2059             .to_string()
   2060             .into_bytes();
   2061         assert_eq!(
   2062             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2063                 serde_json::json!({
   2064                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2065                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2066                     "response": {
   2067                         "clientDataJSON": b64_cdata_json,
   2068                         "authenticatorData": b64_adata,
   2069                         "signature": b64_sig,
   2070                         "userHandle": b64_user,
   2071                     },
   2072                     "clientExtensionResults": {
   2073                         "prf": {
   2074                             "results": {
   2075                                 "first": null,
   2076                                 "second": ""
   2077                             },
   2078                         }
   2079                     },
   2080                     "type": "public-key"
   2081                 })
   2082                 .to_string()
   2083                 .as_str()
   2084             )
   2085             .unwrap_err()
   2086             .to_string()
   2087             .into_bytes()
   2088             .get(..err.len()),
   2089             Some(err.as_slice())
   2090         );
   2091         // `enabled` is still not allowed.
   2092         err = Error::unknown_field("enabled", ["results"].as_slice())
   2093             .to_string()
   2094             .into_bytes();
   2095         assert_eq!(
   2096             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2097                 serde_json::json!({
   2098                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2099                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2100                     "response": {
   2101                         "clientDataJSON": b64_cdata_json,
   2102                         "authenticatorData": b64_adata,
   2103                         "signature": b64_sig,
   2104                         "userHandle": b64_user,
   2105                     },
   2106                     "clientExtensionResults": {
   2107                         "prf": {
   2108                             "enabled": true,
   2109                             "results": null
   2110                         }
   2111                     },
   2112                     "type": "public-key"
   2113                 })
   2114                 .to_string()
   2115                 .as_str()
   2116             )
   2117             .unwrap_err()
   2118             .to_string()
   2119             .into_bytes()
   2120             .get(..err.len()),
   2121             Some(err.as_slice())
   2122         );
   2123         // Unknown `prf` field.
   2124         drop(
   2125             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2126                 serde_json::json!({
   2127                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2128                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2129                     "response": {
   2130                         "clientDataJSON": b64_cdata_json,
   2131                         "authenticatorData": b64_adata,
   2132                         "signature": b64_sig,
   2133                         "userHandle": b64_user,
   2134                     },
   2135                     "clientExtensionResults": {
   2136                         "prf": {
   2137                             "foo": true,
   2138                             "results": null
   2139                         }
   2140                     },
   2141                     "type": "public-key"
   2142                 })
   2143                 .to_string()
   2144                 .as_str(),
   2145             )
   2146             .unwrap(),
   2147         );
   2148         // Unknown `results` field.
   2149         drop(
   2150             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2151                 serde_json::json!({
   2152                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   2153                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   2154                     "response": {
   2155                         "clientDataJSON": b64_cdata_json,
   2156                         "authenticatorData": b64_adata,
   2157                         "signature": b64_sig,
   2158                         "userHandle": b64_user,
   2159                     },
   2160                     "clientExtensionResults": {
   2161                         "prf": {
   2162                             "results": {
   2163                                 "first": null,
   2164                                 "Second": null
   2165                             }
   2166                         }
   2167                     },
   2168                     "type": "public-key"
   2169                 })
   2170                 .to_string()
   2171                 .as_str(),
   2172             )
   2173             .unwrap(),
   2174         );
   2175         // Duplicate field in `results`.
   2176         err = Error::duplicate_field("first").to_string().into_bytes();
   2177         assert_eq!(
   2178             serde_json::from_str::<DiscoverableAuthenticationRelaxed<USER_HANDLE_MIN_LEN>>(
   2179                 format!(
   2180                     "{{
   2181                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2182                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   2183                        \"response\": {{
   2184                            \"clientDataJSON\": \"{b64_cdata_json}\",
   2185                            \"authenticatorData\": \"{b64_adata}\",
   2186                            \"signature\": \"{b64_sig}\",
   2187                            \"userHandle\": \"{b64_user}\"
   2188                        }},
   2189                        \"clientExtensionResults\": {{
   2190                            \"prf\": {{
   2191                                \"results\": {{
   2192                                    \"first\": null,
   2193                                    \"first\": null
   2194                                }}
   2195                            }}
   2196                        }},
   2197                        \"type\": \"public-key\"
   2198                      }}"
   2199                 )
   2200                 .as_str()
   2201             )
   2202             .unwrap_err()
   2203             .to_string()
   2204             .into_bytes()
   2205             .get(..err.len()),
   2206             Some(err.as_slice())
   2207         );
   2208     }
   2209 }