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 }