webauthn_rp

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

ser.rs (61522B)


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