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