webauthn_rp

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

ser.rs (65014B)


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