webauthn_rp

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

ser_relaxed.rs (45699B)


      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::{
    123         super::{super::BASE64URL_NOPAD_ENC, AuthenticatorAttachment},
    124         AuthenticationRelaxed,
    125     };
    126     use rsa::sha2::{Digest as _, Sha256};
    127     use serde::de::{Error as _, Unexpected};
    128     use serde_json::Error;
    129     #[test]
    130     fn eddsa_authentication_deserialize_data_mismatch() {
    131         let c_data_json = serde_json::json!({}).to_string();
    132         let auth_data = [
    133             // `rpIdHash`.
    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             0,
    165             0,
    166             // `flags`.
    167             0b0000_0101,
    168             // `signCount`.
    169             0,
    170             0,
    171             0,
    172             0,
    173         ];
    174         let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
    175         let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
    176         let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
    177         let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
    178         // Base case is valid.
    179         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    180             serde_json::json!({
    181                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    182                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    183                 "response": {
    184                     "clientDataJSON": b64_cdata,
    185                     "authenticatorData": b64_adata,
    186                     "signature": b64_sig,
    187                     "userHandle": b64_user,
    188                 },
    189                 "authenticatorAttachment": "cross-platform",
    190                 "clientExtensionResults": {},
    191                 "type": "public-key"
    192             })
    193             .to_string()
    194             .as_str()
    195         )
    196         .map_or(false, |auth| auth.0.response.client_data_json
    197             == c_data_json.as_bytes()
    198             && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
    199             && auth.0.response.authenticator_data_and_c_data_hash[37..]
    200                 == *Sha256::digest(c_data_json.as_bytes()).as_slice()
    201             && matches!(
    202                 auth.0.authenticator_attachment,
    203                 AuthenticatorAttachment::CrossPlatform
    204             )));
    205         // `id` and `rawId` mismatch.
    206         let mut err = Error::invalid_value(
    207             Unexpected::Bytes(
    208                 BASE64URL_NOPAD_ENC
    209                     .decode("ABABABABABABABABABABAA".as_bytes())
    210                     .unwrap()
    211                     .as_slice(),
    212             ),
    213             &format!("id and rawId to match: CredentialId({:?})", [0; 16]).as_str(),
    214         )
    215         .to_string()
    216         .into_bytes();
    217         assert_eq!(
    218             serde_json::from_str::<AuthenticationRelaxed>(
    219                 serde_json::json!({
    220                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    221                     "rawId": "ABABABABABABABABABABAA",
    222                     "response": {
    223                         "clientDataJSON": b64_cdata,
    224                         "authenticatorData": b64_adata,
    225                         "signature": b64_sig,
    226                         "userHandle": b64_user,
    227                     },
    228                     "authenticatorAttachment": "cross-platform",
    229                     "clientExtensionResults": {},
    230                     "type": "public-key"
    231                 })
    232                 .to_string()
    233                 .as_str()
    234             )
    235             .unwrap_err()
    236             .to_string()
    237             .into_bytes()[..err.len()],
    238             err
    239         );
    240         // missing `id`.
    241         err = Error::missing_field("id").to_string().into_bytes();
    242         assert_eq!(
    243             serde_json::from_str::<AuthenticationRelaxed>(
    244                 serde_json::json!({
    245                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    246                     "response": {
    247                         "clientDataJSON": b64_cdata,
    248                         "authenticatorData": b64_adata,
    249                         "signature": b64_sig,
    250                         "userHandle": b64_user,
    251                     },
    252                     "authenticatorAttachment": "cross-platform",
    253                     "clientExtensionResults": {},
    254                     "type": "public-key"
    255                 })
    256                 .to_string()
    257                 .as_str()
    258             )
    259             .unwrap_err()
    260             .to_string()
    261             .into_bytes()[..err.len()],
    262             err
    263         );
    264         // `null` `id`.
    265         err = Error::invalid_type(Unexpected::Other("null"), &"id")
    266             .to_string()
    267             .into_bytes();
    268         assert_eq!(
    269             serde_json::from_str::<AuthenticationRelaxed>(
    270                 serde_json::json!({
    271                     "id": null,
    272                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    273                     "response": {
    274                         "clientDataJSON": b64_cdata,
    275                         "authenticatorData": b64_adata,
    276                         "signature": b64_sig,
    277                         "userHandle": b64_user,
    278                     },
    279                     "clientExtensionResults": {},
    280                     "type": "public-key"
    281                 })
    282                 .to_string()
    283                 .as_str()
    284             )
    285             .unwrap_err()
    286             .to_string()
    287             .into_bytes()[..err.len()],
    288             err
    289         );
    290         // missing `rawId`.
    291         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    292             serde_json::json!({
    293                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    294                 "response": {
    295                     "clientDataJSON": b64_cdata,
    296                     "authenticatorData": b64_adata,
    297                     "signature": b64_sig,
    298                     "userHandle": b64_user,
    299                 },
    300                 "clientExtensionResults": {},
    301                 "type": "public-key"
    302             })
    303             .to_string()
    304             .as_str()
    305         )
    306         .is_ok());
    307         // `null` `rawId`.
    308         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    309             serde_json::json!({
    310                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    311                 "rawId": null,
    312                 "response": {
    313                     "clientDataJSON": b64_cdata,
    314                     "authenticatorData": b64_adata,
    315                     "signature": b64_sig,
    316                     "userHandle": b64_user,
    317                 },
    318                 "clientExtensionResults": {},
    319                 "type": "public-key"
    320             })
    321             .to_string()
    322             .as_str()
    323         )
    324         .is_ok());
    325         // Missing `authenticatorData`.
    326         err = Error::missing_field("authenticatorData")
    327             .to_string()
    328             .into_bytes();
    329         assert_eq!(
    330             serde_json::from_str::<AuthenticationRelaxed>(
    331                 serde_json::json!({
    332                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    333                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    334                     "response": {
    335                         "clientDataJSON": b64_cdata,
    336                         "signature": b64_sig,
    337                         "userHandle": b64_user,
    338                     },
    339                     "clientExtensionResults": {},
    340                     "type": "public-key"
    341                 })
    342                 .to_string()
    343                 .as_str()
    344             )
    345             .unwrap_err()
    346             .to_string()
    347             .into_bytes()[..err.len()],
    348             err
    349         );
    350         // `null` `authenticatorData`.
    351         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData")
    352             .to_string()
    353             .into_bytes();
    354         assert_eq!(
    355             serde_json::from_str::<AuthenticationRelaxed>(
    356                 serde_json::json!({
    357                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    358                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    359                     "response": {
    360                         "clientDataJSON": b64_cdata,
    361                         "authenticatorData": null,
    362                         "signature": b64_sig,
    363                         "userHandle": b64_user,
    364                     },
    365                     "clientExtensionResults": {},
    366                     "type": "public-key"
    367                 })
    368                 .to_string()
    369                 .as_str()
    370             )
    371             .unwrap_err()
    372             .to_string()
    373             .into_bytes()[..err.len()],
    374             err
    375         );
    376         // Missing `signature`.
    377         err = Error::missing_field("signature").to_string().into_bytes();
    378         assert_eq!(
    379             serde_json::from_str::<AuthenticationRelaxed>(
    380                 serde_json::json!({
    381                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    382                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    383                     "response": {
    384                         "clientDataJSON": b64_cdata,
    385                         "authenticatorData": b64_adata,
    386                         "userHandle": b64_user,
    387                     },
    388                     "clientExtensionResults": {},
    389                     "type": "public-key"
    390                 })
    391                 .to_string()
    392                 .as_str()
    393             )
    394             .unwrap_err()
    395             .to_string()
    396             .into_bytes()[..err.len()],
    397             err
    398         );
    399         // `null` `signature`.
    400         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    401             .to_string()
    402             .into_bytes();
    403         assert_eq!(
    404             serde_json::from_str::<AuthenticationRelaxed>(
    405                 serde_json::json!({
    406                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    407                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    408                     "response": {
    409                         "clientDataJSON": b64_cdata,
    410                         "authenticatorData": b64_adata,
    411                         "signature": null,
    412                         "userHandle": b64_user,
    413                     },
    414                     "clientExtensionResults": {},
    415                     "type": "public-key"
    416                 })
    417                 .to_string()
    418                 .as_str()
    419             )
    420             .unwrap_err()
    421             .to_string()
    422             .into_bytes()[..err.len()],
    423             err
    424         );
    425         // Missing `userHandle`.
    426         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    427             serde_json::json!({
    428                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    429                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    430                 "response": {
    431                     "clientDataJSON": b64_cdata,
    432                     "authenticatorData": b64_adata,
    433                     "signature": b64_sig,
    434                 },
    435                 "clientExtensionResults": {},
    436                 "type": "public-key"
    437             })
    438             .to_string()
    439             .as_str()
    440         )
    441         .is_ok());
    442         // `null` `userHandle`.
    443         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    444             serde_json::json!({
    445                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    446                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    447                 "response": {
    448                     "clientDataJSON": b64_cdata,
    449                     "authenticatorData": b64_adata,
    450                     "signature": b64_sig,
    451                     "userHandle": null,
    452                 },
    453                 "clientExtensionResults": {},
    454                 "type": "public-key"
    455             })
    456             .to_string()
    457             .as_str()
    458         )
    459         .is_ok());
    460         // `null` `authenticatorAttachment`.
    461         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    462             serde_json::json!({
    463                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    464                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    465                 "response": {
    466                     "clientDataJSON": b64_cdata,
    467                     "authenticatorData": b64_adata,
    468                     "signature": b64_sig,
    469                     "userHandle": b64_user,
    470                 },
    471                 "authenticatorAttachment": null,
    472                 "clientExtensionResults": {},
    473                 "type": "public-key"
    474             })
    475             .to_string()
    476             .as_str()
    477         )
    478         .map_or(false, |auth| matches!(
    479             auth.0.authenticator_attachment,
    480             AuthenticatorAttachment::None
    481         )));
    482         // Unknown `authenticatorAttachment`.
    483         err = Error::invalid_value(
    484             Unexpected::Str("Platform"),
    485             &"'platform' or 'cross-platform'",
    486         )
    487         .to_string()
    488         .into_bytes();
    489         assert_eq!(
    490             serde_json::from_str::<AuthenticationRelaxed>(
    491                 serde_json::json!({
    492                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    493                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    494                     "response": {
    495                         "clientDataJSON": b64_cdata,
    496                         "authenticatorData": b64_adata,
    497                         "signature": b64_sig,
    498                         "userHandle": b64_user,
    499                     },
    500                     "authenticatorAttachment": "Platform",
    501                     "clientExtensionResults": {},
    502                     "type": "public-key"
    503                 })
    504                 .to_string()
    505                 .as_str()
    506             )
    507             .unwrap_err()
    508             .to_string()
    509             .into_bytes()[..err.len()],
    510             err
    511         );
    512         // Missing `clientDataJSON`.
    513         err = Error::missing_field("clientDataJSON")
    514             .to_string()
    515             .into_bytes();
    516         assert_eq!(
    517             serde_json::from_str::<AuthenticationRelaxed>(
    518                 serde_json::json!({
    519                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    520                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    521                     "response": {
    522                         "authenticatorData": b64_adata,
    523                         "signature": b64_sig,
    524                         "userHandle": b64_user,
    525                     },
    526                     "clientExtensionResults": {},
    527                     "type": "public-key"
    528                 })
    529                 .to_string()
    530                 .as_str()
    531             )
    532             .unwrap_err()
    533             .to_string()
    534             .into_bytes()[..err.len()],
    535             err
    536         );
    537         // `null` `clientDataJSON`.
    538         err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data")
    539             .to_string()
    540             .into_bytes();
    541         assert_eq!(
    542             serde_json::from_str::<AuthenticationRelaxed>(
    543                 serde_json::json!({
    544                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    545                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    546                     "response": {
    547                         "clientDataJSON": null,
    548                         "authenticatorData": b64_adata,
    549                         "signature": b64_sig,
    550                         "userHandle": b64_user,
    551                     },
    552                     "clientExtensionResults": {},
    553                     "type": "public-key"
    554                 })
    555                 .to_string()
    556                 .as_str()
    557             )
    558             .unwrap_err()
    559             .to_string()
    560             .into_bytes()[..err.len()],
    561             err
    562         );
    563         // Missing `response`.
    564         err = Error::missing_field("response").to_string().into_bytes();
    565         assert_eq!(
    566             serde_json::from_str::<AuthenticationRelaxed>(
    567                 serde_json::json!({
    568                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    569                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    570                     "clientExtensionResults": {},
    571                     "type": "public-key"
    572                 })
    573                 .to_string()
    574                 .as_str()
    575             )
    576             .unwrap_err()
    577             .to_string()
    578             .into_bytes()[..err.len()],
    579             err
    580         );
    581         // `null` `response`.
    582         err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion")
    583             .to_string()
    584             .into_bytes();
    585         assert_eq!(
    586             serde_json::from_str::<AuthenticationRelaxed>(
    587                 serde_json::json!({
    588                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    589                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    590                     "response": null,
    591                     "clientExtensionResults": {},
    592                     "type": "public-key"
    593                 })
    594                 .to_string()
    595                 .as_str()
    596             )
    597             .unwrap_err()
    598             .to_string()
    599             .into_bytes()[..err.len()],
    600             err
    601         );
    602         // Empty `response`.
    603         err = Error::missing_field("clientDataJSON")
    604             .to_string()
    605             .into_bytes();
    606         assert_eq!(
    607             serde_json::from_str::<AuthenticationRelaxed>(
    608                 serde_json::json!({
    609                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    610                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    611                     "response": {},
    612                     "clientExtensionResults": {},
    613                     "type": "public-key"
    614                 })
    615                 .to_string()
    616                 .as_str()
    617             )
    618             .unwrap_err()
    619             .to_string()
    620             .into_bytes()[..err.len()],
    621             err
    622         );
    623         // Missing `clientExtensionResults`.
    624         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    625             serde_json::json!({
    626                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    627                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    628                 "response": {
    629                     "clientDataJSON": b64_cdata,
    630                     "authenticatorData": b64_adata,
    631                     "signature": b64_sig,
    632                     "userHandle": b64_user,
    633                 },
    634                 "type": "public-key"
    635             })
    636             .to_string()
    637             .as_str()
    638         )
    639         .is_ok());
    640         // `null` `clientExtensionResults`.
    641         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    642             serde_json::json!({
    643                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    644                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    645                 "response": {
    646                     "clientDataJSON": b64_cdata,
    647                     "authenticatorData": b64_adata,
    648                     "signature": b64_sig,
    649                     "userHandle": b64_user,
    650                 },
    651                 "clientExtensionResults": null,
    652                 "type": "public-key"
    653             })
    654             .to_string()
    655             .as_str()
    656         )
    657         .is_ok());
    658         // Missing `type`.
    659         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    660             serde_json::json!({
    661                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    662                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    663                 "response": {
    664                     "clientDataJSON": b64_cdata,
    665                     "authenticatorData": b64_adata,
    666                     "signature": b64_sig,
    667                     "userHandle": b64_user,
    668                 },
    669                 "clientExtensionResults": {},
    670             })
    671             .to_string()
    672             .as_str()
    673         )
    674         .is_ok());
    675         // `null` `type`.
    676         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    677             serde_json::json!({
    678                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    679                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    680                 "response": {
    681                     "clientDataJSON": b64_cdata,
    682                     "authenticatorData": b64_adata,
    683                     "signature": b64_sig,
    684                     "userHandle": b64_user,
    685                 },
    686                 "clientExtensionResults": {},
    687                 "type": null
    688             })
    689             .to_string()
    690             .as_str()
    691         )
    692         .is_ok());
    693         // Not exactly `public-type` `type`.
    694         err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key")
    695             .to_string()
    696             .into_bytes();
    697         assert_eq!(
    698             serde_json::from_str::<AuthenticationRelaxed>(
    699                 serde_json::json!({
    700                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
    701                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    702                     "response": {
    703                         "clientDataJSON": b64_cdata,
    704                         "authenticatorData": b64_adata,
    705                         "signature": b64_sig,
    706                         "userHandle": b64_user,
    707                     },
    708                     "clientExtensionResults": {},
    709                     "type": "Public-key"
    710                 })
    711                 .to_string()
    712                 .as_str()
    713             )
    714             .unwrap_err()
    715             .to_string()
    716             .into_bytes()[..err.len()],
    717             err
    718         );
    719         // `null`.
    720         err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential")
    721             .to_string()
    722             .into_bytes();
    723         assert_eq!(
    724             serde_json::from_str::<AuthenticationRelaxed>(
    725                 serde_json::json!(null).to_string().as_str()
    726             )
    727             .unwrap_err()
    728             .to_string()
    729             .into_bytes()[..err.len()],
    730             err
    731         );
    732         // Empty.
    733         err = Error::missing_field("response").to_string().into_bytes();
    734         assert_eq!(
    735             serde_json::from_str::<AuthenticationRelaxed>(
    736                 serde_json::json!({}).to_string().as_str()
    737             )
    738             .unwrap_err()
    739             .to_string()
    740             .into_bytes()[..err.len()],
    741             err
    742         );
    743         // Unknown field in `response`.
    744         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    745             serde_json::json!({
    746                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    747                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    748                 "response": {
    749                     "clientDataJSON": b64_cdata,
    750                     "authenticatorData": b64_adata,
    751                     "signature": b64_sig,
    752                     "userHandle": b64_user,
    753                     "foo": true,
    754                 },
    755                 "clientExtensionResults": {},
    756                 "type": "public-key"
    757             })
    758             .to_string()
    759             .as_str()
    760         )
    761         .is_ok());
    762         // Duplicate field in `response`.
    763         err = Error::duplicate_field("userHandle")
    764             .to_string()
    765             .into_bytes();
    766         assert_eq!(
    767             serde_json::from_str::<AuthenticationRelaxed>(
    768                 format!(
    769                     "{{
    770                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    771                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    772                        \"response\": {{
    773                            \"clientDataJSON\": \"{b64_cdata}\",
    774                            \"authenticatorData\": \"{b64_adata}\",
    775                            \"signature\": \"{b64_sig}\",
    776                            \"userHandle\": \"{b64_user}\",
    777                            \"userHandle\": \"{b64_user}\"
    778                        }},
    779                        \"clientExtensionResults\": {{}},
    780                        \"type\": \"public-key\"
    781 
    782                      }}"
    783                 )
    784                 .as_str()
    785             )
    786             .unwrap_err()
    787             .to_string()
    788             .into_bytes()[..err.len()],
    789             err
    790         );
    791         // Unknown field in `PublicKeyCredential`.
    792         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    793             serde_json::json!({
    794                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    795                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    796                 "response": {
    797                     "clientDataJSON": b64_cdata,
    798                     "authenticatorData": b64_adata,
    799                     "signature": b64_sig,
    800                     "userHandle": b64_user,
    801                 },
    802                 "clientExtensionResults": {},
    803                 "type": "public-key",
    804                 "foo": true,
    805             })
    806             .to_string()
    807             .as_str()
    808         )
    809         .is_ok());
    810         // Duplicate field in `PublicKeyCredential`.
    811         err = Error::duplicate_field("id").to_string().into_bytes();
    812         assert_eq!(
    813             serde_json::from_str::<AuthenticationRelaxed>(
    814                 format!(
    815                     "{{
    816                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    817                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    818                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    819                        \"response\": {{
    820                            \"clientDataJSON\": \"{b64_cdata}\",
    821                            \"authenticatorData\": \"{b64_adata}\",
    822                            \"signature\": \"{b64_sig}\",
    823                            \"userHandle\": \"{b64_user}\"
    824                        }},
    825                        \"clientExtensionResults\": {{}},
    826                        \"type\": \"public-key\"
    827 
    828                      }}"
    829                 )
    830                 .as_str()
    831             )
    832             .unwrap_err()
    833             .to_string()
    834             .into_bytes()[..err.len()],
    835             err
    836         );
    837     }
    838     #[test]
    839     fn client_extensions() {
    840         let c_data_json = serde_json::json!({}).to_string();
    841         let auth_data = [
    842             // `rpIdHash`.
    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             0,
    874             0,
    875             // `flags`.
    876             0b0000_0101,
    877             // `signCount`.
    878             0,
    879             0,
    880             0,
    881             0,
    882         ];
    883         let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
    884         let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
    885         let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
    886         let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
    887         // Base case is valid.
    888         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    889             serde_json::json!({
    890                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    891                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    892                 "response": {
    893                     "clientDataJSON": b64_cdata,
    894                     "authenticatorData": b64_adata,
    895                     "signature": b64_sig,
    896                     "userHandle": b64_user,
    897                 },
    898                 "authenticatorAttachment": "cross-platform",
    899                 "clientExtensionResults": {},
    900                 "type": "public-key"
    901             })
    902             .to_string()
    903             .as_str()
    904         )
    905         .map_or(false, |auth| auth.0.response.client_data_json
    906             == c_data_json.as_bytes()
    907             && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data
    908             && auth.0.response.authenticator_data_and_c_data_hash[37..]
    909                 == *Sha256::digest(c_data_json.as_bytes()).as_slice()
    910             && matches!(
    911                 auth.0.authenticator_attachment,
    912                 AuthenticatorAttachment::CrossPlatform
    913             )));
    914         // `null` `prf`.
    915         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    916             serde_json::json!({
    917                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    918                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    919                 "response": {
    920                     "clientDataJSON": b64_cdata,
    921                     "authenticatorData": b64_adata,
    922                     "signature": b64_sig,
    923                     "userHandle": b64_user,
    924                 },
    925                 "clientExtensionResults": {
    926                     "prf": null
    927                 },
    928                 "type": "public-key"
    929             })
    930             .to_string()
    931             .as_str()
    932         )
    933         .is_ok());
    934         // Unknown `clientExtensionResults`.
    935         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    936             serde_json::json!({
    937                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    938                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    939                 "response": {
    940                     "clientDataJSON": b64_cdata,
    941                     "authenticatorData": b64_adata,
    942                     "signature": b64_sig,
    943                     "userHandle": b64_user,
    944                 },
    945                 "clientExtensionResults": {
    946                     "Prf": null
    947                 },
    948                 "type": "public-key"
    949             })
    950             .to_string()
    951             .as_str()
    952         )
    953         .is_ok());
    954         // Duplicate field.
    955         let mut err = Error::duplicate_field("prf").to_string().into_bytes();
    956         assert_eq!(
    957             serde_json::from_str::<AuthenticationRelaxed>(
    958                 format!(
    959                     "{{
    960                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    961                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
    962                        \"response\": {{
    963                            \"clientDataJSON\": \"{b64_cdata}\",
    964                            \"authenticatorData\": \"{b64_adata}\",
    965                            \"signature\": \"{b64_sig}\",
    966                            \"userHandle\": \"{b64_user}\"
    967                        }},
    968                        \"clientExtensionResults\": {{
    969                            \"prf\": null,
    970                            \"prf\": null
    971                        }},
    972                        \"type\": \"public-key\"
    973                      }}"
    974                 )
    975                 .as_str()
    976             )
    977             .unwrap_err()
    978             .to_string()
    979             .into_bytes()[..err.len()],
    980             err
    981         );
    982         // `null` `results`.
    983         assert!(serde_json::from_str::<AuthenticationRelaxed>(
    984             serde_json::json!({
    985                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
    986                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
    987                 "response": {
    988                     "clientDataJSON": b64_cdata,
    989                     "authenticatorData": b64_adata,
    990                     "signature": b64_sig,
    991                     "userHandle": b64_user,
    992                 },
    993                 "clientExtensionResults": {
    994                     "prf": {
    995                         "results": null,
    996                     }
    997                 },
    998                 "type": "public-key"
    999             })
   1000             .to_string()
   1001             .as_str()
   1002         )
   1003         .is_ok());
   1004         // Duplicate field in `prf`.
   1005         err = Error::duplicate_field("results").to_string().into_bytes();
   1006         assert_eq!(
   1007             serde_json::from_str::<AuthenticationRelaxed>(
   1008                 format!(
   1009                     "{{
   1010                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1011                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1012                        \"response\": {{
   1013                            \"clientDataJSON\": \"{b64_cdata}\",
   1014                            \"authenticatorData\": \"{b64_adata}\",
   1015                            \"signature\": \"{b64_sig}\",
   1016                            \"userHandle\": \"{b64_user}\"
   1017                        }},
   1018                        \"clientExtensionResults\": {{
   1019                            \"prf\": {{
   1020                                \"results\": null,
   1021                                \"results\": null
   1022                            }}
   1023                        }},
   1024                        \"type\": \"public-key\"
   1025                      }}"
   1026                 )
   1027                 .as_str()
   1028             )
   1029             .unwrap_err()
   1030             .to_string()
   1031             .into_bytes()[..err.len()],
   1032             err
   1033         );
   1034         // Missing `first`.
   1035         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1036             serde_json::json!({
   1037                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1038                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1039                 "response": {
   1040                     "clientDataJSON": b64_cdata,
   1041                     "authenticatorData": b64_adata,
   1042                     "signature": b64_sig,
   1043                     "userHandle": b64_user,
   1044                 },
   1045                 "clientExtensionResults": {
   1046                     "prf": {
   1047                         "results": {},
   1048                     }
   1049                 },
   1050                 "type": "public-key"
   1051             })
   1052             .to_string()
   1053             .as_str()
   1054         )
   1055         .is_ok());
   1056         // `null` `first`.
   1057         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1058             serde_json::json!({
   1059                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1060                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1061                 "response": {
   1062                     "clientDataJSON": b64_cdata,
   1063                     "authenticatorData": b64_adata,
   1064                     "signature": b64_sig,
   1065                     "userHandle": b64_user,
   1066                 },
   1067                 "clientExtensionResults": {
   1068                     "prf": {
   1069                         "results": {
   1070                             "first": null
   1071                         },
   1072                     }
   1073                 },
   1074                 "type": "public-key"
   1075             })
   1076             .to_string()
   1077             .as_str()
   1078         )
   1079         .is_ok());
   1080         // `null` `second`.
   1081         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1082             serde_json::json!({
   1083                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1084                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1085                 "response": {
   1086                     "clientDataJSON": b64_cdata,
   1087                     "authenticatorData": b64_adata,
   1088                     "signature": b64_sig,
   1089                     "userHandle": b64_user,
   1090                 },
   1091                 "clientExtensionResults": {
   1092                     "prf": {
   1093                         "results": {
   1094                             "first": null,
   1095                             "second": null
   1096                         },
   1097                     }
   1098                 },
   1099                 "type": "public-key"
   1100             })
   1101             .to_string()
   1102             .as_str()
   1103         )
   1104         .is_ok());
   1105         // Non-`null` `first`.
   1106         err = Error::invalid_type(Unexpected::Option, &"null")
   1107             .to_string()
   1108             .into_bytes();
   1109         assert_eq!(
   1110             serde_json::from_str::<AuthenticationRelaxed>(
   1111                 serde_json::json!({
   1112                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1113                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1114                     "response": {
   1115                         "clientDataJSON": b64_cdata,
   1116                         "authenticatorData": b64_adata,
   1117                         "signature": b64_sig,
   1118                         "userHandle": b64_user,
   1119                     },
   1120                     "clientExtensionResults": {
   1121                         "prf": {
   1122                             "results": {
   1123                                 "first": ""
   1124                             },
   1125                         }
   1126                     },
   1127                     "type": "public-key"
   1128                 })
   1129                 .to_string()
   1130                 .as_str()
   1131             )
   1132             .unwrap_err()
   1133             .to_string()
   1134             .into_bytes()[..err.len()],
   1135             err
   1136         );
   1137         // Non-`null` `second`.
   1138         err = Error::invalid_type(Unexpected::Option, &"null")
   1139             .to_string()
   1140             .into_bytes();
   1141         assert_eq!(
   1142             serde_json::from_str::<AuthenticationRelaxed>(
   1143                 serde_json::json!({
   1144                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1145                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1146                     "response": {
   1147                         "clientDataJSON": b64_cdata,
   1148                         "authenticatorData": b64_adata,
   1149                         "signature": b64_sig,
   1150                         "userHandle": b64_user,
   1151                     },
   1152                     "clientExtensionResults": {
   1153                         "prf": {
   1154                             "results": {
   1155                                 "first": null,
   1156                                 "second": ""
   1157                             },
   1158                         }
   1159                     },
   1160                     "type": "public-key"
   1161                 })
   1162                 .to_string()
   1163                 .as_str()
   1164             )
   1165             .unwrap_err()
   1166             .to_string()
   1167             .into_bytes()[..err.len()],
   1168             err
   1169         );
   1170         // `enabled` is still not allowed.
   1171         err = Error::unknown_field("enabled", ["results"].as_slice())
   1172             .to_string()
   1173             .into_bytes();
   1174         assert_eq!(
   1175             serde_json::from_str::<AuthenticationRelaxed>(
   1176                 serde_json::json!({
   1177                     "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1178                     "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1179                     "response": {
   1180                         "clientDataJSON": b64_cdata,
   1181                         "authenticatorData": b64_adata,
   1182                         "signature": b64_sig,
   1183                         "userHandle": b64_user,
   1184                     },
   1185                     "clientExtensionResults": {
   1186                         "prf": {
   1187                             "enabled": true,
   1188                             "results": null
   1189                         }
   1190                     },
   1191                     "type": "public-key"
   1192                 })
   1193                 .to_string()
   1194                 .as_str()
   1195             )
   1196             .unwrap_err()
   1197             .to_string()
   1198             .into_bytes()[..err.len()],
   1199             err
   1200         );
   1201         // Unknown `prf` field.
   1202         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1203             serde_json::json!({
   1204                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1205                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1206                 "response": {
   1207                     "clientDataJSON": b64_cdata,
   1208                     "authenticatorData": b64_adata,
   1209                     "signature": b64_sig,
   1210                     "userHandle": b64_user,
   1211                 },
   1212                 "clientExtensionResults": {
   1213                     "prf": {
   1214                         "foo": true,
   1215                         "results": null
   1216                     }
   1217                 },
   1218                 "type": "public-key"
   1219             })
   1220             .to_string()
   1221             .as_str()
   1222         )
   1223         .is_ok());
   1224         // Unknown `results` field.
   1225         assert!(serde_json::from_str::<AuthenticationRelaxed>(
   1226             serde_json::json!({
   1227                 "id": "AAAAAAAAAAAAAAAAAAAAAA",
   1228                 "rawId": "AAAAAAAAAAAAAAAAAAAAAA",
   1229                 "response": {
   1230                     "clientDataJSON": b64_cdata,
   1231                     "authenticatorData": b64_adata,
   1232                     "signature": b64_sig,
   1233                     "userHandle": b64_user,
   1234                 },
   1235                 "clientExtensionResults": {
   1236                     "prf": {
   1237                         "results": {
   1238                             "first": null,
   1239                             "Second": null
   1240                         }
   1241                     }
   1242                 },
   1243                 "type": "public-key"
   1244             })
   1245             .to_string()
   1246             .as_str()
   1247         )
   1248         .is_ok());
   1249         // Duplicate field in `results`.
   1250         err = Error::duplicate_field("first").to_string().into_bytes();
   1251         assert_eq!(
   1252             serde_json::from_str::<AuthenticationRelaxed>(
   1253                 format!(
   1254                     "{{
   1255                        \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1256                        \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\",
   1257                        \"response\": {{
   1258                            \"clientDataJSON\": \"{b64_cdata}\",
   1259                            \"authenticatorData\": \"{b64_adata}\",
   1260                            \"signature\": \"{b64_sig}\",
   1261                            \"userHandle\": \"{b64_user}\"
   1262                        }},
   1263                        \"clientExtensionResults\": {{
   1264                            \"prf\": {{
   1265                                \"results\": {{
   1266                                    \"first\": null,
   1267                                    \"first\": null
   1268                                }}
   1269                            }}
   1270                        }},
   1271                        \"type\": \"public-key\"
   1272                      }}"
   1273                 )
   1274                 .as_str()
   1275             )
   1276             .unwrap_err()
   1277             .to_string()
   1278             .into_bytes()[..err.len()],
   1279             err
   1280         );
   1281     }
   1282 }