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