webauthn_rp

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

tests.rs (57245B)


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