webauthn_rp

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

ser_relaxed.rs (45650B)


      1 #[cfg(doc)]
      2 use super::super::Challenge;
      3 use super::{
      4     super::{
      5         auth::ser::{
      6             AuthenticatorAssertionVisitor, ClientExtensionsOutputs, ClientExtensionsOutputsVisitor,
      7             AUTH_ASSERT_FIELDS, EXT_FIELDS,
      8         },
      9         ser::{AuthenticationExtensionsPrfOutputsHelper, ClientExtensions, PublicKeyCredential},
     10         ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed,
     11     },
     12     Authentication, AuthenticatorAssertion,
     13 };
     14 use core::marker::PhantomData;
     15 #[cfg(doc)]
     16 use data_encoding::BASE64URL_NOPAD;
     17 use serde::de::{Deserialize, Deserializer};
     18 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation.
     19 struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs);
     20 impl ClientExtensions for ClientExtensionsOutputsRelaxed {
     21     fn empty() -> Self {
     22         Self(ClientExtensionsOutputs::empty())
     23     }
     24 }
     25 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed {
     26     /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored.
     27     ///
     28     /// Note that duplicate keys are still forbidden.
     29     #[inline]
     30     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     31     where
     32         D: Deserializer<'de>,
     33     {
     34         deserializer
     35             .deserialize_struct(
     36                 "ClientExtensionsOutputsRelaxed",
     37                 EXT_FIELDS,
     38                 ClientExtensionsOutputsVisitor::<
     39                     true,
     40                     AuthenticationExtensionsPrfOutputsHelper<
     41                         true,
     42                         false,
     43                         AuthenticationExtensionsPrfValuesRelaxed,
     44                     >,
     45                 >(PhantomData),
     46             )
     47             .map(Self)
     48     }
     49 }
     50 /// `newtype` around `AuthenticatorAssertion` with a "relaxed" [`Self::deserialize`] implementation.
     51 #[derive(Debug)]
     52 pub struct AuthenticatorAssertionRelaxed(pub AuthenticatorAssertion);
     53 impl<'de> Deserialize<'de> for AuthenticatorAssertionRelaxed {
     54     /// Same as [`AuthenticatorAssertion::deserialize`] except unknown keys are ignored.
     55     ///
     56     /// Note that duplicate keys are still forbidden.
     57     #[inline]
     58     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     59     where
     60         D: Deserializer<'de>,
     61     {
     62         deserializer
     63             .deserialize_struct(
     64                 "AuthenticatorAssertionRelaxed",
     65                 AUTH_ASSERT_FIELDS,
     66                 AuthenticatorAssertionVisitor::<true>,
     67             )
     68             .map(Self)
     69     }
     70 }
     71 /// `newtype` around `Authentication` with a "relaxed" [`Self::deserialize`] implementation.
     72 #[derive(Debug)]
     73 pub struct AuthenticationRelaxed(pub Authentication);
     74 impl<'de> Deserialize<'de> for AuthenticationRelaxed {
     75     /// Same as [`Authentication::deserialize`] except unknown keys are ignored;
     76     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized
     77     /// via [`AuthenticatorAssertionRelaxed::deserialize`];
     78     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults)
     79     /// is deserialized such unknown keys are ignored but duplicate keys are forbidden,
     80     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` or an
     81     /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs)
     82     /// such that unknown keys are allowed but duplicate keys are forbidden,
     83     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled)
     84     /// is forbidden (including being assigned `null`),
     85     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
     86     /// be `null`, or be an
     87     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
     88     /// where unknown keys are ignored, duplicate keys are forbidden,
     89     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but
     90     /// if it exists it must be `null`, and
     91     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
     92     /// must be `null` if so; and only
     93     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and `response` are required. For
     94     /// the other fields, they are allowed to not exist or be `null`.
     95     ///
     96     /// Note that duplicate keys are still forbidden, and data matching still applies when applicable.
     97     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
     98     #[inline]
     99     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    100     where
    101         D: Deserializer<'de>,
    102     {
    103         PublicKeyCredential::<
    104             true,
    105             false,
    106             AuthenticatorAssertionRelaxed,
    107             ClientExtensionsOutputsRelaxed,
    108         >::deserialize(deserializer)
    109         .map(|cred| {
    110             Self(Authentication {
    111                 raw_id: cred.id.unwrap_or_else(|| {
    112                     unreachable!("there is a bug in PublicKeyCredential::deserialize")
    113                 }),
    114                 response: cred.response.0,
    115                 authenticator_attachment: cred.authenticator_attachment,
    116             })
    117         })
    118     }
    119 }
    120 #[cfg(test)]
    121 mod tests {
    122     use super::{super::AuthenticatorAttachment, AuthenticationRelaxed};
    123     use data_encoding::BASE64URL_NOPAD;
    124     use rsa::sha2::{Digest as _, Sha256};
    125     use serde::de::{Error as _, Unexpected};
    126     use serde_json::Error;
    127     #[test]
    128     fn eddsa_authentication_deserialize_data_mismatch() {
    129         let c_data_json = serde_json::json!({}).to_string();
    130         let auth_data = [
    131             // `rpIdHash`.
    132             0,
    133             0,
    134             0,
    135             0,
    136             0,
    137             0,
    138             0,
    139             0,
    140             0,
    141             0,
    142             0,
    143             0,
    144             0,
    145             0,
    146             0,
    147             0,
    148             0,
    149             0,
    150             0,
    151             0,
    152             0,
    153             0,
    154             0,
    155             0,
    156             0,
    157             0,
    158             0,
    159             0,
    160             0,
    161             0,
    162             0,
    163             0,
    164             // `flags`.
    165             0b0000_0101,
    166             // `signCount`.
    167             0,
    168             0,
    169             0,
    170             0,
    171         ];
    172         let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
    173         let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
    174         let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
    175         let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
    176         // Base case is valid.
    177         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    178             serde_json::json!({
    179                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    180                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    181                 "response": {
    182                     "clientDataJSON": b64_cdata,
    183                     "authenticatorData": b64_adata,
    184                     "signature": b64_sig,
    185                     "userHandle": b64_user,
    186                 },
    187                 "authenticatorAttachment": "cross-platform",
    188                 "clientExtensionResults": {},
    189                 "type": "public-key"
    190             })
    191             .to_string()
    192             .as_str()
    193         )
    194         .map_or(false, |auth| auth.0.response.client_data_json
    195             == c_data_json.as_bytes()
    196             && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
    197             && auth.0.response.authenticator_data_and_c_data_hash[37..]
    198                 == *Sha256::digest(c_data_json.as_bytes()).as_slice()
    199             && matches!(
    200                 auth.0.authenticator_attachment,
    201                 AuthenticatorAttachment::CrossPlatform
    202             )));
    203         // `id` and `rawId` mismatch.
    204         let mut err = Error::invalid_value(
    205             Unexpected::Bytes(
    206                 BASE64URL_NOPAD
    207                     .decode("ABABABABABABABABABABAA".as_bytes())
    208                     .unwrap()
    209                     .as_slice(),
    210             ),
    211             &format!("id and rawId to match: CredentialId({:?})", [0; 16]).as_str(),
    212         )
    213         .to_string()
    214         .into_bytes();
    215         assert_eq!(
    216             serde_json::from_str::<AuthenticationRelaxed>(
    217                 serde_json::json!({
    218                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    219                     "rawId": "ABABABABABABABABABABAA",
    220                     "response": {
    221                         "clientDataJSON": b64_cdata,
    222                         "authenticatorData": b64_adata,
    223                         "signature": b64_sig,
    224                         "userHandle": b64_user,
    225                     },
    226                     "authenticatorAttachment": "cross-platform",
    227                     "clientExtensionResults": {},
    228                     "type": "public-key"
    229                 })
    230                 .to_string()
    231                 .as_str()
    232             )
    233             .unwrap_err()
    234             .to_string()
    235             .into_bytes()[..err.len()],
    236             err
    237         );
    238         // missing `id`.
    239         err = Error::missing_field("id").to_string().into_bytes();
    240         assert_eq!(
    241             serde_json::from_str::<AuthenticationRelaxed>(
    242                 serde_json::json!({
    243                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    244                     "response": {
    245                         "clientDataJSON": b64_cdata,
    246                         "authenticatorData": b64_adata,
    247                         "signature": b64_sig,
    248                         "userHandle": b64_user,
    249                     },
    250                     "authenticatorAttachment": "cross-platform",
    251                     "clientExtensionResults": {},
    252                     "type": "public-key"
    253                 })
    254                 .to_string()
    255                 .as_str()
    256             )
    257             .unwrap_err()
    258             .to_string()
    259             .into_bytes()[..err.len()],
    260             err
    261         );
    262         // `null` `id`.
    263         err = Error::invalid_type(Unexpected::Other("null"), &"id")
    264             .to_string()
    265             .into_bytes();
    266         assert_eq!(
    267             serde_json::from_str::<AuthenticationRelaxed>(
    268                 serde_json::json!({
    269                     "id": null,
    270                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    271                     "response": {
    272                         "clientDataJSON": b64_cdata,
    273                         "authenticatorData": b64_adata,
    274                         "signature": b64_sig,
    275                         "userHandle": b64_user,
    276                     },
    277                     "clientExtensionResults": {},
    278                     "type": "public-key"
    279                 })
    280                 .to_string()
    281                 .as_str()
    282             )
    283             .unwrap_err()
    284             .to_string()
    285             .into_bytes()[..err.len()],
    286             err
    287         );
    288         // missing `rawId`.
    289         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    290             serde_json::json!({
    291                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    292                 "response": {
    293                     "clientDataJSON": b64_cdata,
    294                     "authenticatorData": b64_adata,
    295                     "signature": b64_sig,
    296                     "userHandle": b64_user,
    297                 },
    298                 "clientExtensionResults": {},
    299                 "type": "public-key"
    300             })
    301             .to_string()
    302             .as_str()
    303         )
    304         .is_ok());
    305         // `null` `rawId`.
    306         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    307             serde_json::json!({
    308                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    309                 "rawId": null,
    310                 "response": {
    311                     "clientDataJSON": b64_cdata,
    312                     "authenticatorData": b64_adata,
    313                     "signature": b64_sig,
    314                     "userHandle": b64_user,
    315                 },
    316                 "clientExtensionResults": {},
    317                 "type": "public-key"
    318             })
    319             .to_string()
    320             .as_str()
    321         )
    322         .is_ok());
    323         // Missing `authenticatorData`.
    324         err = Error::missing_field("authenticatorData")
    325             .to_string()
    326             .into_bytes();
    327         assert_eq!(
    328             serde_json::from_str::<AuthenticationRelaxed>(
    329                 serde_json::json!({
    330                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    331                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    332                     "response": {
    333                         "clientDataJSON": b64_cdata,
    334                         "signature": b64_sig,
    335                         "userHandle": b64_user,
    336                     },
    337                     "clientExtensionResults": {},
    338                     "type": "public-key"
    339                 })
    340                 .to_string()
    341                 .as_str()
    342             )
    343             .unwrap_err()
    344             .to_string()
    345             .into_bytes()[..err.len()],
    346             err
    347         );
    348         // `null` `authenticatorData`.
    349         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData")
    350             .to_string()
    351             .into_bytes();
    352         assert_eq!(
    353             serde_json::from_str::<AuthenticationRelaxed>(
    354                 serde_json::json!({
    355                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    356                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    357                     "response": {
    358                         "clientDataJSON": b64_cdata,
    359                         "authenticatorData": null,
    360                         "signature": b64_sig,
    361                         "userHandle": b64_user,
    362                     },
    363                     "clientExtensionResults": {},
    364                     "type": "public-key"
    365                 })
    366                 .to_string()
    367                 .as_str()
    368             )
    369             .unwrap_err()
    370             .to_string()
    371             .into_bytes()[..err.len()],
    372             err
    373         );
    374         // Missing `signature`.
    375         err = Error::missing_field("signature").to_string().into_bytes();
    376         assert_eq!(
    377             serde_json::from_str::<AuthenticationRelaxed>(
    378                 serde_json::json!({
    379                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    380                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    381                     "response": {
    382                         "clientDataJSON": b64_cdata,
    383                         "authenticatorData": b64_adata,
    384                         "userHandle": b64_user,
    385                     },
    386                     "clientExtensionResults": {},
    387                     "type": "public-key"
    388                 })
    389                 .to_string()
    390                 .as_str()
    391             )
    392             .unwrap_err()
    393             .to_string()
    394             .into_bytes()[..err.len()],
    395             err
    396         );
    397         // `null` `signature`.
    398         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    399             .to_string()
    400             .into_bytes();
    401         assert_eq!(
    402             serde_json::from_str::<AuthenticationRelaxed>(
    403                 serde_json::json!({
    404                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    405                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    406                     "response": {
    407                         "clientDataJSON": b64_cdata,
    408                         "authenticatorData": b64_adata,
    409                         "signature": null,
    410                         "userHandle": b64_user,
    411                     },
    412                     "clientExtensionResults": {},
    413                     "type": "public-key"
    414                 })
    415                 .to_string()
    416                 .as_str()
    417             )
    418             .unwrap_err()
    419             .to_string()
    420             .into_bytes()[..err.len()],
    421             err
    422         );
    423         // Missing `userHandle`.
    424         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    425             serde_json::json!({
    426                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    427                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    428                 "response": {
    429                     "clientDataJSON": b64_cdata,
    430                     "authenticatorData": b64_adata,
    431                     "signature": b64_sig,
    432                 },
    433                 "clientExtensionResults": {},
    434                 "type": "public-key"
    435             })
    436             .to_string()
    437             .as_str()
    438         )
    439         .is_ok());
    440         // `null` `userHandle`.
    441         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    442             serde_json::json!({
    443                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    444                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    445                 "response": {
    446                     "clientDataJSON": b64_cdata,
    447                     "authenticatorData": b64_adata,
    448                     "signature": b64_sig,
    449                     "userHandle": null,
    450                 },
    451                 "clientExtensionResults": {},
    452                 "type": "public-key"
    453             })
    454             .to_string()
    455             .as_str()
    456         )
    457         .is_ok());
    458         // `null` `authenticatorAttachment`.
    459         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    460             serde_json::json!({
    461                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    462                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    463                 "response": {
    464                     "clientDataJSON": b64_cdata,
    465                     "authenticatorData": b64_adata,
    466                     "signature": b64_sig,
    467                     "userHandle": b64_user,
    468                 },
    469                 "authenticatorAttachment": null,
    470                 "clientExtensionResults": {},
    471                 "type": "public-key"
    472             })
    473             .to_string()
    474             .as_str()
    475         )
    476         .map_or(false, |auth| matches!(
    477             auth.0.authenticator_attachment,
    478             AuthenticatorAttachment::None
    479         )));
    480         // Unknown `authenticatorAttachment`.
    481         err = Error::invalid_value(
    482             Unexpected::Str("Platform"),
    483             &"'platform' or 'cross-platform'",
    484         )
    485         .to_string()
    486         .into_bytes();
    487         assert_eq!(
    488             serde_json::from_str::<AuthenticationRelaxed>(
    489                 serde_json::json!({
    490                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    491                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    492                     "response": {
    493                         "clientDataJSON": b64_cdata,
    494                         "authenticatorData": b64_adata,
    495                         "signature": b64_sig,
    496                         "userHandle": b64_user,
    497                     },
    498                     "authenticatorAttachment": "Platform",
    499                     "clientExtensionResults": {},
    500                     "type": "public-key"
    501                 })
    502                 .to_string()
    503                 .as_str()
    504             )
    505             .unwrap_err()
    506             .to_string()
    507             .into_bytes()[..err.len()],
    508             err
    509         );
    510         // Missing `clientDataJSON`.
    511         err = Error::missing_field("clientDataJSON")
    512             .to_string()
    513             .into_bytes();
    514         assert_eq!(
    515             serde_json::from_str::<AuthenticationRelaxed>(
    516                 serde_json::json!({
    517                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    518                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    519                     "response": {
    520                         "authenticatorData": b64_adata,
    521                         "signature": b64_sig,
    522                         "userHandle": b64_user,
    523                     },
    524                     "clientExtensionResults": {},
    525                     "type": "public-key"
    526                 })
    527                 .to_string()
    528                 .as_str()
    529             )
    530             .unwrap_err()
    531             .to_string()
    532             .into_bytes()[..err.len()],
    533             err
    534         );
    535         // `null` `clientDataJSON`.
    536         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    537             .to_string()
    538             .into_bytes();
    539         assert_eq!(
    540             serde_json::from_str::<AuthenticationRelaxed>(
    541                 serde_json::json!({
    542                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    543                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    544                     "response": {
    545                         "clientDataJSON": null,
    546                         "authenticatorData": b64_adata,
    547                         "signature": b64_sig,
    548                         "userHandle": b64_user,
    549                     },
    550                     "clientExtensionResults": {},
    551                     "type": "public-key"
    552                 })
    553                 .to_string()
    554                 .as_str()
    555             )
    556             .unwrap_err()
    557             .to_string()
    558             .into_bytes()[..err.len()],
    559             err
    560         );
    561         // Missing `response`.
    562         err = Error::missing_field("response").to_string().into_bytes();
    563         assert_eq!(
    564             serde_json::from_str::<AuthenticationRelaxed>(
    565                 serde_json::json!({
    566                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    567                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    568                     "clientExtensionResults": {},
    569                     "type": "public-key"
    570                 })
    571                 .to_string()
    572                 .as_str()
    573             )
    574             .unwrap_err()
    575             .to_string()
    576             .into_bytes()[..err.len()],
    577             err
    578         );
    579         // `null` `response`.
    580         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion")
    581             .to_string()
    582             .into_bytes();
    583         assert_eq!(
    584             serde_json::from_str::<AuthenticationRelaxed>(
    585                 serde_json::json!({
    586                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    587                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    588                     "response": null,
    589                     "clientExtensionResults": {},
    590                     "type": "public-key"
    591                 })
    592                 .to_string()
    593                 .as_str()
    594             )
    595             .unwrap_err()
    596             .to_string()
    597             .into_bytes()[..err.len()],
    598             err
    599         );
    600         // Empty `response`.
    601         err = Error::missing_field("clientDataJSON")
    602             .to_string()
    603             .into_bytes();
    604         assert_eq!(
    605             serde_json::from_str::<AuthenticationRelaxed>(
    606                 serde_json::json!({
    607                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    608                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    609                     "response": {},
    610                     "clientExtensionResults": {},
    611                     "type": "public-key"
    612                 })
    613                 .to_string()
    614                 .as_str()
    615             )
    616             .unwrap_err()
    617             .to_string()
    618             .into_bytes()[..err.len()],
    619             err
    620         );
    621         // Missing `clientExtensionResults`.
    622         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    623             serde_json::json!({
    624                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    625                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    626                 "response": {
    627                     "clientDataJSON": b64_cdata,
    628                     "authenticatorData": b64_adata,
    629                     "signature": b64_sig,
    630                     "userHandle": b64_user,
    631                 },
    632                 "type": "public-key"
    633             })
    634             .to_string()
    635             .as_str()
    636         )
    637         .is_ok());
    638         // `null` `clientExtensionResults`.
    639         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    640             serde_json::json!({
    641                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    642                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    643                 "response": {
    644                     "clientDataJSON": b64_cdata,
    645                     "authenticatorData": b64_adata,
    646                     "signature": b64_sig,
    647                     "userHandle": b64_user,
    648                 },
    649                 "clientExtensionResults": null,
    650                 "type": "public-key"
    651             })
    652             .to_string()
    653             .as_str()
    654         )
    655         .is_ok());
    656         // Missing `type`.
    657         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    658             serde_json::json!({
    659                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    660                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    661                 "response": {
    662                     "clientDataJSON": b64_cdata,
    663                     "authenticatorData": b64_adata,
    664                     "signature": b64_sig,
    665                     "userHandle": b64_user,
    666                 },
    667                 "clientExtensionResults": {},
    668             })
    669             .to_string()
    670             .as_str()
    671         )
    672         .is_ok());
    673         // `null` `type`.
    674         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    675             serde_json::json!({
    676                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    677                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    678                 "response": {
    679                     "clientDataJSON": b64_cdata,
    680                     "authenticatorData": b64_adata,
    681                     "signature": b64_sig,
    682                     "userHandle": b64_user,
    683                 },
    684                 "clientExtensionResults": {},
    685                 "type": null
    686             })
    687             .to_string()
    688             .as_str()
    689         )
    690         .is_ok());
    691         // Not exactly `public-type` `type`.
    692         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
    693             .to_string()
    694             .into_bytes();
    695         assert_eq!(
    696             serde_json::from_str::<AuthenticationRelaxed>(
    697                 serde_json::json!({
    698                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    699                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    700                     "response": {
    701                         "clientDataJSON": b64_cdata,
    702                         "authenticatorData": b64_adata,
    703                         "signature": b64_sig,
    704                         "userHandle": b64_user,
    705                     },
    706                     "clientExtensionResults": {},
    707                     "type": "Public-key"
    708                 })
    709                 .to_string()
    710                 .as_str()
    711             )
    712             .unwrap_err()
    713             .to_string()
    714             .into_bytes()[..err.len()],
    715             err
    716         );
    717         // `null`.
    718         err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential")
    719             .to_string()
    720             .into_bytes();
    721         assert_eq!(
    722             serde_json::from_str::<AuthenticationRelaxed>(
    723                 serde_json::json!(null).to_string().as_str()
    724             )
    725             .unwrap_err()
    726             .to_string()
    727             .into_bytes()[..err.len()],
    728             err
    729         );
    730         // Empty.
    731         err = Error::missing_field("response").to_string().into_bytes();
    732         assert_eq!(
    733             serde_json::from_str::<AuthenticationRelaxed>(
    734                 serde_json::json!({}).to_string().as_str()
    735             )
    736             .unwrap_err()
    737             .to_string()
    738             .into_bytes()[..err.len()],
    739             err
    740         );
    741         // Unknown field in `response`.
    742         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    743             serde_json::json!({
    744                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    745                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    746                 "response": {
    747                     "clientDataJSON": b64_cdata,
    748                     "authenticatorData": b64_adata,
    749                     "signature": b64_sig,
    750                     "userHandle": b64_user,
    751                     "foo": true,
    752                 },
    753                 "clientExtensionResults": {},
    754                 "type": "public-key"
    755             })
    756             .to_string()
    757             .as_str()
    758         )
    759         .is_ok());
    760         // Duplicate field in `response`.
    761         err = Error::duplicate_field("userHandle")
    762             .to_string()
    763             .into_bytes();
    764         assert_eq!(
    765             serde_json::from_str::<AuthenticationRelaxed>(
    766                 format!(
    767                     "{{
    768                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    769                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    770                        \"response\": {{
    771                            \"clientDataJSON\": \"{b64_cdata}\",
    772                            \"authenticatorData\": \"{b64_adata}\",
    773                            \"signature\": \"{b64_sig}\",
    774                            \"userHandle\": \"{b64_user}\",
    775                            \"userHandle\": \"{b64_user}\"
    776                        }},
    777                        \"clientExtensionResults\": {{}},
    778                        \"type\": \"public-key\"
    779 
    780                      }}"
    781                 )
    782                 .as_str()
    783             )
    784             .unwrap_err()
    785             .to_string()
    786             .into_bytes()[..err.len()],
    787             err
    788         );
    789         // Unknown field in `PublicKeyCredential`.
    790         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    791             serde_json::json!({
    792                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    793                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    794                 "response": {
    795                     "clientDataJSON": b64_cdata,
    796                     "authenticatorData": b64_adata,
    797                     "signature": b64_sig,
    798                     "userHandle": b64_user,
    799                 },
    800                 "clientExtensionResults": {},
    801                 "type": "public-key",
    802                 "foo": true,
    803             })
    804             .to_string()
    805             .as_str()
    806         )
    807         .is_ok());
    808         // Duplicate field in `PublicKeyCredential`.
    809         err = Error::duplicate_field("id").to_string().into_bytes();
    810         assert_eq!(
    811             serde_json::from_str::<AuthenticationRelaxed>(
    812                 format!(
    813                     "{{
    814                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    815                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    816                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    817                        \"response\": {{
    818                            \"clientDataJSON\": \"{b64_cdata}\",
    819                            \"authenticatorData\": \"{b64_adata}\",
    820                            \"signature\": \"{b64_sig}\",
    821                            \"userHandle\": \"{b64_user}\"
    822                        }},
    823                        \"clientExtensionResults\": {{}},
    824                        \"type\": \"public-key\"
    825 
    826                      }}"
    827                 )
    828                 .as_str()
    829             )
    830             .unwrap_err()
    831             .to_string()
    832             .into_bytes()[..err.len()],
    833             err
    834         );
    835     }
    836     #[test]
    837     fn client_extensions() {
    838         let c_data_json = serde_json::json!({}).to_string();
    839         let auth_data = [
    840             // `rpIdHash`.
    841             0,
    842             0,
    843             0,
    844             0,
    845             0,
    846             0,
    847             0,
    848             0,
    849             0,
    850             0,
    851             0,
    852             0,
    853             0,
    854             0,
    855             0,
    856             0,
    857             0,
    858             0,
    859             0,
    860             0,
    861             0,
    862             0,
    863             0,
    864             0,
    865             0,
    866             0,
    867             0,
    868             0,
    869             0,
    870             0,
    871             0,
    872             0,
    873             // `flags`.
    874             0b0000_0101,
    875             // `signCount`.
    876             0,
    877             0,
    878             0,
    879             0,
    880         ];
    881         let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
    882         let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
    883         let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
    884         let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
    885         // Base case is valid.
    886         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    887             serde_json::json!({
    888                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    889                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    890                 "response": {
    891                     "clientDataJSON": b64_cdata,
    892                     "authenticatorData": b64_adata,
    893                     "signature": b64_sig,
    894                     "userHandle": b64_user,
    895                 },
    896                 "authenticatorAttachment": "cross-platform",
    897                 "clientExtensionResults": {},
    898                 "type": "public-key"
    899             })
    900             .to_string()
    901             .as_str()
    902         )
    903         .map_or(false, |auth| auth.0.response.client_data_json
    904             == c_data_json.as_bytes()
    905             && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
    906             && auth.0.response.authenticator_data_and_c_data_hash[37..]
    907                 == *Sha256::digest(c_data_json.as_bytes()).as_slice()
    908             && matches!(
    909                 auth.0.authenticator_attachment,
    910                 AuthenticatorAttachment::CrossPlatform
    911             )));
    912         // `null` `prf`.
    913         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    914             serde_json::json!({
    915                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    916                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    917                 "response": {
    918                     "clientDataJSON": b64_cdata,
    919                     "authenticatorData": b64_adata,
    920                     "signature": b64_sig,
    921                     "userHandle": b64_user,
    922                 },
    923                 "clientExtensionResults": {
    924                     "prf": null
    925                 },
    926                 "type": "public-key"
    927             })
    928             .to_string()
    929             .as_str()
    930         )
    931         .is_ok());
    932         // Unknown `clientExtensionResults`.
    933         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    934             serde_json::json!({
    935                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    936                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    937                 "response": {
    938                     "clientDataJSON": b64_cdata,
    939                     "authenticatorData": b64_adata,
    940                     "signature": b64_sig,
    941                     "userHandle": b64_user,
    942                 },
    943                 "clientExtensionResults": {
    944                     "Prf": null
    945                 },
    946                 "type": "public-key"
    947             })
    948             .to_string()
    949             .as_str()
    950         )
    951         .is_ok());
    952         // Duplicate field.
    953         let mut err = Error::duplicate_field("prf").to_string().into_bytes();
    954         assert_eq!(
    955             serde_json::from_str::<AuthenticationRelaxed>(
    956                 format!(
    957                     "{{
    958                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    959                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    960                        \"response\": {{
    961                            \"clientDataJSON\": \"{b64_cdata}\",
    962                            \"authenticatorData\": \"{b64_adata}\",
    963                            \"signature\": \"{b64_sig}\",
    964                            \"userHandle\": \"{b64_user}\"
    965                        }},
    966                        \"clientExtensionResults\": {{
    967                            \"prf\": null,
    968                            \"prf\": null
    969                        }},
    970                        \"type\": \"public-key\"
    971                      }}"
    972                 )
    973                 .as_str()
    974             )
    975             .unwrap_err()
    976             .to_string()
    977             .into_bytes()[..err.len()],
    978             err
    979         );
    980         // `null` `results`.
    981         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    982             serde_json::json!({
    983                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    984                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    985                 "response": {
    986                     "clientDataJSON": b64_cdata,
    987                     "authenticatorData": b64_adata,
    988                     "signature": b64_sig,
    989                     "userHandle": b64_user,
    990                 },
    991                 "clientExtensionResults": {
    992                     "prf": {
    993                         "results": null,
    994                     }
    995                 },
    996                 "type": "public-key"
    997             })
    998             .to_string()
    999             .as_str()
   1000         )
   1001         .is_ok());
   1002         // Duplicate field in `prf`.
   1003         err = Error::duplicate_field("results").to_string().into_bytes();
   1004         assert_eq!(
   1005             serde_json::from_str::<AuthenticationRelaxed>(
   1006                 format!(
   1007                     "{{
   1008                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1009                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1010                        \"response\": {{
   1011                            \"clientDataJSON\": \"{b64_cdata}\",
   1012                            \"authenticatorData\": \"{b64_adata}\",
   1013                            \"signature\": \"{b64_sig}\",
   1014                            \"userHandle\": \"{b64_user}\"
   1015                        }},
   1016                        \"clientExtensionResults\": {{
   1017                            \"prf\": {{
   1018                                \"results\": null,
   1019                                \"results\": null
   1020                            }}
   1021                        }},
   1022                        \"type\": \"public-key\"
   1023                      }}"
   1024                 )
   1025                 .as_str()
   1026             )
   1027             .unwrap_err()
   1028             .to_string()
   1029             .into_bytes()[..err.len()],
   1030             err
   1031         );
   1032         // Missing `first`.
   1033         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1034             serde_json::json!({
   1035                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1036                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1037                 "response": {
   1038                     "clientDataJSON": b64_cdata,
   1039                     "authenticatorData": b64_adata,
   1040                     "signature": b64_sig,
   1041                     "userHandle": b64_user,
   1042                 },
   1043                 "clientExtensionResults": {
   1044                     "prf": {
   1045                         "results": {},
   1046                     }
   1047                 },
   1048                 "type": "public-key"
   1049             })
   1050             .to_string()
   1051             .as_str()
   1052         )
   1053         .is_ok());
   1054         // `null` `first`.
   1055         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1056             serde_json::json!({
   1057                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1058                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1059                 "response": {
   1060                     "clientDataJSON": b64_cdata,
   1061                     "authenticatorData": b64_adata,
   1062                     "signature": b64_sig,
   1063                     "userHandle": b64_user,
   1064                 },
   1065                 "clientExtensionResults": {
   1066                     "prf": {
   1067                         "results": {
   1068                             "first": null
   1069                         },
   1070                     }
   1071                 },
   1072                 "type": "public-key"
   1073             })
   1074             .to_string()
   1075             .as_str()
   1076         )
   1077         .is_ok());
   1078         // `null` `second`.
   1079         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1080             serde_json::json!({
   1081                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1082                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1083                 "response": {
   1084                     "clientDataJSON": b64_cdata,
   1085                     "authenticatorData": b64_adata,
   1086                     "signature": b64_sig,
   1087                     "userHandle": b64_user,
   1088                 },
   1089                 "clientExtensionResults": {
   1090                     "prf": {
   1091                         "results": {
   1092                             "first": null,
   1093                             "second": null
   1094                         },
   1095                     }
   1096                 },
   1097                 "type": "public-key"
   1098             })
   1099             .to_string()
   1100             .as_str()
   1101         )
   1102         .is_ok());
   1103         // Non-`null` `first`.
   1104         err = Error::invalid_type(Unexpected::Option, &"null")
   1105             .to_string()
   1106             .into_bytes();
   1107         assert_eq!(
   1108             serde_json::from_str::<AuthenticationRelaxed>(
   1109                 serde_json::json!({
   1110                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1111                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1112                     "response": {
   1113                         "clientDataJSON": b64_cdata,
   1114                         "authenticatorData": b64_adata,
   1115                         "signature": b64_sig,
   1116                         "userHandle": b64_user,
   1117                     },
   1118                     "clientExtensionResults": {
   1119                         "prf": {
   1120                             "results": {
   1121                                 "first": ""
   1122                             },
   1123                         }
   1124                     },
   1125                     "type": "public-key"
   1126                 })
   1127                 .to_string()
   1128                 .as_str()
   1129             )
   1130             .unwrap_err()
   1131             .to_string()
   1132             .into_bytes()[..err.len()],
   1133             err
   1134         );
   1135         // Non-`null` `second`.
   1136         err = Error::invalid_type(Unexpected::Option, &"null")
   1137             .to_string()
   1138             .into_bytes();
   1139         assert_eq!(
   1140             serde_json::from_str::<AuthenticationRelaxed>(
   1141                 serde_json::json!({
   1142                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1143                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1144                     "response": {
   1145                         "clientDataJSON": b64_cdata,
   1146                         "authenticatorData": b64_adata,
   1147                         "signature": b64_sig,
   1148                         "userHandle": b64_user,
   1149                     },
   1150                     "clientExtensionResults": {
   1151                         "prf": {
   1152                             "results": {
   1153                                 "first": null,
   1154                                 "second": ""
   1155                             },
   1156                         }
   1157                     },
   1158                     "type": "public-key"
   1159                 })
   1160                 .to_string()
   1161                 .as_str()
   1162             )
   1163             .unwrap_err()
   1164             .to_string()
   1165             .into_bytes()[..err.len()],
   1166             err
   1167         );
   1168         // `enabled` is still not allowed.
   1169         err = Error::unknown_field("enabled", ["results"].as_slice())
   1170             .to_string()
   1171             .into_bytes();
   1172         assert_eq!(
   1173             serde_json::from_str::<AuthenticationRelaxed>(
   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                         "prf": {
   1185                             "enabled": true,
   1186                             "results": null
   1187                         }
   1188                     },
   1189                     "type": "public-key"
   1190                 })
   1191                 .to_string()
   1192                 .as_str()
   1193             )
   1194             .unwrap_err()
   1195             .to_string()
   1196             .into_bytes()[..err.len()],
   1197             err
   1198         );
   1199         // Unknown `prf` field.
   1200         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1201             serde_json::json!({
   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                     "prf": {
   1212                         "foo": true,
   1213                         "results": null
   1214                     }
   1215                 },
   1216                 "type": "public-key"
   1217             })
   1218             .to_string()
   1219             .as_str()
   1220         )
   1221         .is_ok());
   1222         // Unknown `results` field.
   1223         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   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                     "prf": {
   1235                         "results": {
   1236                             "first": null,
   1237                             "Second": null
   1238                         }
   1239                     }
   1240                 },
   1241                 "type": "public-key"
   1242             })
   1243             .to_string()
   1244             .as_str()
   1245         )
   1246         .is_ok());
   1247         // Duplicate field in `results`.
   1248         err = Error::duplicate_field("first").to_string().into_bytes();
   1249         assert_eq!(
   1250             serde_json::from_str::<AuthenticationRelaxed>(
   1251                 format!(
   1252                     "{{
   1253                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1254                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1255                        \"response\": {{
   1256                            \"clientDataJSON\": \"{b64_cdata}\",
   1257                            \"authenticatorData\": \"{b64_adata}\",
   1258                            \"signature\": \"{b64_sig}\",
   1259                            \"userHandle\": \"{b64_user}\"
   1260                        }},
   1261                        \"clientExtensionResults\": {{
   1262                            \"prf\": {{
   1263                                \"results\": {{
   1264                                    \"first\": null,
   1265                                    \"first\": null
   1266                                }}
   1267                            }}
   1268                        }},
   1269                        \"type\": \"public-key\"
   1270                      }}"
   1271                 )
   1272                 .as_str()
   1273             )
   1274             .unwrap_err()
   1275             .to_string()
   1276             .into_bytes()[..err.len()],
   1277             err
   1278         );
   1279     }
   1280 }