webauthn_rp

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

ser.rs (66394B)


      1 use super::{
      2     super::{
      3         super::response::ser::{Base64DecodedVal, PublicKeyCredential},
      4         ser::{
      5             AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues,
      6             ClientExtensions,
      7         },
      8     },
      9     Authentication, AuthenticatorAssertion, UserHandle,
     10     error::UnknownCredentialOptions,
     11 };
     12 #[cfg(doc)]
     13 use super::{AuthenticatorAttachment, CredentialId};
     14 use core::{
     15     fmt::{self, Formatter},
     16     marker::PhantomData,
     17     str,
     18 };
     19 use rsa::sha2::{Sha256, digest::OutputSizeUser as _};
     20 use serde::{
     21     de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor},
     22     ser::{Serialize, SerializeStruct as _, Serializer},
     23 };
     24 /// Authenticator data.
     25 pub(super) struct AuthData(pub Vec<u8>);
     26 impl<'e> Deserialize<'e> for AuthData {
     27     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     28     where
     29         D: Deserializer<'e>,
     30     {
     31         /// `Visitor` for `AuthData`.
     32         struct AuthDataVisitor;
     33         impl Visitor<'_> for AuthDataVisitor {
     34             type Value = AuthData;
     35             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     36                 formatter.write_str("AuthenticatorData")
     37             }
     38             #[expect(
     39                 clippy::arithmetic_side_effects,
     40                 reason = "comment justifies its correctness"
     41             )]
     42             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
     43             where
     44                 E: Error,
     45             {
     46                 base64url_nopad::decode_len(v.len())
     47                     .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value"))
     48                     .and_then(|len| {
     49                         // The decoded length is 3/4 of the encoded length, so overflow could only occur
     50                         // if usize::MAX / 4 < 32 => usize::MAX < 128 < u16::MAX; thus overflow is not
     51                         // possible.
     52                         // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the
     53                         // raw authenticator data by `AuthenticatorDataAssertion::new`.
     54                         let mut auth_data = vec![0; len + Sha256::output_size()];
     55                         auth_data.truncate(len);
     56                         base64url_nopad::decode_buffer_exact(v.as_bytes(), auth_data.as_mut_slice())
     57                             .map_err(E::custom)
     58                             .map(|()| AuthData(auth_data))
     59                     })
     60             }
     61         }
     62         deserializer.deserialize_str(AuthDataVisitor)
     63     }
     64 }
     65 /// `Visitor` for `AuthenticatorAssertion`.
     66 ///
     67 /// Unknown fields are ignored and only `clientDataJSON`, `authenticatorData`, and `signature` are required iff
     68 /// `RELAXED`.
     69 pub(super) struct AuthenticatorAssertionVisitor<
     70     const RELAXED: bool,
     71     const USER_LEN: usize,
     72     const DISCOVERABLE: bool,
     73 >;
     74 impl<'d, const R: bool, const LEN: usize, const DISC: bool> Visitor<'d>
     75     for AuthenticatorAssertionVisitor<R, LEN, DISC>
     76 where
     77     UserHandle<LEN>: Deserialize<'d>,
     78 {
     79     type Value = AuthenticatorAssertion<LEN, DISC>;
     80     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     81         formatter.write_str("AuthenticatorAssertion")
     82     }
     83     #[expect(clippy::too_many_lines, reason = "107 lines is fine")]
     84     fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
     85     where
     86         A: MapAccess<'d>,
     87     {
     88         /// Fields in `AuthenticatorAssertionResponseJSON`.
     89         enum Field<const IGNORE_UNKNOWN: bool> {
     90             /// `clientDataJSON`.
     91             ClientDataJson,
     92             /// `authenticatorData`.
     93             AuthenticatorData,
     94             /// `signature`.
     95             Signature,
     96             /// `userHandle`.
     97             UserHandle,
     98             /// Unknown field.
     99             Other,
    100         }
    101         impl<'e, const I: bool> Deserialize<'e> for Field<I> {
    102             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    103             where
    104                 D: Deserializer<'e>,
    105             {
    106                 /// `Visitor` for `Field`.
    107                 struct FieldVisitor<const IGNORE_UNKNOWN: bool>;
    108                 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> {
    109                     type Value = Field<IG>;
    110                     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    111                         write!(
    112                             formatter,
    113                             "'{CLIENT_DATA_JSON}', '{AUTHENTICATOR_DATA}', '{SIGNATURE}', or '{USER_HANDLE}'"
    114                         )
    115                     }
    116                     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    117                     where
    118                         E: Error,
    119                     {
    120                         match v {
    121                             CLIENT_DATA_JSON => Ok(Field::ClientDataJson),
    122                             AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData),
    123                             SIGNATURE => Ok(Field::Signature),
    124                             USER_HANDLE => Ok(Field::UserHandle),
    125                             _ => {
    126                                 if IG {
    127                                     Ok(Field::Other)
    128                                 } else {
    129                                     Err(E::unknown_field(v, AUTH_ASSERT_FIELDS))
    130                                 }
    131                             }
    132                         }
    133                     }
    134                 }
    135                 deserializer.deserialize_identifier(FieldVisitor::<I>)
    136             }
    137         }
    138         let mut client_data = None;
    139         let mut auth = None;
    140         let mut sig = None;
    141         let mut user_handle = None;
    142         while let Some(key) = map.next_key::<Field<R>>()? {
    143             match key {
    144                 Field::ClientDataJson => {
    145                     if client_data.is_some() {
    146                         return Err(Error::duplicate_field(CLIENT_DATA_JSON));
    147                     }
    148                     client_data = map
    149                         .next_value::<Base64DecodedVal>()
    150                         .map(|c_data| c_data.0)
    151                         .map(Some)?;
    152                 }
    153                 Field::AuthenticatorData => {
    154                     if auth.is_some() {
    155                         return Err(Error::duplicate_field(AUTHENTICATOR_DATA));
    156                     }
    157                     auth = map
    158                         .next_value::<AuthData>()
    159                         .map(|auth_data| Some(auth_data.0))?;
    160                 }
    161                 Field::Signature => {
    162                     if sig.is_some() {
    163                         return Err(Error::duplicate_field(SIGNATURE));
    164                     }
    165                     sig = map
    166                         .next_value::<Base64DecodedVal>()
    167                         .map(|signature| signature.0)
    168                         .map(Some)?;
    169                 }
    170                 Field::UserHandle => {
    171                     if user_handle.is_some() {
    172                         return Err(Error::duplicate_field(USER_HANDLE));
    173                     }
    174                     user_handle = map.next_value().map(Some)?;
    175                 }
    176                 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?,
    177             }
    178         }
    179         client_data
    180             .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON))
    181             .and_then(|client_data_json| {
    182                 auth.ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA))
    183                     .and_then(|authenticator_data| {
    184                         sig.ok_or_else(|| Error::missing_field(SIGNATURE))
    185                             .and_then(|signature| {
    186                                 if DISC {
    187                                     user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE))
    188                                 } else {
    189                                     user_handle.map_or_else(|| Ok(None), Ok)
    190                                 }
    191                                 .map(|user| {
    192                                     AuthenticatorAssertion::new_inner(
    193                                         client_data_json,
    194                                         authenticator_data,
    195                                         signature,
    196                                         user,
    197                                     )
    198                                 })
    199                             })
    200                     })
    201             })
    202     }
    203 }
    204 /// `"clientDataJSON"`.
    205 const CLIENT_DATA_JSON: &str = "clientDataJSON";
    206 /// `"authenticatorData"`.
    207 const AUTHENTICATOR_DATA: &str = "authenticatorData";
    208 /// `"signature"`.
    209 const SIGNATURE: &str = "signature";
    210 /// `"userHandle"`.
    211 const USER_HANDLE: &str = "userHandle";
    212 /// Fields in `AuthenticatorAssertionResponseJSON`.
    213 pub(super) const AUTH_ASSERT_FIELDS: &[&str; 4] =
    214     &[CLIENT_DATA_JSON, AUTHENTICATOR_DATA, SIGNATURE, USER_HANDLE];
    215 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    216     for AuthenticatorAssertion<USER_LEN, DISCOVERABLE>
    217 where
    218     UserHandle<USER_LEN>: Deserialize<'de>,
    219 {
    220     /// Deserializes a `struct` based on
    221     /// [`AuthenticatorAssertionResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorassertionresponsejson).
    222     ///
    223     /// Note unknown keys and duplicate keys are forbidden;
    224     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-clientdatajson),
    225     /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-authenticatordata),
    226     /// and
    227     /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-signature) are
    228     /// base64url-decoded;
    229     /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle) is
    230     /// required and must not be `null` iff `DISCOVERABLE`. When it exists and is not `null`, it is deserialized
    231     /// via [`UserHandle::deserialize`]. All `required` fields in the `AuthenticatorAssertionResponseJSON` Web IDL
    232     /// `dictionary` exist (and are not `null`).
    233     #[inline]
    234     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    235     where
    236         D: Deserializer<'de>,
    237     {
    238         deserializer.deserialize_struct(
    239             "AuthenticatorAssertion",
    240             AUTH_ASSERT_FIELDS,
    241             AuthenticatorAssertionVisitor::<false, USER_LEN, DISCOVERABLE>,
    242         )
    243     }
    244 }
    245 /// Empty map of client extensions.
    246 pub(super) struct ClientExtensionsOutputs;
    247 /// `Visitor` for `ClientExtensionsOutputs`.
    248 ///
    249 /// Unknown fields are ignored iff `RELAXED`.
    250 pub(super) struct ClientExtensionsOutputsVisitor<const RELAXED: bool, PRF>(
    251     pub PhantomData<fn() -> PRF>,
    252 );
    253 impl<'d, const R: bool, P> Visitor<'d> for ClientExtensionsOutputsVisitor<R, P>
    254 where
    255     P: for<'a> Deserialize<'a>,
    256 {
    257     type Value = ClientExtensionsOutputs;
    258     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    259         formatter.write_str("ClientExtensionsOutputs")
    260     }
    261     fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    262     where
    263         A: MapAccess<'d>,
    264     {
    265         /// Allowed fields.
    266         enum Field<const IGNORE_UNKNOWN: bool> {
    267             /// `prf`.
    268             Prf,
    269             /// Unknown field.
    270             Other,
    271         }
    272         impl<'e, const I: bool> Deserialize<'e> for Field<I> {
    273             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    274             where
    275                 D: Deserializer<'e>,
    276             {
    277                 /// `Visitor` for `Field`.
    278                 ///
    279                 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`.
    280                 struct FieldVisitor<const IGNORE_UNKNOWN: bool>;
    281                 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> {
    282                     type Value = Field<IG>;
    283                     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    284                         write!(formatter, "'{PRF}'")
    285                     }
    286                     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    287                     where
    288                         E: Error,
    289                     {
    290                         match v {
    291                             PRF => Ok(Field::Prf),
    292                             _ => {
    293                                 if IG {
    294                                     Ok(Field::Other)
    295                                 } else {
    296                                     Err(E::unknown_field(v, EXT_FIELDS))
    297                                 }
    298                             }
    299                         }
    300                     }
    301                 }
    302                 deserializer.deserialize_identifier(FieldVisitor::<I>)
    303             }
    304         }
    305         let mut prf = None;
    306         while let Some(key) = map.next_key::<Field<R>>()? {
    307             match key {
    308                 Field::Prf => {
    309                     if prf.is_some() {
    310                         return Err(Error::duplicate_field(PRF));
    311                     }
    312                     prf = map.next_value::<Option<P>>().map(Some)?;
    313                 }
    314                 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?,
    315             }
    316         }
    317         Ok(ClientExtensionsOutputs)
    318     }
    319 }
    320 impl ClientExtensions for ClientExtensionsOutputs {
    321     fn empty() -> Self {
    322         Self
    323     }
    324 }
    325 /// `"prf"`
    326 const PRF: &str = "prf";
    327 /// `AuthenticationExtensionsClientOutputsJSON` fields.
    328 pub(super) const EXT_FIELDS: &[&str; 1] = &[PRF];
    329 impl<'de> Deserialize<'de> for ClientExtensionsOutputs {
    330     /// Deserializes a `struct` based on
    331     /// [`AuthenticationExtensionsClientOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputsjson).
    332     ///
    333     /// Note that unknown and duplicate keys are forbidden and
    334     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null`
    335     /// or deserialized via [`AuthenticationExtensionsPrfOutputs::deserialize`].
    336     #[inline]
    337     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    338     where
    339         D: Deserializer<'de>,
    340     {
    341         deserializer.deserialize_struct(
    342             "ClientExtensionsOutputs",
    343             EXT_FIELDS,
    344             ClientExtensionsOutputsVisitor::<
    345                 false,
    346                 AuthenticationExtensionsPrfOutputsHelper<
    347                     false,
    348                     false,
    349                     AuthenticationExtensionsPrfValues,
    350                 >,
    351             >(PhantomData),
    352         )
    353     }
    354 }
    355 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    356     for Authentication<USER_LEN, DISCOVERABLE>
    357 where
    358     UserHandle<USER_LEN>: Deserialize<'de>,
    359 {
    360     /// Deserializes a `struct` based on
    361     /// [`AuthenticationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson).
    362     ///
    363     /// Note that unknown and duplicate keys are forbidden;
    364     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and
    365     /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-rawid) are deserialized
    366     /// via [`CredentialId::deserialize`];
    367     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized
    368     /// via [`AuthenticatorAssertion::deserialize`];
    369     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-authenticatorattachment)
    370     /// is `null` or deserialized via [`AuthenticatorAttachment::deserialize`];
    371     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults)
    372     /// is deserialized such that it is an empty map or a map that only contains
    373     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) which additionally must be
    374     /// `null` or an
    375     /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs)
    376     /// such that unknown and duplicate keys are forbidden,
    377     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled)
    378     /// is forbidden (including being assigned `null`),
    379     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
    380     /// be `null`, or be an
    381     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
    382     /// with no unknown or duplicate keys,
    383     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) must exist but be
    384     /// `null`, and
    385     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
    386     /// must be `null` if so; all `required` fields in the `AuthenticationResponseJSON` Web IDL `dictionary` exist
    387     /// (and are not `null`); [`type`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-type) is
    388     /// `"public-key"`; and the decoded `id` and decoded `rawId` are the same.
    389     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
    390     #[inline]
    391     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    392     where
    393         D: Deserializer<'de>,
    394     {
    395         PublicKeyCredential::<
    396             false,
    397             false,
    398             AuthenticatorAssertion<USER_LEN, DISCOVERABLE>,
    399             ClientExtensionsOutputs,
    400         >::deserialize(deserializer)
    401         .map(|cred| Self {
    402             raw_id: cred.id.unwrap_or_else(|| {
    403                 unreachable!("there is a bug in PublicKeyCredential::deserialize")
    404             }),
    405             response: cred.response,
    406             authenticator_attachment: cred.authenticator_attachment,
    407         })
    408     }
    409 }
    410 impl Serialize for UnknownCredentialOptions<'_, '_> {
    411     /// Serializes `self` to conform with
    412     /// [`UnknownCredentialOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-unknowncredentialoptions).
    413     ///
    414     /// # Examples
    415     ///
    416     /// ```
    417     /// # use core::str::FromStr;
    418     /// # use webauthn_rp::{request::{AsciiDomain, RpId}, response::{auth::error::UnknownCredentialOptions, CredentialId}};
    419     /// # #[cfg(feature = "custom")]
    420     /// let credential_id = CredentialId::try_from(vec![0; 16])?;
    421     /// # #[cfg(feature = "custom")]
    422     /// assert_eq!(
    423     ///     serde_json::to_string(&UnknownCredentialOptions {
    424     ///         rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
    425     ///         credential_id: (&credential_id).into(),
    426     ///     })
    427     ///     .unwrap(),
    428     ///     r#"{"rpId":"example.com","credentialId":"AAAAAAAAAAAAAAAAAAAAAA"}"#
    429     /// );
    430     /// # Ok::<_, webauthn_rp::AggErr>(())
    431     /// ```
    432     #[inline]
    433     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    434     where
    435         S: Serializer,
    436     {
    437         serializer
    438             .serialize_struct("UnknownCredentialOptions", 2)
    439             .and_then(|mut ser| {
    440                 ser.serialize_field("rpId", self.rp_id).and_then(|()| {
    441                     ser.serialize_field("credentialId", &self.credential_id)
    442                         .and_then(|()| ser.end())
    443                 })
    444             })
    445     }
    446 }
    447 #[cfg(test)]
    448 mod tests {
    449     use super::super::{
    450         super::super::request::register::USER_HANDLE_MIN_LEN, AuthenticatorAttachment,
    451         DiscoverableAuthentication, NonDiscoverableAuthentication,
    452     };
    453     use rsa::sha2::{Digest as _, Sha256};
    454     use serde::de::{Error as _, Unexpected};
    455     use serde_json::Error;
    456     #[expect(clippy::unwrap_used, reason = "OK in tests")]
    457     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
    458     #[expect(
    459         clippy::cognitive_complexity,
    460         clippy::too_many_lines,
    461         reason = "a lot to test"
    462     )]
    463     #[test]
    464     fn eddsa_authentication_deserialize_data_mismatch() {
    465         let c_data_json = serde_json::json!({}).to_string();
    466         let auth_data = [
    467             // `rpIdHash`.
    468             0,
    469             0,
    470             0,
    471             0,
    472             0,
    473             0,
    474             0,
    475             0,
    476             0,
    477             0,
    478             0,
    479             0,
    480             0,
    481             0,
    482             0,
    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             // `flags`.
    501             0b0000_0101,
    502             // `signCount`.
    503             0,
    504             0,
    505             0,
    506             0,
    507         ];
    508         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
    509         let b64_adata = base64url_nopad::encode(auth_data.as_slice());
    510         let b64_sig = base64url_nopad::encode([].as_slice());
    511         let b64_user = base64url_nopad::encode(b"\x00".as_slice());
    512         let auth_data_len = 37;
    513         // Base case is valid.
    514         assert!(
    515             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    516                 serde_json::json!({
    517                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    518                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    519                     "response": {
    520                         "clientDataJSON": b64_cdata_json,
    521                         "authenticatorData": b64_adata,
    522                         "signature": b64_sig,
    523                         "userHandle": b64_user,
    524                     },
    525                     "authenticatorAttachment": "cross-platform",
    526                     "clientExtensionResults": {},
    527                     "type": "public-key"
    528                 })
    529                 .to_string()
    530                 .as_str()
    531             )
    532             .is_ok_and(
    533                 |auth| auth.response.client_data_json == c_data_json.as_bytes()
    534                     && auth.response.authenticator_data_and_c_data_hash[..auth_data_len]
    535                         == auth_data
    536                     && auth.response.authenticator_data_and_c_data_hash[auth_data_len..]
    537                         == *Sha256::digest(c_data_json.as_bytes())
    538                     && matches!(
    539                         auth.authenticator_attachment,
    540                         AuthenticatorAttachment::CrossPlatform
    541                     )
    542             )
    543         );
    544         // `id` and `rawId` mismatch.
    545         let mut err = Error::invalid_value(
    546             Unexpected::Bytes(
    547                 base64url_nopad::decode(b"ABABABABABABABABABABAA")
    548                     .unwrap()
    549                     .as_slice(),
    550             ),
    551             &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(),
    552         )
    553         .to_string()
    554         .into_bytes();
    555         assert_eq!(
    556             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    557                 serde_json::json!({
    558                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    559                     "rawId": "ABABABABABABABABABABAA",
    560                     "response": {
    561                         "clientDataJSON": b64_cdata_json,
    562                         "authenticatorData": b64_adata,
    563                         "signature": b64_sig,
    564                         "userHandle": b64_user,
    565                     },
    566                     "authenticatorAttachment": "cross-platform",
    567                     "clientExtensionResults": {},
    568                     "type": "public-key"
    569                 })
    570                 .to_string()
    571                 .as_str()
    572             )
    573             .unwrap_err()
    574             .to_string()
    575             .into_bytes()
    576             .get(..err.len()),
    577             Some(err.as_slice())
    578         );
    579         // missing `id`.
    580         err = Error::missing_field("id").to_string().into_bytes();
    581         assert_eq!(
    582             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    583                 serde_json::json!({
    584                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    585                     "response": {
    586                         "clientDataJSON": b64_cdata_json,
    587                         "authenticatorData": b64_adata,
    588                         "signature": b64_sig,
    589                         "userHandle": b64_user,
    590                     },
    591                     "authenticatorAttachment": "cross-platform",
    592                     "clientExtensionResults": {},
    593                     "type": "public-key"
    594                 })
    595                 .to_string()
    596                 .as_str()
    597             )
    598             .unwrap_err()
    599             .to_string()
    600             .into_bytes()
    601             .get(..err.len()),
    602             Some(err.as_slice())
    603         );
    604         // `null` `id`.
    605         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    606             .to_string()
    607             .into_bytes();
    608         assert_eq!(
    609             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    610                 serde_json::json!({
    611                     "id": null,
    612                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    613                     "response": {
    614                         "clientDataJSON": b64_cdata_json,
    615                         "authenticatorData": b64_adata,
    616                         "signature": b64_sig,
    617                         "userHandle": b64_user,
    618                     },
    619                     "clientExtensionResults": {},
    620                     "type": "public-key"
    621                 })
    622                 .to_string()
    623                 .as_str()
    624             )
    625             .unwrap_err()
    626             .to_string()
    627             .into_bytes()
    628             .get(..err.len()),
    629             Some(err.as_slice())
    630         );
    631         // missing `rawId`.
    632         err = Error::missing_field("rawId").to_string().into_bytes();
    633         assert_eq!(
    634             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    635                 serde_json::json!({
    636                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    637                     "response": {
    638                         "clientDataJSON": b64_cdata_json,
    639                         "authenticatorData": b64_adata,
    640                         "signature": b64_sig,
    641                         "userHandle": b64_user,
    642                     },
    643                     "clientExtensionResults": {},
    644                     "type": "public-key"
    645                 })
    646                 .to_string()
    647                 .as_str()
    648             )
    649             .unwrap_err()
    650             .to_string()
    651             .into_bytes()
    652             .get(..err.len()),
    653             Some(err.as_slice())
    654         );
    655         // `null` `rawId`.
    656         err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId")
    657             .to_string()
    658             .into_bytes();
    659         assert_eq!(
    660             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    661                 serde_json::json!({
    662                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    663                     "rawId": null,
    664                     "response": {
    665                         "clientDataJSON": b64_cdata_json,
    666                         "authenticatorData": b64_adata,
    667                         "signature": b64_sig,
    668                         "userHandle": b64_user,
    669                     },
    670                     "clientExtensionResults": {},
    671                     "type": "public-key"
    672                 })
    673                 .to_string()
    674                 .as_str()
    675             )
    676             .unwrap_err()
    677             .to_string()
    678             .into_bytes()
    679             .get(..err.len()),
    680             Some(err.as_slice())
    681         );
    682         // Missing `authenticatorData`.
    683         err = Error::missing_field("authenticatorData")
    684             .to_string()
    685             .into_bytes();
    686         assert_eq!(
    687             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    688                 serde_json::json!({
    689                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    690                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    691                     "response": {
    692                         "clientDataJSON": b64_cdata_json,
    693                         "signature": b64_sig,
    694                         "userHandle": b64_user,
    695                     },
    696                     "clientExtensionResults": {},
    697                     "type": "public-key"
    698                 })
    699                 .to_string()
    700                 .as_str()
    701             )
    702             .unwrap_err()
    703             .to_string()
    704             .into_bytes()
    705             .get(..err.len()),
    706             Some(err.as_slice())
    707         );
    708         // `null` `authenticatorData`.
    709         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData")
    710             .to_string()
    711             .into_bytes();
    712         assert_eq!(
    713             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    714                 serde_json::json!({
    715                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    716                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    717                     "response": {
    718                         "clientDataJSON": b64_cdata_json,
    719                         "authenticatorData": null,
    720                         "signature": b64_sig,
    721                         "userHandle": b64_user,
    722                     },
    723                     "clientExtensionResults": {},
    724                     "type": "public-key"
    725                 })
    726                 .to_string()
    727                 .as_str()
    728             )
    729             .unwrap_err()
    730             .to_string()
    731             .into_bytes()
    732             .get(..err.len()),
    733             Some(err.as_slice())
    734         );
    735         // Missing `signature`.
    736         err = Error::missing_field("signature").to_string().into_bytes();
    737         assert_eq!(
    738             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    739                 serde_json::json!({
    740                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    741                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    742                     "response": {
    743                         "clientDataJSON": b64_cdata_json,
    744                         "authenticatorData": b64_adata,
    745                         "userHandle": b64_user,
    746                     },
    747                     "clientExtensionResults": {},
    748                     "type": "public-key"
    749                 })
    750                 .to_string()
    751                 .as_str()
    752             )
    753             .unwrap_err()
    754             .to_string()
    755             .into_bytes()
    756             .get(..err.len()),
    757             Some(err.as_slice())
    758         );
    759         // `null` `signature`.
    760         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    761             .to_string()
    762             .into_bytes();
    763         assert_eq!(
    764             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    765                 serde_json::json!({
    766                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    767                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    768                     "response": {
    769                         "clientDataJSON": b64_cdata_json,
    770                         "authenticatorData": b64_adata,
    771                         "signature": null,
    772                         "userHandle": b64_user,
    773                     },
    774                     "clientExtensionResults": {},
    775                     "type": "public-key"
    776                 })
    777                 .to_string()
    778                 .as_str()
    779             )
    780             .unwrap_err()
    781             .to_string()
    782             .into_bytes()
    783             .get(..err.len()),
    784             Some(err.as_slice())
    785         );
    786         // Missing `userHandle`.
    787         drop(
    788             serde_json::from_str::<NonDiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    789                 serde_json::json!({
    790                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    791                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    792                     "response": {
    793                         "clientDataJSON": b64_cdata_json,
    794                         "authenticatorData": b64_adata,
    795                         "signature": b64_sig,
    796                     },
    797                     "clientExtensionResults": {},
    798                     "type": "public-key"
    799                 })
    800                 .to_string()
    801                 .as_str(),
    802             )
    803             .unwrap(),
    804         );
    805         // `null` `userHandle`.
    806         drop(
    807             serde_json::from_str::<NonDiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    808                 serde_json::json!({
    809                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    810                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    811                     "response": {
    812                         "clientDataJSON": b64_cdata_json,
    813                         "authenticatorData": b64_adata,
    814                         "signature": b64_sig,
    815                         "userHandle": null,
    816                     },
    817                     "clientExtensionResults": {},
    818                     "type": "public-key"
    819                 })
    820                 .to_string()
    821                 .as_str(),
    822             )
    823             .unwrap(),
    824         );
    825         // `null` `authenticatorAttachment`.
    826         assert!(
    827             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    828                 serde_json::json!({
    829                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    830                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    831                     "response": {
    832                         "clientDataJSON": b64_cdata_json,
    833                         "authenticatorData": b64_adata,
    834                         "signature": b64_sig,
    835                         "userHandle": b64_user,
    836                     },
    837                     "authenticatorAttachment": null,
    838                     "clientExtensionResults": {},
    839                     "type": "public-key"
    840                 })
    841                 .to_string()
    842                 .as_str()
    843             )
    844             .is_ok_and(|auth| matches!(
    845                 auth.authenticator_attachment,
    846                 AuthenticatorAttachment::None
    847             ))
    848         );
    849         // Unknown `authenticatorAttachment`.
    850         err = Error::invalid_value(
    851             Unexpected::Str("Platform"),
    852             &"'platform' or 'cross-platform'",
    853         )
    854         .to_string()
    855         .into_bytes();
    856         assert_eq!(
    857             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    858                 serde_json::json!({
    859                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    860                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    861                     "response": {
    862                         "clientDataJSON": b64_cdata_json,
    863                         "authenticatorData": b64_adata,
    864                         "signature": b64_sig,
    865                         "userHandle": b64_user,
    866                     },
    867                     "authenticatorAttachment": "Platform",
    868                     "clientExtensionResults": {},
    869                     "type": "public-key"
    870                 })
    871                 .to_string()
    872                 .as_str()
    873             )
    874             .unwrap_err()
    875             .to_string()
    876             .into_bytes()
    877             .get(..err.len()),
    878             Some(err.as_slice())
    879         );
    880         // Missing `clientDataJSON`.
    881         err = Error::missing_field("clientDataJSON")
    882             .to_string()
    883             .into_bytes();
    884         assert_eq!(
    885             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    886                 serde_json::json!({
    887                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    888                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    889                     "response": {
    890                         "authenticatorData": b64_adata,
    891                         "signature": b64_sig,
    892                         "userHandle": b64_user,
    893                     },
    894                     "clientExtensionResults": {},
    895                     "type": "public-key"
    896                 })
    897                 .to_string()
    898                 .as_str()
    899             )
    900             .unwrap_err()
    901             .to_string()
    902             .into_bytes()
    903             .get(..err.len()),
    904             Some(err.as_slice())
    905         );
    906         // `null` `clientDataJSON`.
    907         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    908             .to_string()
    909             .into_bytes();
    910         assert_eq!(
    911             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    912                 serde_json::json!({
    913                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    914                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    915                     "response": {
    916                         "clientDataJSON": null,
    917                         "authenticatorData": b64_adata,
    918                         "signature": b64_sig,
    919                         "userHandle": b64_user,
    920                     },
    921                     "clientExtensionResults": {},
    922                     "type": "public-key"
    923                 })
    924                 .to_string()
    925                 .as_str()
    926             )
    927             .unwrap_err()
    928             .to_string()
    929             .into_bytes()
    930             .get(..err.len()),
    931             Some(err.as_slice())
    932         );
    933         // Missing `response`.
    934         err = Error::missing_field("response").to_string().into_bytes();
    935         assert_eq!(
    936             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    937                 serde_json::json!({
    938                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    939                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    940                     "clientExtensionResults": {},
    941                     "type": "public-key"
    942                 })
    943                 .to_string()
    944                 .as_str()
    945             )
    946             .unwrap_err()
    947             .to_string()
    948             .into_bytes()
    949             .get(..err.len()),
    950             Some(err.as_slice())
    951         );
    952         // `null` `response`.
    953         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion")
    954             .to_string()
    955             .into_bytes();
    956         assert_eq!(
    957             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    958                 serde_json::json!({
    959                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    960                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    961                     "response": null,
    962                     "clientExtensionResults": {},
    963                     "type": "public-key"
    964                 })
    965                 .to_string()
    966                 .as_str()
    967             )
    968             .unwrap_err()
    969             .to_string()
    970             .into_bytes()
    971             .get(..err.len()),
    972             Some(err.as_slice())
    973         );
    974         // Empty `response`.
    975         err = Error::missing_field("clientDataJSON")
    976             .to_string()
    977             .into_bytes();
    978         assert_eq!(
    979             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
    980                 serde_json::json!({
    981                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    982                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    983                     "response": {},
    984                     "clientExtensionResults": {},
    985                     "type": "public-key"
    986                 })
    987                 .to_string()
    988                 .as_str()
    989             )
    990             .unwrap_err()
    991             .to_string()
    992             .into_bytes()
    993             .get(..err.len()),
    994             Some(err.as_slice())
    995         );
    996         // Missing `clientExtensionResults`.
    997         err = Error::missing_field("clientExtensionResults")
    998             .to_string()
    999             .into_bytes();
   1000         assert_eq!(
   1001             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1002                 serde_json::json!({
   1003                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1004                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1005                     "response": {
   1006                         "clientDataJSON": b64_cdata_json,
   1007                         "authenticatorData": b64_adata,
   1008                         "signature": b64_sig,
   1009                         "userHandle": b64_user,
   1010                     },
   1011                     "type": "public-key"
   1012                 })
   1013                 .to_string()
   1014                 .as_str()
   1015             )
   1016             .unwrap_err()
   1017             .to_string()
   1018             .into_bytes()
   1019             .get(..err.len()),
   1020             Some(err.as_slice())
   1021         );
   1022         // `null` `clientExtensionResults`.
   1023         err = Error::invalid_type(
   1024             Unexpected::Other("null"),
   1025             &"clientExtensionResults to be a map of allowed client extensions",
   1026         )
   1027         .to_string()
   1028         .into_bytes();
   1029         assert_eq!(
   1030             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1031                 serde_json::json!({
   1032                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1033                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1034                     "response": {
   1035                         "clientDataJSON": b64_cdata_json,
   1036                         "authenticatorData": b64_adata,
   1037                         "signature": b64_sig,
   1038                         "userHandle": b64_user,
   1039                     },
   1040                     "clientExtensionResults": null,
   1041                     "type": "public-key"
   1042                 })
   1043                 .to_string()
   1044                 .as_str()
   1045             )
   1046             .unwrap_err()
   1047             .to_string()
   1048             .into_bytes()
   1049             .get(..err.len()),
   1050             Some(err.as_slice())
   1051         );
   1052         // Missing `type`.
   1053         err = Error::missing_field("type").to_string().into_bytes();
   1054         assert_eq!(
   1055             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1056                 serde_json::json!({
   1057                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1058                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1059                     "response": {
   1060                         "clientDataJSON": b64_cdata_json,
   1061                         "authenticatorData": b64_adata,
   1062                         "signature": b64_sig,
   1063                         "userHandle": b64_user,
   1064                     },
   1065                     "clientExtensionResults": {},
   1066                 })
   1067                 .to_string()
   1068                 .as_str()
   1069             )
   1070             .unwrap_err()
   1071             .to_string()
   1072             .into_bytes()
   1073             .get(..err.len()),
   1074             Some(err.as_slice())
   1075         );
   1076         // `null` `type`.
   1077         err = Error::invalid_type(Unexpected::Other("null"), &"public-key")
   1078             .to_string()
   1079             .into_bytes();
   1080         assert_eq!(
   1081             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1082                 serde_json::json!({
   1083                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1084                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1085                     "response": {
   1086                         "clientDataJSON": b64_cdata_json,
   1087                         "authenticatorData": b64_adata,
   1088                         "signature": b64_sig,
   1089                         "userHandle": b64_user,
   1090                     },
   1091                     "clientExtensionResults": {},
   1092                     "type": null
   1093                 })
   1094                 .to_string()
   1095                 .as_str()
   1096             )
   1097             .unwrap_err()
   1098             .to_string()
   1099             .into_bytes()
   1100             .get(..err.len()),
   1101             Some(err.as_slice())
   1102         );
   1103         // Not exactly `public-type` `type`.
   1104         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
   1105             .to_string()
   1106             .into_bytes();
   1107         assert_eq!(
   1108             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1109                 serde_json::json!({
   1110                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1111                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1112                     "response": {
   1113                         "clientDataJSON": b64_cdata_json,
   1114                         "authenticatorData": b64_adata,
   1115                         "signature": b64_sig,
   1116                         "userHandle": b64_user,
   1117                     },
   1118                     "clientExtensionResults": {},
   1119                     "type": "Public-key"
   1120                 })
   1121                 .to_string()
   1122                 .as_str()
   1123             )
   1124             .unwrap_err()
   1125             .to_string()
   1126             .into_bytes()
   1127             .get(..err.len()),
   1128             Some(err.as_slice())
   1129         );
   1130         // `null`.
   1131         err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential")
   1132             .to_string()
   1133             .into_bytes();
   1134         assert_eq!(
   1135             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1136                 serde_json::json!(null).to_string().as_str()
   1137             )
   1138             .unwrap_err()
   1139             .to_string()
   1140             .into_bytes()
   1141             .get(..err.len()),
   1142             Some(err.as_slice())
   1143         );
   1144         // Empty.
   1145         err = Error::missing_field("response").to_string().into_bytes();
   1146         assert_eq!(
   1147             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1148                 serde_json::json!({}).to_string().as_str()
   1149             )
   1150             .unwrap_err()
   1151             .to_string()
   1152             .into_bytes()
   1153             .get(..err.len()),
   1154             Some(err.as_slice())
   1155         );
   1156         // Unknown field in `response`.
   1157         err = Error::unknown_field(
   1158             "foo",
   1159             [
   1160                 "clientDataJSON",
   1161                 "authenticatorData",
   1162                 "signature",
   1163                 "userHandle",
   1164             ]
   1165             .as_slice(),
   1166         )
   1167         .to_string()
   1168         .into_bytes();
   1169         assert_eq!(
   1170             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1171                 serde_json::json!({
   1172                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1173                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1174                     "response": {
   1175                         "clientDataJSON": b64_cdata_json,
   1176                         "authenticatorData": b64_adata,
   1177                         "signature": b64_sig,
   1178                         "userHandle": b64_user,
   1179                         "foo": true,
   1180                     },
   1181                     "clientExtensionResults": {},
   1182                     "type": "public-key"
   1183                 })
   1184                 .to_string()
   1185                 .as_str()
   1186             )
   1187             .unwrap_err()
   1188             .to_string()
   1189             .into_bytes()
   1190             .get(..err.len()),
   1191             Some(err.as_slice())
   1192         );
   1193         // Duplicate field in `response`.
   1194         err = Error::duplicate_field("userHandle")
   1195             .to_string()
   1196             .into_bytes();
   1197         assert_eq!(
   1198             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1199                 format!(
   1200                     "{{
   1201                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1202                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1203                        \"response\": {{
   1204                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1205                            \"authenticatorData\": \"{b64_adata}\",
   1206                            \"signature\": \"{b64_sig}\",
   1207                            \"userHandle\": \"{b64_user}\",
   1208                            \"userHandle\": \"{b64_user}\"
   1209                        }},
   1210                        \"clientExtensionResults\": {{}},
   1211                        \"type\": \"public-key\"
   1212 
   1213                      }}"
   1214                 )
   1215                 .as_str()
   1216             )
   1217             .unwrap_err()
   1218             .to_string()
   1219             .into_bytes()
   1220             .get(..err.len()),
   1221             Some(err.as_slice())
   1222         );
   1223         // Unknown field in `PublicKeyCredential`.
   1224         err = Error::unknown_field(
   1225             "foo",
   1226             [
   1227                 "id",
   1228                 "type",
   1229                 "rawId",
   1230                 "response",
   1231                 "authenticatorAttachment",
   1232                 "clientExtensionResults",
   1233             ]
   1234             .as_slice(),
   1235         )
   1236         .to_string()
   1237         .into_bytes();
   1238         assert_eq!(
   1239             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1240                 serde_json::json!({
   1241                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1242                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1243                     "response": {
   1244                         "clientDataJSON": b64_cdata_json,
   1245                         "authenticatorData": b64_adata,
   1246                         "signature": b64_sig,
   1247                         "userHandle": b64_user,
   1248                     },
   1249                     "clientExtensionResults": {},
   1250                     "type": "public-key",
   1251                     "foo": true,
   1252                 })
   1253                 .to_string()
   1254                 .as_str()
   1255             )
   1256             .unwrap_err()
   1257             .to_string()
   1258             .into_bytes()
   1259             .get(..err.len()),
   1260             Some(err.as_slice())
   1261         );
   1262         // Duplicate field in `PublicKeyCredential`.
   1263         err = Error::duplicate_field("id").to_string().into_bytes();
   1264         assert_eq!(
   1265             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1266                 format!(
   1267                     "{{
   1268                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1269                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1270                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1271                        \"response\": {{
   1272                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1273                            \"authenticatorData\": \"{b64_adata}\",
   1274                            \"signature\": \"{b64_sig}\",
   1275                            \"userHandle\": \"{b64_user}\"
   1276                        }},
   1277                        \"clientExtensionResults\": {{}},
   1278                        \"type\": \"public-key\"
   1279 
   1280                      }}"
   1281                 )
   1282                 .as_str()
   1283             )
   1284             .unwrap_err()
   1285             .to_string()
   1286             .into_bytes()
   1287             .get(..err.len()),
   1288             Some(err.as_slice())
   1289         );
   1290     }
   1291     #[expect(clippy::unwrap_used, reason = "OK in tests")]
   1292     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1293     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   1294     #[test]
   1295     fn client_extensions() {
   1296         let c_data_json = serde_json::json!({}).to_string();
   1297         let auth_data: [u8; 37] = [
   1298             // `rpIdHash`.
   1299             0,
   1300             0,
   1301             0,
   1302             0,
   1303             0,
   1304             0,
   1305             0,
   1306             0,
   1307             0,
   1308             0,
   1309             0,
   1310             0,
   1311             0,
   1312             0,
   1313             0,
   1314             0,
   1315             0,
   1316             0,
   1317             0,
   1318             0,
   1319             0,
   1320             0,
   1321             0,
   1322             0,
   1323             0,
   1324             0,
   1325             0,
   1326             0,
   1327             0,
   1328             0,
   1329             0,
   1330             0,
   1331             // `flags`.
   1332             0b0000_0101,
   1333             // `signCount`.
   1334             0,
   1335             0,
   1336             0,
   1337             0,
   1338         ];
   1339         let auth_data_len = 37;
   1340         let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes());
   1341         let b64_adata = base64url_nopad::encode(auth_data.as_slice());
   1342         let b64_sig = base64url_nopad::encode([].as_slice());
   1343         let b64_user = base64url_nopad::encode(b"\x00".as_slice());
   1344         // Base case is valid.
   1345         assert!(
   1346             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1347                 serde_json::json!({
   1348                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1349                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1350                     "response": {
   1351                         "clientDataJSON": b64_cdata_json,
   1352                         "authenticatorData": b64_adata,
   1353                         "signature": b64_sig,
   1354                         "userHandle": b64_user,
   1355                     },
   1356                     "authenticatorAttachment": "cross-platform",
   1357                     "clientExtensionResults": {},
   1358                     "type": "public-key"
   1359                 })
   1360                 .to_string()
   1361                 .as_str()
   1362             )
   1363             .is_ok_and(
   1364                 |auth| auth.response.client_data_json == c_data_json.as_bytes()
   1365                     && auth.response.authenticator_data_and_c_data_hash[..auth_data_len]
   1366                         == auth_data
   1367                     && auth.response.authenticator_data_and_c_data_hash[auth_data_len..]
   1368                         == *Sha256::digest(c_data_json.as_bytes())
   1369                     && matches!(
   1370                         auth.authenticator_attachment,
   1371                         AuthenticatorAttachment::CrossPlatform
   1372                     )
   1373             )
   1374         );
   1375         // `null` `prf`.
   1376         drop(
   1377             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1378                 serde_json::json!({
   1379                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1380                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1381                     "response": {
   1382                         "clientDataJSON": b64_cdata_json,
   1383                         "authenticatorData": b64_adata,
   1384                         "signature": b64_sig,
   1385                         "userHandle": b64_user,
   1386                     },
   1387                     "clientExtensionResults": {
   1388                         "prf": null
   1389                     },
   1390                     "type": "public-key"
   1391                 })
   1392                 .to_string()
   1393                 .as_str(),
   1394             )
   1395             .unwrap(),
   1396         );
   1397         // Unknown `clientExtensionResults`.
   1398         let mut err = Error::unknown_field("Prf", ["prf"].as_slice())
   1399             .to_string()
   1400             .into_bytes();
   1401         assert_eq!(
   1402             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1403                 serde_json::json!({
   1404                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1405                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1406                     "response": {
   1407                         "clientDataJSON": b64_cdata_json,
   1408                         "authenticatorData": b64_adata,
   1409                         "signature": b64_sig,
   1410                         "userHandle": b64_user,
   1411                     },
   1412                     "clientExtensionResults": {
   1413                         "Prf": null
   1414                     },
   1415                     "type": "public-key"
   1416                 })
   1417                 .to_string()
   1418                 .as_str()
   1419             )
   1420             .unwrap_err()
   1421             .to_string()
   1422             .into_bytes()
   1423             .get(..err.len()),
   1424             Some(err.as_slice())
   1425         );
   1426         // Duplicate field.
   1427         err = Error::duplicate_field("prf").to_string().into_bytes();
   1428         assert_eq!(
   1429             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1430                 format!(
   1431                     "{{
   1432                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1433                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1434                        \"response\": {{
   1435                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1436                            \"authenticatorData\": \"{b64_adata}\",
   1437                            \"signature\": \"{b64_sig}\",
   1438                            \"userHandle\": \"{b64_user}\"
   1439                        }},
   1440                        \"clientExtensionResults\": {{
   1441                            \"prf\": null,
   1442                            \"prf\": null
   1443                        }},
   1444                        \"type\": \"public-key\"
   1445                      }}"
   1446                 )
   1447                 .as_str()
   1448             )
   1449             .unwrap_err()
   1450             .to_string()
   1451             .into_bytes()
   1452             .get(..err.len()),
   1453             Some(err.as_slice())
   1454         );
   1455         // `null` `results`.
   1456         drop(
   1457             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1458                 serde_json::json!({
   1459                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1460                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1461                     "response": {
   1462                         "clientDataJSON": b64_cdata_json,
   1463                         "authenticatorData": b64_adata,
   1464                         "signature": b64_sig,
   1465                         "userHandle": b64_user,
   1466                     },
   1467                     "clientExtensionResults": {
   1468                         "prf": {
   1469                             "results": null,
   1470                         }
   1471                     },
   1472                     "type": "public-key"
   1473                 })
   1474                 .to_string()
   1475                 .as_str(),
   1476             )
   1477             .unwrap(),
   1478         );
   1479         // Duplicate field in `prf`.
   1480         err = Error::duplicate_field("results").to_string().into_bytes();
   1481         assert_eq!(
   1482             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1483                 format!(
   1484                     "{{
   1485                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1486                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1487                        \"response\": {{
   1488                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1489                            \"authenticatorData\": \"{b64_adata}\",
   1490                            \"signature\": \"{b64_sig}\",
   1491                            \"userHandle\": \"{b64_user}\"
   1492                        }},
   1493                        \"clientExtensionResults\": {{
   1494                            \"prf\": {{
   1495                                \"results\": null,
   1496                                \"results\": null
   1497                            }}
   1498                        }},
   1499                        \"type\": \"public-key\"
   1500                      }}"
   1501                 )
   1502                 .as_str()
   1503             )
   1504             .unwrap_err()
   1505             .to_string()
   1506             .into_bytes()
   1507             .get(..err.len()),
   1508             Some(err.as_slice())
   1509         );
   1510         // Missing `first`.
   1511         err = Error::missing_field("first").to_string().into_bytes();
   1512         assert_eq!(
   1513             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1514                 serde_json::json!({
   1515                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1516                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1517                     "response": {
   1518                         "clientDataJSON": b64_cdata_json,
   1519                         "authenticatorData": b64_adata,
   1520                         "signature": b64_sig,
   1521                         "userHandle": b64_user,
   1522                     },
   1523                     "clientExtensionResults": {
   1524                         "prf": {
   1525                             "results": {},
   1526                         }
   1527                     },
   1528                     "type": "public-key"
   1529                 })
   1530                 .to_string()
   1531                 .as_str()
   1532             )
   1533             .unwrap_err()
   1534             .to_string()
   1535             .into_bytes()
   1536             .get(..err.len()),
   1537             Some(err.as_slice())
   1538         );
   1539         // `null` `first`.
   1540         drop(
   1541             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1542                 serde_json::json!({
   1543                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1544                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1545                     "response": {
   1546                         "clientDataJSON": b64_cdata_json,
   1547                         "authenticatorData": b64_adata,
   1548                         "signature": b64_sig,
   1549                         "userHandle": b64_user,
   1550                     },
   1551                     "clientExtensionResults": {
   1552                         "prf": {
   1553                             "results": {
   1554                                 "first": null
   1555                             },
   1556                         }
   1557                     },
   1558                     "type": "public-key"
   1559                 })
   1560                 .to_string()
   1561                 .as_str(),
   1562             )
   1563             .unwrap(),
   1564         );
   1565         // `null` `second`.
   1566         drop(
   1567             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1568                 serde_json::json!({
   1569                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1570                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1571                     "response": {
   1572                         "clientDataJSON": b64_cdata_json,
   1573                         "authenticatorData": b64_adata,
   1574                         "signature": b64_sig,
   1575                         "userHandle": b64_user,
   1576                     },
   1577                     "clientExtensionResults": {
   1578                         "prf": {
   1579                             "results": {
   1580                                 "first": null,
   1581                                 "second": null
   1582                             },
   1583                         }
   1584                     },
   1585                     "type": "public-key"
   1586                 })
   1587                 .to_string()
   1588                 .as_str(),
   1589             )
   1590             .unwrap(),
   1591         );
   1592         // Non-`null` `first`.
   1593         err = Error::invalid_type(Unexpected::Option, &"null")
   1594             .to_string()
   1595             .into_bytes();
   1596         assert_eq!(
   1597             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1598                 serde_json::json!({
   1599                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1600                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1601                     "response": {
   1602                         "clientDataJSON": b64_cdata_json,
   1603                         "authenticatorData": b64_adata,
   1604                         "signature": b64_sig,
   1605                         "userHandle": b64_user,
   1606                     },
   1607                     "clientExtensionResults": {
   1608                         "prf": {
   1609                             "results": {
   1610                                 "first": ""
   1611                             },
   1612                         }
   1613                     },
   1614                     "type": "public-key"
   1615                 })
   1616                 .to_string()
   1617                 .as_str()
   1618             )
   1619             .unwrap_err()
   1620             .to_string()
   1621             .into_bytes()
   1622             .get(..err.len()),
   1623             Some(err.as_slice())
   1624         );
   1625         // Non-`null` `second`.
   1626         err = Error::invalid_type(Unexpected::Option, &"null")
   1627             .to_string()
   1628             .into_bytes();
   1629         assert_eq!(
   1630             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1631                 serde_json::json!({
   1632                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1633                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1634                     "response": {
   1635                         "clientDataJSON": b64_cdata_json,
   1636                         "authenticatorData": b64_adata,
   1637                         "signature": b64_sig,
   1638                         "userHandle": b64_user,
   1639                     },
   1640                     "clientExtensionResults": {
   1641                         "prf": {
   1642                             "results": {
   1643                                 "first": null,
   1644                                 "second": ""
   1645                             },
   1646                         }
   1647                     },
   1648                     "type": "public-key"
   1649                 })
   1650                 .to_string()
   1651                 .as_str()
   1652             )
   1653             .unwrap_err()
   1654             .to_string()
   1655             .into_bytes()
   1656             .get(..err.len()),
   1657             Some(err.as_slice())
   1658         );
   1659         // Unknown `prf` field.
   1660         err = Error::unknown_field("enabled", ["results"].as_slice())
   1661             .to_string()
   1662             .into_bytes();
   1663         assert_eq!(
   1664             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1665                 serde_json::json!({
   1666                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1667                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1668                     "response": {
   1669                         "clientDataJSON": b64_cdata_json,
   1670                         "authenticatorData": b64_adata,
   1671                         "signature": b64_sig,
   1672                         "userHandle": b64_user,
   1673                     },
   1674                     "clientExtensionResults": {
   1675                         "prf": {
   1676                             "enabled": true,
   1677                             "results": null
   1678                         }
   1679                     },
   1680                     "type": "public-key"
   1681                 })
   1682                 .to_string()
   1683                 .as_str()
   1684             )
   1685             .unwrap_err()
   1686             .to_string()
   1687             .into_bytes()
   1688             .get(..err.len()),
   1689             Some(err.as_slice())
   1690         );
   1691         // Unknown `results` field.
   1692         err = Error::unknown_field("Second", ["first", "second"].as_slice())
   1693             .to_string()
   1694             .into_bytes();
   1695         assert_eq!(
   1696             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1697                 serde_json::json!({
   1698                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1699                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1700                     "response": {
   1701                         "clientDataJSON": b64_cdata_json,
   1702                         "authenticatorData": b64_adata,
   1703                         "signature": b64_sig,
   1704                         "userHandle": b64_user,
   1705                     },
   1706                     "clientExtensionResults": {
   1707                         "prf": {
   1708                             "results": {
   1709                                 "first": null,
   1710                                 "Second": null
   1711                             }
   1712                         }
   1713                     },
   1714                     "type": "public-key"
   1715                 })
   1716                 .to_string()
   1717                 .as_str()
   1718             )
   1719             .unwrap_err()
   1720             .to_string()
   1721             .into_bytes()
   1722             .get(..err.len()),
   1723             Some(err.as_slice())
   1724         );
   1725         // Duplicate field in `results`.
   1726         err = Error::duplicate_field("first").to_string().into_bytes();
   1727         assert_eq!(
   1728             serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>(
   1729                 format!(
   1730                     "{{
   1731                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1732                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1733                        \"response\": {{
   1734                            \"clientDataJSON\": \"{b64_cdata_json}\",
   1735                            \"authenticatorData\": \"{b64_adata}\",
   1736                            \"signature\": \"{b64_sig}\",
   1737                            \"userHandle\": \"{b64_user}\"
   1738                        }},
   1739                        \"clientExtensionResults\": {{
   1740                            \"prf\": {{
   1741                                \"results\": {{
   1742                                    \"first\": null,
   1743                                    \"first\": null
   1744                                }}
   1745                            }}
   1746                        }},
   1747                        \"type\": \"public-key\"
   1748                      }}"
   1749                 )
   1750                 .as_str()
   1751             )
   1752             .unwrap_err()
   1753             .to_string()
   1754             .into_bytes()
   1755             .get(..err.len()),
   1756             Some(err.as_slice())
   1757         );
   1758     }
   1759 }