webauthn_rp

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

tests.rs (41997B)


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