ser_relaxed.rs (83697B)
1 #![expect( 2 clippy::question_mark_used, 3 reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs" 4 )] 5 #[cfg(doc)] 6 use super::super::{Challenge, CredentialId}; 7 use super::{ 8 super::{ 9 super::request::register::{User, UserHandle16, UserHandle64}, 10 auth::ser::{ 11 AUTH_ASSERT_FIELDS, AuthData, AuthenticatorAssertionVisitor, ClientExtensionsOutputs, 12 ClientExtensionsOutputsVisitor, EXT_FIELDS, 13 }, 14 ser::{ 15 AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions, 16 PublicKeyCredential, Type, 17 }, 18 ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed, 19 }, 20 Authentication, AuthenticatorAssertion, AuthenticatorAttachment, UserHandle, 21 }; 22 use core::{ 23 fmt::{self, Formatter}, 24 marker::PhantomData, 25 }; 26 #[cfg(doc)] 27 use data_encoding::BASE64URL_NOPAD; 28 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor}; 29 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation. 30 struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs); 31 impl ClientExtensions for ClientExtensionsOutputsRelaxed { 32 fn empty() -> Self { 33 Self(ClientExtensionsOutputs::empty()) 34 } 35 } 36 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed { 37 /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored. 38 /// 39 /// Note that duplicate keys are still forbidden. 40 #[inline] 41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 42 where 43 D: Deserializer<'de>, 44 { 45 deserializer 46 .deserialize_struct( 47 "ClientExtensionsOutputsRelaxed", 48 EXT_FIELDS, 49 ClientExtensionsOutputsVisitor::< 50 true, 51 AuthenticationExtensionsPrfOutputsHelper< 52 true, 53 false, 54 AuthenticationExtensionsPrfValuesRelaxed, 55 >, 56 >(PhantomData), 57 ) 58 .map(Self) 59 } 60 } 61 /// `newtype` around `AuthenticatorAssertion` with a "relaxed" [`Self::deserialize`] implementation. 62 #[derive(Debug)] 63 pub struct AuthenticatorAssertionRelaxed<U>(pub AuthenticatorAssertion<U>); 64 impl<'de, U> Deserialize<'de> for AuthenticatorAssertionRelaxed<U> 65 where 66 U: Deserialize<'de> + User + Default, 67 { 68 /// Same as [`AuthenticatorAssertion::deserialize`] except unknown keys are ignored. 69 /// 70 /// Note that duplicate keys are still forbidden. 71 #[inline] 72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 73 where 74 D: Deserializer<'de>, 75 { 76 deserializer 77 .deserialize_struct( 78 "AuthenticatorAssertionRelaxed", 79 AUTH_ASSERT_FIELDS, 80 AuthenticatorAssertionVisitor::<true, U>::new(), 81 ) 82 .map(Self) 83 } 84 } 85 /// `newtype` around `Authentication` with a "relaxed" [`Self::deserialize`] implementation. 86 #[derive(Debug)] 87 pub struct AuthenticationRelaxed<U>(pub Authentication<U>); 88 impl<'de, U> Deserialize<'de> for AuthenticationRelaxed<U> 89 where 90 U: Deserialize<'de> + User + Default, 91 { 92 /// Same as [`Authentication::deserialize`] except unknown keys are ignored; 93 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized 94 /// via [`AuthenticatorAssertionRelaxed::deserialize`]; 95 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults) 96 /// is deserialized such unknown keys are ignored but duplicate keys are forbidden, 97 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` or an 98 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 99 /// such that unknown keys are allowed but duplicate keys are forbidden, 100 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 101 /// is forbidden (including being assigned `null`), 102 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 103 /// be `null`, or be an 104 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 105 /// where unknown keys are ignored, duplicate keys are forbidden, 106 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but 107 /// if it exists it must be `null`, and 108 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 109 /// must be `null` if so; and only 110 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and `response` are required. 111 /// `rawId` and `type` and allowed to not exist. For the other fields, they are allowed to not exist or be `null`. 112 /// 113 /// Note that duplicate keys are still forbidden, and data matching still applies when applicable. 114 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 115 #[inline] 116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 117 where 118 D: Deserializer<'de>, 119 { 120 PublicKeyCredential::< 121 true, 122 false, 123 AuthenticatorAssertionRelaxed<U>, 124 ClientExtensionsOutputsRelaxed, 125 >::deserialize(deserializer) 126 .map(|cred| { 127 Self(Authentication { 128 raw_id: cred.id.unwrap_or_else(|| { 129 unreachable!("there is a bug in PublicKeyCredential::deserialize") 130 }), 131 response: cred.response.0, 132 authenticator_attachment: cred.authenticator_attachment, 133 }) 134 }) 135 } 136 } 137 /// `AuthenticationRelaxed` with a required `UserHandle`. 138 pub type PasskeyAuthenticationRelaxed<T> = AuthenticationRelaxed<UserHandle<T>>; 139 /// `AuthenticationRelaxed` with a required `UserHandle64`. 140 pub type PasskeyAuthenticationRelaxed64 = AuthenticationRelaxed<UserHandle64>; 141 /// `AuthenticationRelaxed` with a required `UserHandle16`. 142 pub type PasskeyAuthenticationRelaxed16 = AuthenticationRelaxed<UserHandle16>; 143 /// `newtype` around `Authentication` with a custom [`Self::deserialize`] implementation. 144 #[derive(Debug)] 145 pub struct CustomAuthentication<U>(pub Authentication<U>); 146 impl<'de, U> Deserialize<'de> for CustomAuthentication<U> 147 where 148 U: Deserialize<'de> + User + Default, 149 { 150 /// Despite the spec having a 151 /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson) that clients 152 /// can follow, the downside is the superfluous data it contains. 153 /// 154 /// There simply is no reason to send the [`CredentialId`] twice. This redundant data puts RPs in 155 /// a position where they either ignore the data or parse the data to ensure no contradictions exist 156 /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)). 157 /// 158 /// While [`Authentication::deserialize`] _strictly_ adheres to the JSON definition, this implementation 159 /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate 160 /// and unknown keys are disallowed: 161 /// 162 /// ```json 163 /// { 164 /// "authenticatorAttachment": null | "platform" | "cross-platform", 165 /// "authenticatorData": <base64url string>, 166 /// "clientDataJSON": <base64url string>, 167 /// "clientExtensionResults": { 168 /// "prf": null | PRFJSON 169 /// }, 170 /// "id": <see CredentialId::deserialize>, 171 /// "signature": <base64url string>, 172 /// "type": "public-key", 173 /// "userHandle": null | <see UserHandle::deserialize> 174 /// } 175 /// // PRFJSON: 176 /// { 177 /// "results": null | PRFOutputsJSON 178 /// } 179 /// // PRFOutputsJSON: 180 /// { 181 /// "first": null, 182 /// "second": null 183 /// } 184 /// ``` 185 /// 186 /// `"userHandle"` is required to exist and not be `null` iff `U` is [`UserHandle`]. When it does exist and 187 /// is not `null`, then it is deserialized via [`UserHandle::deserialize`]. All of the remaining keys are 188 /// required with the exceptions of `"authenticatorAttachment"` and `"type"`. `"prf"` is not required in the 189 /// `clientExtensionResults` object, `"results"` is required in the `PRFJSON` object, and `"first"` 190 /// (but not `"second"`) is required in `PRFOutputsJSON`. 191 /// 192 /// # Examples 193 /// 194 /// ``` 195 /// # use webauthn_rp::{request::register::{UserHandle, USER_HANDLE_MIN_LEN}, response::auth::ser_relaxed::CustomAuthentication}; 196 /// assert!( 197 /// // The below payload is technically valid, but `AuthenticationServerState::verify` will fail 198 /// // since the authenticatorData is not valid. This is true for `Authentication::deserialize` 199 /// // as well since authenticatorData parsing is always deferred. 200 /// serde_json::from_str::<CustomAuthentication<UserHandle<[u8; USER_HANDLE_MIN_LEN]>>>( 201 /// r#"{ 202 /// "authenticatorData": "AA", 203 /// "authenticatorAttachment": "cross-platform", 204 /// "clientExtensionResults": {}, 205 /// "clientDataJSON": "AA", 206 /// "id": "AAAAAAAAAAAAAAAAAAAAAA", 207 /// "signature": "AA", 208 /// "type": "public-key", 209 /// "userHandle": "AA" 210 /// }"# 211 /// ).is_ok()); 212 /// ``` 213 #[expect( 214 clippy::too_many_lines, 215 reason = "want to hide; thus don't put in outer scope" 216 )] 217 #[inline] 218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 219 where 220 D: Deserializer<'de>, 221 { 222 /// `Visitor` for `CustomAuthentication`. 223 struct CustomAuthenticationVisitor<UHand>(PhantomData<fn() -> UHand>); 224 impl<'d, UHand> Visitor<'d> for CustomAuthenticationVisitor<UHand> 225 where 226 UHand: Deserialize<'d> + User + Default, 227 { 228 type Value = CustomAuthentication<UHand>; 229 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 230 formatter.write_str("CustomAuthentication") 231 } 232 #[expect( 233 clippy::too_many_lines, 234 reason = "want to hide; thus don't put in outer scope" 235 )] 236 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 237 where 238 A: MapAccess<'d>, 239 { 240 /// Fields in the JSON. 241 enum Field { 242 /// `authenticatorAttachment` key. 243 AuthenticatorAttachment, 244 /// `authenticatorData` key. 245 AuthenticatorData, 246 /// `clientDataJSON` key. 247 ClientDataJson, 248 /// `clientExtensionResults` key. 249 ClientExtensionResults, 250 /// `id` key. 251 Id, 252 /// `signature` key. 253 Signature, 254 /// `type` key. 255 Type, 256 /// `userHandle` key. 257 UserHandle, 258 } 259 impl<'e> Deserialize<'e> for Field { 260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 261 where 262 D: Deserializer<'e>, 263 { 264 /// `Visitor` for `Field`. 265 struct FieldVisitor; 266 impl Visitor<'_> for FieldVisitor { 267 type Value = Field; 268 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 269 write!( 270 formatter, 271 "'{AUTHENTICATOR_ATTACHMENT}', '{AUTHENTICATOR_DATA}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{ID}', '{SIGNATURE}', '{TYPE}', or '{USER_HANDLE}'" 272 ) 273 } 274 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 275 where 276 E: Error, 277 { 278 match v { 279 AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment), 280 AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData), 281 CLIENT_DATA_JSON => Ok(Field::ClientDataJson), 282 CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults), 283 ID => Ok(Field::Id), 284 SIGNATURE => Ok(Field::Signature), 285 TYPE => Ok(Field::Type), 286 USER_HANDLE => Ok(Field::UserHandle), 287 _ => Err(E::unknown_field(v, FIELDS)), 288 } 289 } 290 } 291 deserializer.deserialize_identifier(FieldVisitor) 292 } 293 } 294 let mut authenticator_attachment = None; 295 let mut authenticator_data = None; 296 let mut client_data_json = None; 297 let mut ext = false; 298 let mut id = None; 299 let mut signature = None; 300 let mut typ = false; 301 let mut user_handle = None; 302 while let Some(key) = map.next_key()? { 303 match key { 304 Field::AuthenticatorAttachment => { 305 if authenticator_attachment.is_some() { 306 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT)); 307 } 308 authenticator_attachment = map.next_value::<Option<_>>().map(Some)?; 309 } 310 Field::AuthenticatorData => { 311 if authenticator_data.is_some() { 312 return Err(Error::duplicate_field(AUTHENTICATOR_DATA)); 313 } 314 authenticator_data = 315 map.next_value::<AuthData>().map(|val| Some(val.0))?; 316 } 317 Field::ClientDataJson => { 318 if client_data_json.is_some() { 319 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 320 } 321 client_data_json = map 322 .next_value::<Base64DecodedVal>() 323 .map(|val| Some(val.0))?; 324 } 325 Field::ClientExtensionResults => { 326 if ext { 327 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS)); 328 } 329 ext = map.next_value::<ClientExtensionsOutputs>().map(|_| true)?; 330 } 331 Field::Id => { 332 if id.is_some() { 333 return Err(Error::duplicate_field(ID)); 334 } 335 id = map.next_value().map(Some)?; 336 } 337 Field::Signature => { 338 if signature.is_some() { 339 return Err(Error::duplicate_field(SIGNATURE)); 340 } 341 signature = map 342 .next_value::<Base64DecodedVal>() 343 .map(|val| Some(val.0))?; 344 } 345 Field::Type => { 346 if typ { 347 return Err(Error::duplicate_field(TYPE)); 348 } 349 typ = map.next_value::<Type>().map(|_| true)?; 350 } 351 Field::UserHandle => { 352 if user_handle.is_some() { 353 return Err(Error::duplicate_field(USER_HANDLE)); 354 } 355 user_handle = map.next_value().map(Some)?; 356 } 357 } 358 } 359 authenticator_data 360 .ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA)) 361 .and_then(|auth_data| { 362 client_data_json 363 .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)) 364 .and_then(|c_data| { 365 id.ok_or_else(|| Error::missing_field(ID)) 366 .and_then(|raw_id| { 367 signature 368 .ok_or_else(|| Error::missing_field(SIGNATURE)) 369 .and_then(|sig| { 370 if ext { 371 if UHand::must_exist() { 372 user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE)) 373 } else { 374 user_handle.map_or_else(|| Ok(UHand::default()), Ok) 375 }.map(|user| { 376 CustomAuthentication(Authentication { 377 response: AuthenticatorAssertion::new_inner( 378 c_data, 379 auth_data, 380 sig, 381 user, 382 ), 383 authenticator_attachment: 384 authenticator_attachment.map_or( 385 AuthenticatorAttachment::None, 386 |auth_attach| { 387 auth_attach.unwrap_or( 388 AuthenticatorAttachment::None, 389 ) 390 }, 391 ), 392 raw_id, 393 }) 394 }) 395 } else { 396 Err(Error::missing_field( 397 CLIENT_EXTENSION_RESULTS, 398 )) 399 } 400 }) 401 }) 402 }) 403 }) 404 } 405 } 406 /// `authenticatorAttachment` key. 407 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment"; 408 /// `authenticatorData` key. 409 const AUTHENTICATOR_DATA: &str = "authenticatorData"; 410 /// `clientDataJSON` key. 411 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 412 /// `clientExtensionResults` key. 413 const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults"; 414 /// `id` key. 415 const ID: &str = "id"; 416 /// `signature` key. 417 const SIGNATURE: &str = "signature"; 418 /// `type` key. 419 const TYPE: &str = "type"; 420 /// `userHandle` key. 421 const USER_HANDLE: &str = "userHandle"; 422 /// Fields. 423 const FIELDS: &[&str; 8] = &[ 424 AUTHENTICATOR_ATTACHMENT, 425 AUTHENTICATOR_DATA, 426 CLIENT_DATA_JSON, 427 CLIENT_EXTENSION_RESULTS, 428 ID, 429 SIGNATURE, 430 TYPE, 431 USER_HANDLE, 432 ]; 433 deserializer.deserialize_struct( 434 "CustomAuthentication", 435 FIELDS, 436 CustomAuthenticationVisitor(PhantomData), 437 ) 438 } 439 } 440 /// `CustomAuthentication` with a required `UserHandle`. 441 pub type PasskeyCustomAuthentication<T> = CustomAuthentication<UserHandle<T>>; 442 /// `CustomAuthentication` with a required `UserHandle64`. 443 pub type PasskeyCustomAuthentication64 = CustomAuthentication<UserHandle64>; 444 /// `CustomAuthentication` with a required `UserHandle16`. 445 pub type PasskeyCustomAuthentication16 = CustomAuthentication<UserHandle16>; 446 #[cfg(test)] 447 mod tests { 448 use super::{ 449 super::{ 450 super::super::request::register::USER_HANDLE_MIN_LEN, AuthenticatorAttachment, 451 UserHandle, 452 }, 453 AuthenticationRelaxed, CustomAuthentication, PasskeyAuthenticationRelaxed, 454 PasskeyCustomAuthentication, 455 }; 456 use data_encoding::BASE64URL_NOPAD; 457 use rsa::sha2::{Digest as _, Sha256}; 458 use serde::de::{Error as _, Unexpected}; 459 use serde_json::Error; 460 #[test] 461 fn eddsa_authentication_deserialize_data_mismatch() { 462 let c_data_json = serde_json::json!({}).to_string(); 463 let auth_data = [ 464 // `rpIdHash`. 465 0, 466 0, 467 0, 468 0, 469 0, 470 0, 471 0, 472 0, 473 0, 474 0, 475 0, 476 0, 477 0, 478 0, 479 0, 480 0, 481 0, 482 0, 483 0, 484 0, 485 0, 486 0, 487 0, 488 0, 489 0, 490 0, 491 0, 492 0, 493 0, 494 0, 495 0, 496 0, 497 // `flags`. 498 0b0000_0101, 499 // `signCount`. 500 0, 501 0, 502 0, 503 0, 504 ]; 505 let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes()); 506 let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice()); 507 let b64_sig = BASE64URL_NOPAD.encode([].as_slice()); 508 let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice()); 509 // Base case is valid. 510 assert!( 511 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 512 serde_json::json!({ 513 "id": "AAAAAAAAAAAAAAAAAAAAAA", 514 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 515 "response": { 516 "clientDataJSON": b64_cdata, 517 "authenticatorData": b64_adata, 518 "signature": b64_sig, 519 "userHandle": b64_user, 520 }, 521 "authenticatorAttachment": "cross-platform", 522 "clientExtensionResults": {}, 523 "type": "public-key" 524 }) 525 .to_string() 526 .as_str() 527 ) 528 .map_or(false, |auth| auth.0.response.client_data_json 529 == c_data_json.as_bytes() 530 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data 531 && auth.0.response.authenticator_data_and_c_data_hash[37..] 532 == *Sha256::digest(c_data_json.as_bytes()).as_slice() 533 && matches!( 534 auth.0.authenticator_attachment, 535 AuthenticatorAttachment::CrossPlatform 536 )) 537 ); 538 // `id` and `rawId` mismatch. 539 let mut err = Error::invalid_value( 540 Unexpected::Bytes( 541 BASE64URL_NOPAD 542 .decode("ABABABABABABABABABABAA".as_bytes()) 543 .unwrap() 544 .as_slice(), 545 ), 546 &format!("id and rawId to match: CredentialId({:?})", [0; 16]).as_str(), 547 ) 548 .to_string() 549 .into_bytes(); 550 assert_eq!( 551 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 552 serde_json::json!({ 553 "id": "AAAAAAAAAAAAAAAAAAAAAA", 554 "rawId": "ABABABABABABABABABABAA", 555 "response": { 556 "clientDataJSON": b64_cdata, 557 "authenticatorData": b64_adata, 558 "signature": b64_sig, 559 "userHandle": b64_user, 560 }, 561 "authenticatorAttachment": "cross-platform", 562 "clientExtensionResults": {}, 563 "type": "public-key" 564 }) 565 .to_string() 566 .as_str() 567 ) 568 .unwrap_err() 569 .to_string() 570 .into_bytes()[..err.len()], 571 err 572 ); 573 // missing `id`. 574 err = Error::missing_field("id").to_string().into_bytes(); 575 assert_eq!( 576 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 577 serde_json::json!({ 578 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 579 "response": { 580 "clientDataJSON": b64_cdata, 581 "authenticatorData": b64_adata, 582 "signature": b64_sig, 583 "userHandle": b64_user, 584 }, 585 "authenticatorAttachment": "cross-platform", 586 "clientExtensionResults": {}, 587 "type": "public-key" 588 }) 589 .to_string() 590 .as_str() 591 ) 592 .unwrap_err() 593 .to_string() 594 .into_bytes()[..err.len()], 595 err 596 ); 597 // `null` `id`. 598 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 599 .to_string() 600 .into_bytes(); 601 assert_eq!( 602 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 603 serde_json::json!({ 604 "id": null, 605 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 606 "response": { 607 "clientDataJSON": b64_cdata, 608 "authenticatorData": b64_adata, 609 "signature": b64_sig, 610 "userHandle": b64_user, 611 }, 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 `rawId`. 624 assert!( 625 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 626 serde_json::json!({ 627 "id": "AAAAAAAAAAAAAAAAAAAAAA", 628 "response": { 629 "clientDataJSON": b64_cdata, 630 "authenticatorData": b64_adata, 631 "signature": b64_sig, 632 "userHandle": b64_user, 633 }, 634 "clientExtensionResults": {}, 635 "type": "public-key" 636 }) 637 .to_string() 638 .as_str() 639 ) 640 .is_ok() 641 ); 642 // `null` `rawId`. 643 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 644 .to_string() 645 .into_bytes(); 646 assert_eq!( 647 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 648 serde_json::json!({ 649 "id": "AAAAAAAAAAAAAAAAAAAAAA", 650 "rawId": null, 651 "response": { 652 "clientDataJSON": b64_cdata, 653 "authenticatorData": b64_adata, 654 "signature": b64_sig, 655 "userHandle": b64_user, 656 }, 657 "clientExtensionResults": {}, 658 "type": "public-key" 659 }) 660 .to_string() 661 .as_str() 662 ) 663 .unwrap_err() 664 .to_string() 665 .into_bytes()[..err.len()], 666 err 667 ); 668 // Missing `authenticatorData`. 669 err = Error::missing_field("authenticatorData") 670 .to_string() 671 .into_bytes(); 672 assert_eq!( 673 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 674 serde_json::json!({ 675 "id": "AAAAAAAAAAAAAAAAAAAAAA", 676 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 677 "response": { 678 "clientDataJSON": b64_cdata, 679 "signature": b64_sig, 680 "userHandle": b64_user, 681 }, 682 "clientExtensionResults": {}, 683 "type": "public-key" 684 }) 685 .to_string() 686 .as_str() 687 ) 688 .unwrap_err() 689 .to_string() 690 .into_bytes()[..err.len()], 691 err 692 ); 693 // `null` `authenticatorData`. 694 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData") 695 .to_string() 696 .into_bytes(); 697 assert_eq!( 698 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 699 serde_json::json!({ 700 "id": "AAAAAAAAAAAAAAAAAAAAAA", 701 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 702 "response": { 703 "clientDataJSON": b64_cdata, 704 "authenticatorData": null, 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 // Missing `signature`. 720 err = Error::missing_field("signature").to_string().into_bytes(); 721 assert_eq!( 722 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 723 serde_json::json!({ 724 "id": "AAAAAAAAAAAAAAAAAAAAAA", 725 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 726 "response": { 727 "clientDataJSON": b64_cdata, 728 "authenticatorData": b64_adata, 729 "userHandle": b64_user, 730 }, 731 "clientExtensionResults": {}, 732 "type": "public-key" 733 }) 734 .to_string() 735 .as_str() 736 ) 737 .unwrap_err() 738 .to_string() 739 .into_bytes()[..err.len()], 740 err 741 ); 742 // `null` `signature`. 743 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 744 .to_string() 745 .into_bytes(); 746 assert_eq!( 747 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 748 serde_json::json!({ 749 "id": "AAAAAAAAAAAAAAAAAAAAAA", 750 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 751 "response": { 752 "clientDataJSON": b64_cdata, 753 "authenticatorData": b64_adata, 754 "signature": null, 755 "userHandle": b64_user, 756 }, 757 "clientExtensionResults": {}, 758 "type": "public-key" 759 }) 760 .to_string() 761 .as_str() 762 ) 763 .unwrap_err() 764 .to_string() 765 .into_bytes()[..err.len()], 766 err 767 ); 768 // Missing `userHandle`. 769 assert!( 770 serde_json::from_str::< 771 AuthenticationRelaxed<Option<UserHandle<[u8; USER_HANDLE_MIN_LEN]>>>, 772 >( 773 serde_json::json!({ 774 "id": "AAAAAAAAAAAAAAAAAAAAAA", 775 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 776 "response": { 777 "clientDataJSON": b64_cdata, 778 "authenticatorData": b64_adata, 779 "signature": b64_sig, 780 }, 781 "clientExtensionResults": {}, 782 "type": "public-key" 783 }) 784 .to_string() 785 .as_str() 786 ) 787 .is_ok() 788 ); 789 // `null` `userHandle`. 790 assert!( 791 serde_json::from_str::< 792 AuthenticationRelaxed<Option<UserHandle<[u8; USER_HANDLE_MIN_LEN]>>>, 793 >( 794 serde_json::json!({ 795 "id": "AAAAAAAAAAAAAAAAAAAAAA", 796 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 797 "response": { 798 "clientDataJSON": b64_cdata, 799 "authenticatorData": b64_adata, 800 "signature": b64_sig, 801 "userHandle": null, 802 }, 803 "clientExtensionResults": {}, 804 "type": "public-key" 805 }) 806 .to_string() 807 .as_str() 808 ) 809 .is_ok() 810 ); 811 // `null` `authenticatorAttachment`. 812 assert!( 813 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 814 serde_json::json!({ 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 "authenticatorAttachment": null, 824 "clientExtensionResults": {}, 825 "type": "public-key" 826 }) 827 .to_string() 828 .as_str() 829 ) 830 .map_or(false, |auth| matches!( 831 auth.0.authenticator_attachment, 832 AuthenticatorAttachment::None 833 )) 834 ); 835 // Unknown `authenticatorAttachment`. 836 err = Error::invalid_value( 837 Unexpected::Str("Platform"), 838 &"'platform' or 'cross-platform'", 839 ) 840 .to_string() 841 .into_bytes(); 842 assert_eq!( 843 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 844 serde_json::json!({ 845 "id": "AAAAAAAAAAAAAAAAAAAAAA", 846 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 847 "response": { 848 "clientDataJSON": b64_cdata, 849 "authenticatorData": b64_adata, 850 "signature": b64_sig, 851 "userHandle": b64_user, 852 }, 853 "authenticatorAttachment": "Platform", 854 "clientExtensionResults": {}, 855 "type": "public-key" 856 }) 857 .to_string() 858 .as_str() 859 ) 860 .unwrap_err() 861 .to_string() 862 .into_bytes()[..err.len()], 863 err 864 ); 865 // Missing `clientDataJSON`. 866 err = Error::missing_field("clientDataJSON") 867 .to_string() 868 .into_bytes(); 869 assert_eq!( 870 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 871 serde_json::json!({ 872 "id": "AAAAAAAAAAAAAAAAAAAAAA", 873 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 874 "response": { 875 "authenticatorData": b64_adata, 876 "signature": b64_sig, 877 "userHandle": b64_user, 878 }, 879 "clientExtensionResults": {}, 880 "type": "public-key" 881 }) 882 .to_string() 883 .as_str() 884 ) 885 .unwrap_err() 886 .to_string() 887 .into_bytes()[..err.len()], 888 err 889 ); 890 // `null` `clientDataJSON`. 891 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 892 .to_string() 893 .into_bytes(); 894 assert_eq!( 895 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 896 serde_json::json!({ 897 "id": "AAAAAAAAAAAAAAAAAAAAAA", 898 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 899 "response": { 900 "clientDataJSON": null, 901 "authenticatorData": b64_adata, 902 "signature": b64_sig, 903 "userHandle": b64_user, 904 }, 905 "clientExtensionResults": {}, 906 "type": "public-key" 907 }) 908 .to_string() 909 .as_str() 910 ) 911 .unwrap_err() 912 .to_string() 913 .into_bytes()[..err.len()], 914 err 915 ); 916 // Missing `response`. 917 err = Error::missing_field("response").to_string().into_bytes(); 918 assert_eq!( 919 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 920 serde_json::json!({ 921 "id": "AAAAAAAAAAAAAAAAAAAAAA", 922 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 923 "clientExtensionResults": {}, 924 "type": "public-key" 925 }) 926 .to_string() 927 .as_str() 928 ) 929 .unwrap_err() 930 .to_string() 931 .into_bytes()[..err.len()], 932 err 933 ); 934 // `null` `response`. 935 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion") 936 .to_string() 937 .into_bytes(); 938 assert_eq!( 939 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 940 serde_json::json!({ 941 "id": "AAAAAAAAAAAAAAAAAAAAAA", 942 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 943 "response": null, 944 "clientExtensionResults": {}, 945 "type": "public-key" 946 }) 947 .to_string() 948 .as_str() 949 ) 950 .unwrap_err() 951 .to_string() 952 .into_bytes()[..err.len()], 953 err 954 ); 955 // Empty `response`. 956 err = Error::missing_field("clientDataJSON") 957 .to_string() 958 .into_bytes(); 959 assert_eq!( 960 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 961 serde_json::json!({ 962 "id": "AAAAAAAAAAAAAAAAAAAAAA", 963 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 964 "response": {}, 965 "clientExtensionResults": {}, 966 "type": "public-key" 967 }) 968 .to_string() 969 .as_str() 970 ) 971 .unwrap_err() 972 .to_string() 973 .into_bytes()[..err.len()], 974 err 975 ); 976 // Missing `clientExtensionResults`. 977 assert!( 978 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 979 serde_json::json!({ 980 "id": "AAAAAAAAAAAAAAAAAAAAAA", 981 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 982 "response": { 983 "clientDataJSON": b64_cdata, 984 "authenticatorData": b64_adata, 985 "signature": b64_sig, 986 "userHandle": b64_user, 987 }, 988 "type": "public-key" 989 }) 990 .to_string() 991 .as_str() 992 ) 993 .is_ok() 994 ); 995 // `null` `clientExtensionResults`. 996 assert!( 997 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 998 serde_json::json!({ 999 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1000 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1001 "response": { 1002 "clientDataJSON": b64_cdata, 1003 "authenticatorData": b64_adata, 1004 "signature": b64_sig, 1005 "userHandle": b64_user, 1006 }, 1007 "clientExtensionResults": null, 1008 "type": "public-key" 1009 }) 1010 .to_string() 1011 .as_str() 1012 ) 1013 .is_ok() 1014 ); 1015 // Missing `type`. 1016 assert!( 1017 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1018 serde_json::json!({ 1019 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1020 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1021 "response": { 1022 "clientDataJSON": b64_cdata, 1023 "authenticatorData": b64_adata, 1024 "signature": b64_sig, 1025 "userHandle": b64_user, 1026 }, 1027 "clientExtensionResults": {}, 1028 }) 1029 .to_string() 1030 .as_str() 1031 ) 1032 .is_ok() 1033 ); 1034 // `null` `type`. 1035 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 1036 .to_string() 1037 .into_bytes(); 1038 assert_eq!( 1039 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1040 serde_json::json!({ 1041 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1042 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1043 "response": { 1044 "clientDataJSON": b64_cdata, 1045 "authenticatorData": b64_adata, 1046 "signature": b64_sig, 1047 "userHandle": b64_user, 1048 }, 1049 "clientExtensionResults": {}, 1050 "type": null 1051 }) 1052 .to_string() 1053 .as_str() 1054 ) 1055 .unwrap_err() 1056 .to_string() 1057 .into_bytes()[..err.len()], 1058 err 1059 ); 1060 // Not exactly `public-type` `type`. 1061 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 1062 .to_string() 1063 .into_bytes(); 1064 assert_eq!( 1065 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1066 serde_json::json!({ 1067 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1068 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1069 "response": { 1070 "clientDataJSON": b64_cdata, 1071 "authenticatorData": b64_adata, 1072 "signature": b64_sig, 1073 "userHandle": b64_user, 1074 }, 1075 "clientExtensionResults": {}, 1076 "type": "Public-key" 1077 }) 1078 .to_string() 1079 .as_str() 1080 ) 1081 .unwrap_err() 1082 .to_string() 1083 .into_bytes()[..err.len()], 1084 err 1085 ); 1086 // `null`. 1087 err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential") 1088 .to_string() 1089 .into_bytes(); 1090 assert_eq!( 1091 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1092 serde_json::json!(null).to_string().as_str() 1093 ) 1094 .unwrap_err() 1095 .to_string() 1096 .into_bytes()[..err.len()], 1097 err 1098 ); 1099 // Empty. 1100 err = Error::missing_field("response").to_string().into_bytes(); 1101 assert_eq!( 1102 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1103 serde_json::json!({}).to_string().as_str() 1104 ) 1105 .unwrap_err() 1106 .to_string() 1107 .into_bytes()[..err.len()], 1108 err 1109 ); 1110 // Unknown field in `response`. 1111 assert!( 1112 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1113 serde_json::json!({ 1114 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1115 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1116 "response": { 1117 "clientDataJSON": b64_cdata, 1118 "authenticatorData": b64_adata, 1119 "signature": b64_sig, 1120 "userHandle": b64_user, 1121 "foo": true, 1122 }, 1123 "clientExtensionResults": {}, 1124 "type": "public-key" 1125 }) 1126 .to_string() 1127 .as_str() 1128 ) 1129 .is_ok() 1130 ); 1131 // Duplicate field in `response`. 1132 err = Error::duplicate_field("userHandle") 1133 .to_string() 1134 .into_bytes(); 1135 assert_eq!( 1136 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1137 format!( 1138 "{{ 1139 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1140 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1141 \"response\": {{ 1142 \"clientDataJSON\": \"{b64_cdata}\", 1143 \"authenticatorData\": \"{b64_adata}\", 1144 \"signature\": \"{b64_sig}\", 1145 \"userHandle\": \"{b64_user}\", 1146 \"userHandle\": \"{b64_user}\" 1147 }}, 1148 \"clientExtensionResults\": {{}}, 1149 \"type\": \"public-key\" 1150 1151 }}" 1152 ) 1153 .as_str() 1154 ) 1155 .unwrap_err() 1156 .to_string() 1157 .into_bytes()[..err.len()], 1158 err 1159 ); 1160 // Unknown field in `PublicKeyCredential`. 1161 assert!( 1162 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1163 serde_json::json!({ 1164 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1165 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1166 "response": { 1167 "clientDataJSON": b64_cdata, 1168 "authenticatorData": b64_adata, 1169 "signature": b64_sig, 1170 "userHandle": b64_user, 1171 }, 1172 "clientExtensionResults": {}, 1173 "type": "public-key", 1174 "foo": true, 1175 }) 1176 .to_string() 1177 .as_str() 1178 ) 1179 .is_ok() 1180 ); 1181 // Duplicate field in `PublicKeyCredential`. 1182 err = Error::duplicate_field("id").to_string().into_bytes(); 1183 assert_eq!( 1184 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1185 format!( 1186 "{{ 1187 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1188 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1189 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1190 \"response\": {{ 1191 \"clientDataJSON\": \"{b64_cdata}\", 1192 \"authenticatorData\": \"{b64_adata}\", 1193 \"signature\": \"{b64_sig}\", 1194 \"userHandle\": \"{b64_user}\" 1195 }}, 1196 \"clientExtensionResults\": {{}}, 1197 \"type\": \"public-key\" 1198 1199 }}" 1200 ) 1201 .as_str() 1202 ) 1203 .unwrap_err() 1204 .to_string() 1205 .into_bytes()[..err.len()], 1206 err 1207 ); 1208 // Base case is valid. 1209 assert!( 1210 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1211 serde_json::json!({ 1212 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1213 "clientDataJSON": b64_cdata, 1214 "authenticatorData": b64_adata, 1215 "signature": b64_sig, 1216 "userHandle": b64_user, 1217 "authenticatorAttachment": "cross-platform", 1218 "clientExtensionResults": {}, 1219 "type": "public-key" 1220 }) 1221 .to_string() 1222 .as_str() 1223 ) 1224 .map_or(false, |auth| auth.0.response.client_data_json 1225 == c_data_json.as_bytes() 1226 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data 1227 && auth.0.response.authenticator_data_and_c_data_hash[37..] 1228 == *Sha256::digest(c_data_json.as_bytes()).as_slice() 1229 && matches!( 1230 auth.0.authenticator_attachment, 1231 AuthenticatorAttachment::CrossPlatform 1232 )) 1233 ); 1234 // missing `id`. 1235 err = Error::missing_field("id").to_string().into_bytes(); 1236 assert_eq!( 1237 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1238 serde_json::json!({ 1239 "clientDataJSON": b64_cdata, 1240 "authenticatorData": b64_adata, 1241 "signature": b64_sig, 1242 "userHandle": b64_user, 1243 "authenticatorAttachment": "cross-platform", 1244 "clientExtensionResults": {}, 1245 "type": "public-key" 1246 }) 1247 .to_string() 1248 .as_str() 1249 ) 1250 .unwrap_err() 1251 .to_string() 1252 .into_bytes()[..err.len()], 1253 err 1254 ); 1255 // `null` `id`. 1256 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 1257 .to_string() 1258 .into_bytes(); 1259 assert_eq!( 1260 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1261 serde_json::json!({ 1262 "id": null, 1263 "clientDataJSON": b64_cdata, 1264 "authenticatorData": b64_adata, 1265 "signature": b64_sig, 1266 "userHandle": b64_user, 1267 "clientExtensionResults": {}, 1268 "type": "public-key" 1269 }) 1270 .to_string() 1271 .as_str() 1272 ) 1273 .unwrap_err() 1274 .to_string() 1275 .into_bytes()[..err.len()], 1276 err 1277 ); 1278 // Missing `authenticatorData`. 1279 err = Error::missing_field("authenticatorData") 1280 .to_string() 1281 .into_bytes(); 1282 assert_eq!( 1283 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1284 serde_json::json!({ 1285 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1286 "clientDataJSON": b64_cdata, 1287 "signature": b64_sig, 1288 "userHandle": b64_user, 1289 "clientExtensionResults": {}, 1290 "type": "public-key" 1291 }) 1292 .to_string() 1293 .as_str() 1294 ) 1295 .unwrap_err() 1296 .to_string() 1297 .into_bytes()[..err.len()], 1298 err 1299 ); 1300 // `null` `authenticatorData`. 1301 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData") 1302 .to_string() 1303 .into_bytes(); 1304 assert_eq!( 1305 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1306 serde_json::json!({ 1307 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1308 "clientDataJSON": b64_cdata, 1309 "authenticatorData": null, 1310 "signature": b64_sig, 1311 "userHandle": b64_user, 1312 "clientExtensionResults": {}, 1313 "type": "public-key" 1314 }) 1315 .to_string() 1316 .as_str() 1317 ) 1318 .unwrap_err() 1319 .to_string() 1320 .into_bytes()[..err.len()], 1321 err 1322 ); 1323 // Missing `signature`. 1324 err = Error::missing_field("signature").to_string().into_bytes(); 1325 assert_eq!( 1326 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1327 serde_json::json!({ 1328 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1329 "clientDataJSON": b64_cdata, 1330 "authenticatorData": b64_adata, 1331 "userHandle": b64_user, 1332 "clientExtensionResults": {}, 1333 "type": "public-key" 1334 }) 1335 .to_string() 1336 .as_str() 1337 ) 1338 .unwrap_err() 1339 .to_string() 1340 .into_bytes()[..err.len()], 1341 err 1342 ); 1343 // `null` `signature`. 1344 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 1345 .to_string() 1346 .into_bytes(); 1347 assert_eq!( 1348 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1349 serde_json::json!({ 1350 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1351 "clientDataJSON": b64_cdata, 1352 "authenticatorData": b64_adata, 1353 "signature": null, 1354 "userHandle": b64_user, 1355 "clientExtensionResults": {}, 1356 "type": "public-key" 1357 }) 1358 .to_string() 1359 .as_str() 1360 ) 1361 .unwrap_err() 1362 .to_string() 1363 .into_bytes()[..err.len()], 1364 err 1365 ); 1366 // Missing `userHandle`. 1367 assert!( 1368 serde_json::from_str::< 1369 CustomAuthentication<Option<UserHandle<[u8; USER_HANDLE_MIN_LEN]>>>, 1370 >( 1371 serde_json::json!({ 1372 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1373 "clientDataJSON": b64_cdata, 1374 "authenticatorData": b64_adata, 1375 "signature": b64_sig, 1376 "clientExtensionResults": {}, 1377 "type": "public-key" 1378 }) 1379 .to_string() 1380 .as_str() 1381 ) 1382 .is_ok() 1383 ); 1384 // `null` `userHandle`. 1385 assert!( 1386 serde_json::from_str::< 1387 CustomAuthentication<Option<UserHandle<[u8; USER_HANDLE_MIN_LEN]>>>, 1388 >( 1389 serde_json::json!({ 1390 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1391 "clientDataJSON": b64_cdata, 1392 "authenticatorData": b64_adata, 1393 "signature": b64_sig, 1394 "userHandle": null, 1395 "clientExtensionResults": {}, 1396 "type": "public-key" 1397 }) 1398 .to_string() 1399 .as_str() 1400 ) 1401 .is_ok() 1402 ); 1403 // `null` `authenticatorAttachment`. 1404 assert!( 1405 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1406 serde_json::json!({ 1407 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1408 "clientDataJSON": b64_cdata, 1409 "authenticatorData": b64_adata, 1410 "signature": b64_sig, 1411 "userHandle": b64_user, 1412 "authenticatorAttachment": null, 1413 "clientExtensionResults": {}, 1414 "type": "public-key" 1415 }) 1416 .to_string() 1417 .as_str() 1418 ) 1419 .map_or(false, |auth| matches!( 1420 auth.0.authenticator_attachment, 1421 AuthenticatorAttachment::None 1422 )) 1423 ); 1424 // Unknown `authenticatorAttachment`. 1425 err = Error::invalid_value( 1426 Unexpected::Str("Platform"), 1427 &"'platform' or 'cross-platform'", 1428 ) 1429 .to_string() 1430 .into_bytes(); 1431 assert_eq!( 1432 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1433 serde_json::json!({ 1434 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1435 "clientDataJSON": b64_cdata, 1436 "authenticatorData": b64_adata, 1437 "signature": b64_sig, 1438 "userHandle": b64_user, 1439 "authenticatorAttachment": "Platform", 1440 "clientExtensionResults": {}, 1441 "type": "public-key" 1442 }) 1443 .to_string() 1444 .as_str() 1445 ) 1446 .unwrap_err() 1447 .to_string() 1448 .into_bytes()[..err.len()], 1449 err 1450 ); 1451 // Missing `clientDataJSON`. 1452 err = Error::missing_field("clientDataJSON") 1453 .to_string() 1454 .into_bytes(); 1455 assert_eq!( 1456 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1457 serde_json::json!({ 1458 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1459 "authenticatorData": b64_adata, 1460 "signature": b64_sig, 1461 "userHandle": b64_user, 1462 "clientExtensionResults": {}, 1463 "type": "public-key" 1464 }) 1465 .to_string() 1466 .as_str() 1467 ) 1468 .unwrap_err() 1469 .to_string() 1470 .into_bytes()[..err.len()], 1471 err 1472 ); 1473 // `null` `clientDataJSON`. 1474 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 1475 .to_string() 1476 .into_bytes(); 1477 assert_eq!( 1478 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1479 serde_json::json!({ 1480 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1481 "clientDataJSON": null, 1482 "authenticatorData": b64_adata, 1483 "signature": b64_sig, 1484 "userHandle": b64_user, 1485 "clientExtensionResults": {}, 1486 "type": "public-key" 1487 }) 1488 .to_string() 1489 .as_str() 1490 ) 1491 .unwrap_err() 1492 .to_string() 1493 .into_bytes()[..err.len()], 1494 err 1495 ); 1496 // Empty. 1497 err = Error::missing_field("authenticatorData") 1498 .to_string() 1499 .into_bytes(); 1500 assert_eq!( 1501 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1502 serde_json::json!({}).to_string().as_str() 1503 ) 1504 .unwrap_err() 1505 .to_string() 1506 .into_bytes()[..err.len()], 1507 err 1508 ); 1509 // Missing `clientExtensionResults`. 1510 err = Error::missing_field("clientExtensionResults") 1511 .to_string() 1512 .into_bytes(); 1513 assert_eq!( 1514 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1515 serde_json::json!({ 1516 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1517 "clientDataJSON": b64_cdata, 1518 "authenticatorData": b64_adata, 1519 "signature": b64_sig, 1520 "userHandle": b64_user, 1521 "type": "public-key" 1522 }) 1523 .to_string() 1524 .as_str() 1525 ) 1526 .unwrap_err() 1527 .to_string() 1528 .into_bytes()[..err.len()], 1529 err 1530 ); 1531 // `null` `clientExtensionResults`. 1532 err = Error::invalid_type(Unexpected::Other("null"), &"ClientExtensionsOutputs") 1533 .to_string() 1534 .into_bytes(); 1535 assert_eq!( 1536 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1537 serde_json::json!({ 1538 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1539 "clientDataJSON": b64_cdata, 1540 "authenticatorData": b64_adata, 1541 "signature": b64_sig, 1542 "userHandle": b64_user, 1543 "clientExtensionResults": null, 1544 "type": "public-key" 1545 }) 1546 .to_string() 1547 .as_str() 1548 ) 1549 .unwrap_err() 1550 .to_string() 1551 .into_bytes()[..err.len()], 1552 err 1553 ); 1554 assert!( 1555 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1556 serde_json::json!({ 1557 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1558 "clientDataJSON": b64_cdata, 1559 "authenticatorData": b64_adata, 1560 "signature": b64_sig, 1561 "userHandle": b64_user, 1562 "clientExtensionResults": {}, 1563 }) 1564 .to_string() 1565 .as_str() 1566 ) 1567 .is_ok() 1568 ); 1569 // `null` `type`. 1570 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 1571 .to_string() 1572 .into_bytes(); 1573 assert_eq!( 1574 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1575 serde_json::json!({ 1576 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1577 "clientDataJSON": b64_cdata, 1578 "authenticatorData": b64_adata, 1579 "signature": b64_sig, 1580 "userHandle": b64_user, 1581 "clientExtensionResults": {}, 1582 "type": null 1583 }) 1584 .to_string() 1585 .as_str() 1586 ) 1587 .unwrap_err() 1588 .to_string() 1589 .into_bytes()[..err.len()], 1590 err 1591 ); 1592 // Not exactly `public-type` `type`. 1593 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 1594 .to_string() 1595 .into_bytes(); 1596 assert_eq!( 1597 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1598 serde_json::json!({ 1599 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1600 "clientDataJSON": b64_cdata, 1601 "authenticatorData": b64_adata, 1602 "signature": b64_sig, 1603 "userHandle": b64_user, 1604 "clientExtensionResults": {}, 1605 "type": "Public-key" 1606 }) 1607 .to_string() 1608 .as_str() 1609 ) 1610 .unwrap_err() 1611 .to_string() 1612 .into_bytes()[..err.len()], 1613 err 1614 ); 1615 // `null`. 1616 err = Error::invalid_type(Unexpected::Other("null"), &"CustomAuthentication") 1617 .to_string() 1618 .into_bytes(); 1619 assert_eq!( 1620 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1621 serde_json::json!(null).to_string().as_str() 1622 ) 1623 .unwrap_err() 1624 .to_string() 1625 .into_bytes()[..err.len()], 1626 err 1627 ); 1628 // Unknown field. 1629 err = Error::unknown_field( 1630 "foo", 1631 [ 1632 "authenticatorAttachment", 1633 "authenticatorData", 1634 "clientDataJSON", 1635 "clientExtensionResults", 1636 "id", 1637 "signature", 1638 "type", 1639 "userHandle", 1640 ] 1641 .as_slice(), 1642 ) 1643 .to_string() 1644 .into_bytes(); 1645 assert_eq!( 1646 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1647 serde_json::json!({ 1648 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1649 "clientDataJSON": b64_cdata, 1650 "authenticatorData": b64_adata, 1651 "signature": b64_sig, 1652 "userHandle": b64_user, 1653 "foo": true, 1654 "clientExtensionResults": {}, 1655 "type": "public-key" 1656 }) 1657 .to_string() 1658 .as_str() 1659 ) 1660 .unwrap_err() 1661 .to_string() 1662 .into_bytes()[..err.len()], 1663 err 1664 ); 1665 // Duplicate field. 1666 err = Error::duplicate_field("userHandle") 1667 .to_string() 1668 .into_bytes(); 1669 assert_eq!( 1670 serde_json::from_str::<PasskeyCustomAuthentication<[u8; USER_HANDLE_MIN_LEN]>>( 1671 format!( 1672 "{{ 1673 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1674 \"clientDataJSON\": \"{b64_cdata}\", 1675 \"authenticatorData\": \"{b64_adata}\", 1676 \"signature\": \"{b64_sig}\", 1677 \"userHandle\": \"{b64_user}\", 1678 \"userHandle\": \"{b64_user}\" 1679 \"clientExtensionResults\": {{}}, 1680 \"type\": \"public-key\" 1681 1682 }}" 1683 ) 1684 .as_str() 1685 ) 1686 .unwrap_err() 1687 .to_string() 1688 .into_bytes()[..err.len()], 1689 err 1690 ); 1691 } 1692 #[test] 1693 fn client_extensions() { 1694 let c_data_json = serde_json::json!({}).to_string(); 1695 let auth_data = [ 1696 // `rpIdHash`. 1697 0, 1698 0, 1699 0, 1700 0, 1701 0, 1702 0, 1703 0, 1704 0, 1705 0, 1706 0, 1707 0, 1708 0, 1709 0, 1710 0, 1711 0, 1712 0, 1713 0, 1714 0, 1715 0, 1716 0, 1717 0, 1718 0, 1719 0, 1720 0, 1721 0, 1722 0, 1723 0, 1724 0, 1725 0, 1726 0, 1727 0, 1728 0, 1729 // `flags`. 1730 0b0000_0101, 1731 // `signCount`. 1732 0, 1733 0, 1734 0, 1735 0, 1736 ]; 1737 let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes()); 1738 let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice()); 1739 let b64_sig = BASE64URL_NOPAD.encode([].as_slice()); 1740 let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice()); 1741 // Base case is valid. 1742 assert!( 1743 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1744 serde_json::json!({ 1745 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1746 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1747 "response": { 1748 "clientDataJSON": b64_cdata, 1749 "authenticatorData": b64_adata, 1750 "signature": b64_sig, 1751 "userHandle": b64_user, 1752 }, 1753 "authenticatorAttachment": "cross-platform", 1754 "clientExtensionResults": {}, 1755 "type": "public-key" 1756 }) 1757 .to_string() 1758 .as_str() 1759 ) 1760 .map_or(false, |auth| auth.0.response.client_data_json 1761 == c_data_json.as_bytes() 1762 && auth.0.response.authenticator_data_and_c_data_hash[..37] == auth_data 1763 && auth.0.response.authenticator_data_and_c_data_hash[37..] 1764 == *Sha256::digest(c_data_json.as_bytes()).as_slice() 1765 && matches!( 1766 auth.0.authenticator_attachment, 1767 AuthenticatorAttachment::CrossPlatform 1768 )) 1769 ); 1770 // `null` `prf`. 1771 assert!( 1772 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1773 serde_json::json!({ 1774 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1775 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1776 "response": { 1777 "clientDataJSON": b64_cdata, 1778 "authenticatorData": b64_adata, 1779 "signature": b64_sig, 1780 "userHandle": b64_user, 1781 }, 1782 "clientExtensionResults": { 1783 "prf": null 1784 }, 1785 "type": "public-key" 1786 }) 1787 .to_string() 1788 .as_str() 1789 ) 1790 .is_ok() 1791 ); 1792 // Unknown `clientExtensionResults`. 1793 assert!( 1794 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1795 serde_json::json!({ 1796 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1797 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1798 "response": { 1799 "clientDataJSON": b64_cdata, 1800 "authenticatorData": b64_adata, 1801 "signature": b64_sig, 1802 "userHandle": b64_user, 1803 }, 1804 "clientExtensionResults": { 1805 "Prf": null 1806 }, 1807 "type": "public-key" 1808 }) 1809 .to_string() 1810 .as_str() 1811 ) 1812 .is_ok() 1813 ); 1814 // Duplicate field. 1815 let mut err = Error::duplicate_field("prf").to_string().into_bytes(); 1816 assert_eq!( 1817 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1818 format!( 1819 "{{ 1820 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1821 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1822 \"response\": {{ 1823 \"clientDataJSON\": \"{b64_cdata}\", 1824 \"authenticatorData\": \"{b64_adata}\", 1825 \"signature\": \"{b64_sig}\", 1826 \"userHandle\": \"{b64_user}\" 1827 }}, 1828 \"clientExtensionResults\": {{ 1829 \"prf\": null, 1830 \"prf\": null 1831 }}, 1832 \"type\": \"public-key\" 1833 }}" 1834 ) 1835 .as_str() 1836 ) 1837 .unwrap_err() 1838 .to_string() 1839 .into_bytes()[..err.len()], 1840 err 1841 ); 1842 // `null` `results`. 1843 assert!( 1844 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1845 serde_json::json!({ 1846 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1847 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1848 "response": { 1849 "clientDataJSON": b64_cdata, 1850 "authenticatorData": b64_adata, 1851 "signature": b64_sig, 1852 "userHandle": b64_user, 1853 }, 1854 "clientExtensionResults": { 1855 "prf": { 1856 "results": null, 1857 } 1858 }, 1859 "type": "public-key" 1860 }) 1861 .to_string() 1862 .as_str() 1863 ) 1864 .is_ok() 1865 ); 1866 // Duplicate field in `prf`. 1867 err = Error::duplicate_field("results").to_string().into_bytes(); 1868 assert_eq!( 1869 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1870 format!( 1871 "{{ 1872 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1873 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1874 \"response\": {{ 1875 \"clientDataJSON\": \"{b64_cdata}\", 1876 \"authenticatorData\": \"{b64_adata}\", 1877 \"signature\": \"{b64_sig}\", 1878 \"userHandle\": \"{b64_user}\" 1879 }}, 1880 \"clientExtensionResults\": {{ 1881 \"prf\": {{ 1882 \"results\": null, 1883 \"results\": null 1884 }} 1885 }}, 1886 \"type\": \"public-key\" 1887 }}" 1888 ) 1889 .as_str() 1890 ) 1891 .unwrap_err() 1892 .to_string() 1893 .into_bytes()[..err.len()], 1894 err 1895 ); 1896 // Missing `first`. 1897 assert!( 1898 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1899 serde_json::json!({ 1900 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1901 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1902 "response": { 1903 "clientDataJSON": b64_cdata, 1904 "authenticatorData": b64_adata, 1905 "signature": b64_sig, 1906 "userHandle": b64_user, 1907 }, 1908 "clientExtensionResults": { 1909 "prf": { 1910 "results": {}, 1911 } 1912 }, 1913 "type": "public-key" 1914 }) 1915 .to_string() 1916 .as_str() 1917 ) 1918 .is_ok() 1919 ); 1920 // `null` `first`. 1921 assert!( 1922 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1923 serde_json::json!({ 1924 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1925 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1926 "response": { 1927 "clientDataJSON": b64_cdata, 1928 "authenticatorData": b64_adata, 1929 "signature": b64_sig, 1930 "userHandle": b64_user, 1931 }, 1932 "clientExtensionResults": { 1933 "prf": { 1934 "results": { 1935 "first": null 1936 }, 1937 } 1938 }, 1939 "type": "public-key" 1940 }) 1941 .to_string() 1942 .as_str() 1943 ) 1944 .is_ok() 1945 ); 1946 // `null` `second`. 1947 assert!( 1948 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1949 serde_json::json!({ 1950 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1951 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1952 "response": { 1953 "clientDataJSON": b64_cdata, 1954 "authenticatorData": b64_adata, 1955 "signature": b64_sig, 1956 "userHandle": b64_user, 1957 }, 1958 "clientExtensionResults": { 1959 "prf": { 1960 "results": { 1961 "first": null, 1962 "second": null 1963 }, 1964 } 1965 }, 1966 "type": "public-key" 1967 }) 1968 .to_string() 1969 .as_str() 1970 ) 1971 .is_ok() 1972 ); 1973 // Non-`null` `first`. 1974 err = Error::invalid_type(Unexpected::Option, &"null") 1975 .to_string() 1976 .into_bytes(); 1977 assert_eq!( 1978 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 1979 serde_json::json!({ 1980 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1981 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1982 "response": { 1983 "clientDataJSON": b64_cdata, 1984 "authenticatorData": b64_adata, 1985 "signature": b64_sig, 1986 "userHandle": b64_user, 1987 }, 1988 "clientExtensionResults": { 1989 "prf": { 1990 "results": { 1991 "first": "" 1992 }, 1993 } 1994 }, 1995 "type": "public-key" 1996 }) 1997 .to_string() 1998 .as_str() 1999 ) 2000 .unwrap_err() 2001 .to_string() 2002 .into_bytes()[..err.len()], 2003 err 2004 ); 2005 // Non-`null` `second`. 2006 err = Error::invalid_type(Unexpected::Option, &"null") 2007 .to_string() 2008 .into_bytes(); 2009 assert_eq!( 2010 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 2011 serde_json::json!({ 2012 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2013 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2014 "response": { 2015 "clientDataJSON": b64_cdata, 2016 "authenticatorData": b64_adata, 2017 "signature": b64_sig, 2018 "userHandle": b64_user, 2019 }, 2020 "clientExtensionResults": { 2021 "prf": { 2022 "results": { 2023 "first": null, 2024 "second": "" 2025 }, 2026 } 2027 }, 2028 "type": "public-key" 2029 }) 2030 .to_string() 2031 .as_str() 2032 ) 2033 .unwrap_err() 2034 .to_string() 2035 .into_bytes()[..err.len()], 2036 err 2037 ); 2038 // `enabled` is still not allowed. 2039 err = Error::unknown_field("enabled", ["results"].as_slice()) 2040 .to_string() 2041 .into_bytes(); 2042 assert_eq!( 2043 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 2044 serde_json::json!({ 2045 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2046 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2047 "response": { 2048 "clientDataJSON": b64_cdata, 2049 "authenticatorData": b64_adata, 2050 "signature": b64_sig, 2051 "userHandle": b64_user, 2052 }, 2053 "clientExtensionResults": { 2054 "prf": { 2055 "enabled": true, 2056 "results": null 2057 } 2058 }, 2059 "type": "public-key" 2060 }) 2061 .to_string() 2062 .as_str() 2063 ) 2064 .unwrap_err() 2065 .to_string() 2066 .into_bytes()[..err.len()], 2067 err 2068 ); 2069 // Unknown `prf` field. 2070 assert!( 2071 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 2072 serde_json::json!({ 2073 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2074 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2075 "response": { 2076 "clientDataJSON": b64_cdata, 2077 "authenticatorData": b64_adata, 2078 "signature": b64_sig, 2079 "userHandle": b64_user, 2080 }, 2081 "clientExtensionResults": { 2082 "prf": { 2083 "foo": true, 2084 "results": null 2085 } 2086 }, 2087 "type": "public-key" 2088 }) 2089 .to_string() 2090 .as_str() 2091 ) 2092 .is_ok() 2093 ); 2094 // Unknown `results` field. 2095 assert!( 2096 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 2097 serde_json::json!({ 2098 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2099 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2100 "response": { 2101 "clientDataJSON": b64_cdata, 2102 "authenticatorData": b64_adata, 2103 "signature": b64_sig, 2104 "userHandle": b64_user, 2105 }, 2106 "clientExtensionResults": { 2107 "prf": { 2108 "results": { 2109 "first": null, 2110 "Second": null 2111 } 2112 } 2113 }, 2114 "type": "public-key" 2115 }) 2116 .to_string() 2117 .as_str() 2118 ) 2119 .is_ok() 2120 ); 2121 // Duplicate field in `results`. 2122 err = Error::duplicate_field("first").to_string().into_bytes(); 2123 assert_eq!( 2124 serde_json::from_str::<PasskeyAuthenticationRelaxed<[u8; USER_HANDLE_MIN_LEN]>>( 2125 format!( 2126 "{{ 2127 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2128 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2129 \"response\": {{ 2130 \"clientDataJSON\": \"{b64_cdata}\", 2131 \"authenticatorData\": \"{b64_adata}\", 2132 \"signature\": \"{b64_sig}\", 2133 \"userHandle\": \"{b64_user}\" 2134 }}, 2135 \"clientExtensionResults\": {{ 2136 \"prf\": {{ 2137 \"results\": {{ 2138 \"first\": null, 2139 \"first\": null 2140 }} 2141 }} 2142 }}, 2143 \"type\": \"public-key\" 2144 }}" 2145 ) 2146 .as_str() 2147 ) 2148 .unwrap_err() 2149 .to_string() 2150 .into_bytes()[..err.len()], 2151 err 2152 ); 2153 } 2154 }