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