webauthn_rp

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

ser.rs (65048B)


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