ser.rs (66394B)
1 use super::{ 2 super::{ 3 super::response::ser::{Base64DecodedVal, PublicKeyCredential}, 4 ser::{ 5 AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues, 6 ClientExtensions, 7 }, 8 }, 9 Authentication, AuthenticatorAssertion, UserHandle, 10 error::UnknownCredentialOptions, 11 }; 12 #[cfg(doc)] 13 use super::{AuthenticatorAttachment, CredentialId}; 14 use core::{ 15 fmt::{self, Formatter}, 16 marker::PhantomData, 17 str, 18 }; 19 use rsa::sha2::{Sha256, digest::OutputSizeUser as _}; 20 use serde::{ 21 de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor}, 22 ser::{Serialize, SerializeStruct as _, Serializer}, 23 }; 24 /// Authenticator data. 25 pub(super) struct AuthData(pub Vec<u8>); 26 impl<'e> Deserialize<'e> for AuthData { 27 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 28 where 29 D: Deserializer<'e>, 30 { 31 /// `Visitor` for `AuthData`. 32 struct AuthDataVisitor; 33 impl Visitor<'_> for AuthDataVisitor { 34 type Value = AuthData; 35 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 36 formatter.write_str("AuthenticatorData") 37 } 38 #[expect( 39 clippy::arithmetic_side_effects, 40 reason = "comment justifies its correctness" 41 )] 42 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 43 where 44 E: Error, 45 { 46 base64url_nopad::decode_len(v.len()) 47 .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value")) 48 .and_then(|len| { 49 // The decoded length is 3/4 of the encoded length, so overflow could only occur 50 // if usize::MAX / 4 < 32 => usize::MAX < 128 < u16::MAX; thus overflow is not 51 // possible. 52 // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the 53 // raw authenticator data by `AuthenticatorDataAssertion::new`. 54 let mut auth_data = vec![0; len + Sha256::output_size()]; 55 auth_data.truncate(len); 56 base64url_nopad::decode_buffer_exact(v.as_bytes(), auth_data.as_mut_slice()) 57 .map_err(E::custom) 58 .map(|()| AuthData(auth_data)) 59 }) 60 } 61 } 62 deserializer.deserialize_str(AuthDataVisitor) 63 } 64 } 65 /// `Visitor` for `AuthenticatorAssertion`. 66 /// 67 /// Unknown fields are ignored and only `clientDataJSON`, `authenticatorData`, and `signature` are required iff 68 /// `RELAXED`. 69 pub(super) struct AuthenticatorAssertionVisitor< 70 const RELAXED: bool, 71 const USER_LEN: usize, 72 const DISCOVERABLE: bool, 73 >; 74 impl<'d, const R: bool, const LEN: usize, const DISC: bool> Visitor<'d> 75 for AuthenticatorAssertionVisitor<R, LEN, DISC> 76 where 77 UserHandle<LEN>: Deserialize<'d>, 78 { 79 type Value = AuthenticatorAssertion<LEN, DISC>; 80 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 81 formatter.write_str("AuthenticatorAssertion") 82 } 83 #[expect(clippy::too_many_lines, reason = "107 lines is fine")] 84 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 85 where 86 A: MapAccess<'d>, 87 { 88 /// Fields in `AuthenticatorAssertionResponseJSON`. 89 enum Field<const IGNORE_UNKNOWN: bool> { 90 /// `clientDataJSON`. 91 ClientDataJson, 92 /// `authenticatorData`. 93 AuthenticatorData, 94 /// `signature`. 95 Signature, 96 /// `userHandle`. 97 UserHandle, 98 /// Unknown field. 99 Other, 100 } 101 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 103 where 104 D: Deserializer<'e>, 105 { 106 /// `Visitor` for `Field`. 107 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 108 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 109 type Value = Field<IG>; 110 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 111 write!( 112 formatter, 113 "'{CLIENT_DATA_JSON}', '{AUTHENTICATOR_DATA}', '{SIGNATURE}', or '{USER_HANDLE}'" 114 ) 115 } 116 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 117 where 118 E: Error, 119 { 120 match v { 121 CLIENT_DATA_JSON => Ok(Field::ClientDataJson), 122 AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData), 123 SIGNATURE => Ok(Field::Signature), 124 USER_HANDLE => Ok(Field::UserHandle), 125 _ => { 126 if IG { 127 Ok(Field::Other) 128 } else { 129 Err(E::unknown_field(v, AUTH_ASSERT_FIELDS)) 130 } 131 } 132 } 133 } 134 } 135 deserializer.deserialize_identifier(FieldVisitor::<I>) 136 } 137 } 138 let mut client_data = None; 139 let mut auth = None; 140 let mut sig = None; 141 let mut user_handle = None; 142 while let Some(key) = map.next_key::<Field<R>>()? { 143 match key { 144 Field::ClientDataJson => { 145 if client_data.is_some() { 146 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 147 } 148 client_data = map 149 .next_value::<Base64DecodedVal>() 150 .map(|c_data| c_data.0) 151 .map(Some)?; 152 } 153 Field::AuthenticatorData => { 154 if auth.is_some() { 155 return Err(Error::duplicate_field(AUTHENTICATOR_DATA)); 156 } 157 auth = map 158 .next_value::<AuthData>() 159 .map(|auth_data| Some(auth_data.0))?; 160 } 161 Field::Signature => { 162 if sig.is_some() { 163 return Err(Error::duplicate_field(SIGNATURE)); 164 } 165 sig = map 166 .next_value::<Base64DecodedVal>() 167 .map(|signature| signature.0) 168 .map(Some)?; 169 } 170 Field::UserHandle => { 171 if user_handle.is_some() { 172 return Err(Error::duplicate_field(USER_HANDLE)); 173 } 174 user_handle = map.next_value().map(Some)?; 175 } 176 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 177 } 178 } 179 client_data 180 .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)) 181 .and_then(|client_data_json| { 182 auth.ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA)) 183 .and_then(|authenticator_data| { 184 sig.ok_or_else(|| Error::missing_field(SIGNATURE)) 185 .and_then(|signature| { 186 if DISC { 187 user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE)) 188 } else { 189 user_handle.map_or_else(|| Ok(None), Ok) 190 } 191 .map(|user| { 192 AuthenticatorAssertion::new_inner( 193 client_data_json, 194 authenticator_data, 195 signature, 196 user, 197 ) 198 }) 199 }) 200 }) 201 }) 202 } 203 } 204 /// `"clientDataJSON"`. 205 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 206 /// `"authenticatorData"`. 207 const AUTHENTICATOR_DATA: &str = "authenticatorData"; 208 /// `"signature"`. 209 const SIGNATURE: &str = "signature"; 210 /// `"userHandle"`. 211 const USER_HANDLE: &str = "userHandle"; 212 /// Fields in `AuthenticatorAssertionResponseJSON`. 213 pub(super) const AUTH_ASSERT_FIELDS: &[&str; 4] = 214 &[CLIENT_DATA_JSON, AUTHENTICATOR_DATA, SIGNATURE, USER_HANDLE]; 215 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 216 for AuthenticatorAssertion<USER_LEN, DISCOVERABLE> 217 where 218 UserHandle<USER_LEN>: Deserialize<'de>, 219 { 220 /// Deserializes a `struct` based on 221 /// [`AuthenticatorAssertionResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorassertionresponsejson). 222 /// 223 /// Note unknown keys and duplicate keys are forbidden; 224 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-clientdatajson), 225 /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-authenticatordata), 226 /// and 227 /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-signature) are 228 /// base64url-decoded; 229 /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle) is 230 /// required and must not be `null` iff `DISCOVERABLE`. When it exists and is not `null`, it is deserialized 231 /// via [`UserHandle::deserialize`]. All `required` fields in the `AuthenticatorAssertionResponseJSON` Web IDL 232 /// `dictionary` exist (and are not `null`). 233 #[inline] 234 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 235 where 236 D: Deserializer<'de>, 237 { 238 deserializer.deserialize_struct( 239 "AuthenticatorAssertion", 240 AUTH_ASSERT_FIELDS, 241 AuthenticatorAssertionVisitor::<false, USER_LEN, DISCOVERABLE>, 242 ) 243 } 244 } 245 /// Empty map of client extensions. 246 pub(super) struct ClientExtensionsOutputs; 247 /// `Visitor` for `ClientExtensionsOutputs`. 248 /// 249 /// Unknown fields are ignored iff `RELAXED`. 250 pub(super) struct ClientExtensionsOutputsVisitor<const RELAXED: bool, PRF>( 251 pub PhantomData<fn() -> PRF>, 252 ); 253 impl<'d, const R: bool, P> Visitor<'d> for ClientExtensionsOutputsVisitor<R, P> 254 where 255 P: for<'a> Deserialize<'a>, 256 { 257 type Value = ClientExtensionsOutputs; 258 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 259 formatter.write_str("ClientExtensionsOutputs") 260 } 261 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 262 where 263 A: MapAccess<'d>, 264 { 265 /// Allowed fields. 266 enum Field<const IGNORE_UNKNOWN: bool> { 267 /// `prf`. 268 Prf, 269 /// Unknown field. 270 Other, 271 } 272 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 273 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 274 where 275 D: Deserializer<'e>, 276 { 277 /// `Visitor` for `Field`. 278 /// 279 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 280 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 281 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 282 type Value = Field<IG>; 283 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 284 write!(formatter, "'{PRF}'") 285 } 286 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 287 where 288 E: Error, 289 { 290 match v { 291 PRF => Ok(Field::Prf), 292 _ => { 293 if IG { 294 Ok(Field::Other) 295 } else { 296 Err(E::unknown_field(v, EXT_FIELDS)) 297 } 298 } 299 } 300 } 301 } 302 deserializer.deserialize_identifier(FieldVisitor::<I>) 303 } 304 } 305 let mut prf = None; 306 while let Some(key) = map.next_key::<Field<R>>()? { 307 match key { 308 Field::Prf => { 309 if prf.is_some() { 310 return Err(Error::duplicate_field(PRF)); 311 } 312 prf = map.next_value::<Option<P>>().map(Some)?; 313 } 314 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 315 } 316 } 317 Ok(ClientExtensionsOutputs) 318 } 319 } 320 impl ClientExtensions for ClientExtensionsOutputs { 321 fn empty() -> Self { 322 Self 323 } 324 } 325 /// `"prf"` 326 const PRF: &str = "prf"; 327 /// `AuthenticationExtensionsClientOutputsJSON` fields. 328 pub(super) const EXT_FIELDS: &[&str; 1] = &[PRF]; 329 impl<'de> Deserialize<'de> for ClientExtensionsOutputs { 330 /// Deserializes a `struct` based on 331 /// [`AuthenticationExtensionsClientOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputsjson). 332 /// 333 /// Note that unknown and duplicate keys are forbidden and 334 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` 335 /// or deserialized via [`AuthenticationExtensionsPrfOutputs::deserialize`]. 336 #[inline] 337 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 338 where 339 D: Deserializer<'de>, 340 { 341 deserializer.deserialize_struct( 342 "ClientExtensionsOutputs", 343 EXT_FIELDS, 344 ClientExtensionsOutputsVisitor::< 345 false, 346 AuthenticationExtensionsPrfOutputsHelper< 347 false, 348 false, 349 AuthenticationExtensionsPrfValues, 350 >, 351 >(PhantomData), 352 ) 353 } 354 } 355 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 356 for Authentication<USER_LEN, DISCOVERABLE> 357 where 358 UserHandle<USER_LEN>: Deserialize<'de>, 359 { 360 /// Deserializes a `struct` based on 361 /// [`AuthenticationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson). 362 /// 363 /// Note that unknown and duplicate keys are forbidden; 364 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and 365 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-rawid) are deserialized 366 /// via [`CredentialId::deserialize`]; 367 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized 368 /// via [`AuthenticatorAssertion::deserialize`]; 369 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-authenticatorattachment) 370 /// is `null` or deserialized via [`AuthenticatorAttachment::deserialize`]; 371 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults) 372 /// is deserialized such that it is an empty map or a map that only contains 373 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) which additionally must be 374 /// `null` or an 375 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 376 /// such that unknown and duplicate keys are forbidden, 377 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 378 /// is forbidden (including being assigned `null`), 379 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 380 /// be `null`, or be an 381 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 382 /// with no unknown or duplicate keys, 383 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) must exist but be 384 /// `null`, and 385 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 386 /// must be `null` if so; all `required` fields in the `AuthenticationResponseJSON` Web IDL `dictionary` exist 387 /// (and are not `null`); [`type`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-type) is 388 /// `"public-key"`; and the decoded `id` and decoded `rawId` are the same. 389 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 390 #[inline] 391 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 392 where 393 D: Deserializer<'de>, 394 { 395 PublicKeyCredential::< 396 false, 397 false, 398 AuthenticatorAssertion<USER_LEN, DISCOVERABLE>, 399 ClientExtensionsOutputs, 400 >::deserialize(deserializer) 401 .map(|cred| Self { 402 raw_id: cred.id.unwrap_or_else(|| { 403 unreachable!("there is a bug in PublicKeyCredential::deserialize") 404 }), 405 response: cred.response, 406 authenticator_attachment: cred.authenticator_attachment, 407 }) 408 } 409 } 410 impl Serialize for UnknownCredentialOptions<'_, '_> { 411 /// Serializes `self` to conform with 412 /// [`UnknownCredentialOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-unknowncredentialoptions). 413 /// 414 /// # Examples 415 /// 416 /// ``` 417 /// # use core::str::FromStr; 418 /// # use webauthn_rp::{request::{AsciiDomain, RpId}, response::{auth::error::UnknownCredentialOptions, CredentialId}}; 419 /// # #[cfg(feature = "custom")] 420 /// let credential_id = CredentialId::try_from(vec![0; 16])?; 421 /// # #[cfg(feature = "custom")] 422 /// assert_eq!( 423 /// serde_json::to_string(&UnknownCredentialOptions { 424 /// rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 425 /// credential_id: (&credential_id).into(), 426 /// }) 427 /// .unwrap(), 428 /// r#"{"rpId":"example.com","credentialId":"AAAAAAAAAAAAAAAAAAAAAA"}"# 429 /// ); 430 /// # Ok::<_, webauthn_rp::AggErr>(()) 431 /// ``` 432 #[inline] 433 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 434 where 435 S: Serializer, 436 { 437 serializer 438 .serialize_struct("UnknownCredentialOptions", 2) 439 .and_then(|mut ser| { 440 ser.serialize_field("rpId", self.rp_id).and_then(|()| { 441 ser.serialize_field("credentialId", &self.credential_id) 442 .and_then(|()| ser.end()) 443 }) 444 }) 445 } 446 } 447 #[cfg(test)] 448 mod tests { 449 use super::super::{ 450 super::super::request::register::USER_HANDLE_MIN_LEN, AuthenticatorAttachment, 451 DiscoverableAuthentication, NonDiscoverableAuthentication, 452 }; 453 use rsa::sha2::{Digest as _, Sha256}; 454 use serde::de::{Error as _, Unexpected}; 455 use serde_json::Error; 456 #[expect(clippy::unwrap_used, reason = "OK in tests")] 457 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 458 #[expect( 459 clippy::cognitive_complexity, 460 clippy::too_many_lines, 461 reason = "a lot to test" 462 )] 463 #[test] 464 fn eddsa_authentication_deserialize_data_mismatch() { 465 let c_data_json = serde_json::json!({}).to_string(); 466 let auth_data = [ 467 // `rpIdHash`. 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 0, 498 0, 499 0, 500 // `flags`. 501 0b0000_0101, 502 // `signCount`. 503 0, 504 0, 505 0, 506 0, 507 ]; 508 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 509 let b64_adata = base64url_nopad::encode(auth_data.as_slice()); 510 let b64_sig = base64url_nopad::encode([].as_slice()); 511 let b64_user = base64url_nopad::encode(b"\x00".as_slice()); 512 let auth_data_len = 37; 513 // Base case is valid. 514 assert!( 515 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 516 serde_json::json!({ 517 "id": "AAAAAAAAAAAAAAAAAAAAAA", 518 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 519 "response": { 520 "clientDataJSON": b64_cdata_json, 521 "authenticatorData": b64_adata, 522 "signature": b64_sig, 523 "userHandle": b64_user, 524 }, 525 "authenticatorAttachment": "cross-platform", 526 "clientExtensionResults": {}, 527 "type": "public-key" 528 }) 529 .to_string() 530 .as_str() 531 ) 532 .is_ok_and( 533 |auth| auth.response.client_data_json == c_data_json.as_bytes() 534 && auth.response.authenticator_data_and_c_data_hash[..auth_data_len] 535 == auth_data 536 && auth.response.authenticator_data_and_c_data_hash[auth_data_len..] 537 == *Sha256::digest(c_data_json.as_bytes()) 538 && matches!( 539 auth.authenticator_attachment, 540 AuthenticatorAttachment::CrossPlatform 541 ) 542 ) 543 ); 544 // `id` and `rawId` mismatch. 545 let mut err = Error::invalid_value( 546 Unexpected::Bytes( 547 base64url_nopad::decode(b"ABABABABABABABABABABAA") 548 .unwrap() 549 .as_slice(), 550 ), 551 &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(), 552 ) 553 .to_string() 554 .into_bytes(); 555 assert_eq!( 556 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 557 serde_json::json!({ 558 "id": "AAAAAAAAAAAAAAAAAAAAAA", 559 "rawId": "ABABABABABABABABABABAA", 560 "response": { 561 "clientDataJSON": b64_cdata_json, 562 "authenticatorData": b64_adata, 563 "signature": b64_sig, 564 "userHandle": b64_user, 565 }, 566 "authenticatorAttachment": "cross-platform", 567 "clientExtensionResults": {}, 568 "type": "public-key" 569 }) 570 .to_string() 571 .as_str() 572 ) 573 .unwrap_err() 574 .to_string() 575 .into_bytes() 576 .get(..err.len()), 577 Some(err.as_slice()) 578 ); 579 // missing `id`. 580 err = Error::missing_field("id").to_string().into_bytes(); 581 assert_eq!( 582 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 583 serde_json::json!({ 584 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 585 "response": { 586 "clientDataJSON": b64_cdata_json, 587 "authenticatorData": b64_adata, 588 "signature": b64_sig, 589 "userHandle": b64_user, 590 }, 591 "authenticatorAttachment": "cross-platform", 592 "clientExtensionResults": {}, 593 "type": "public-key" 594 }) 595 .to_string() 596 .as_str() 597 ) 598 .unwrap_err() 599 .to_string() 600 .into_bytes() 601 .get(..err.len()), 602 Some(err.as_slice()) 603 ); 604 // `null` `id`. 605 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 606 .to_string() 607 .into_bytes(); 608 assert_eq!( 609 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 610 serde_json::json!({ 611 "id": null, 612 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 613 "response": { 614 "clientDataJSON": b64_cdata_json, 615 "authenticatorData": b64_adata, 616 "signature": b64_sig, 617 "userHandle": b64_user, 618 }, 619 "clientExtensionResults": {}, 620 "type": "public-key" 621 }) 622 .to_string() 623 .as_str() 624 ) 625 .unwrap_err() 626 .to_string() 627 .into_bytes() 628 .get(..err.len()), 629 Some(err.as_slice()) 630 ); 631 // missing `rawId`. 632 err = Error::missing_field("rawId").to_string().into_bytes(); 633 assert_eq!( 634 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 635 serde_json::json!({ 636 "id": "AAAAAAAAAAAAAAAAAAAAAA", 637 "response": { 638 "clientDataJSON": b64_cdata_json, 639 "authenticatorData": b64_adata, 640 "signature": b64_sig, 641 "userHandle": b64_user, 642 }, 643 "clientExtensionResults": {}, 644 "type": "public-key" 645 }) 646 .to_string() 647 .as_str() 648 ) 649 .unwrap_err() 650 .to_string() 651 .into_bytes() 652 .get(..err.len()), 653 Some(err.as_slice()) 654 ); 655 // `null` `rawId`. 656 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 657 .to_string() 658 .into_bytes(); 659 assert_eq!( 660 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 661 serde_json::json!({ 662 "id": "AAAAAAAAAAAAAAAAAAAAAA", 663 "rawId": null, 664 "response": { 665 "clientDataJSON": b64_cdata_json, 666 "authenticatorData": b64_adata, 667 "signature": b64_sig, 668 "userHandle": b64_user, 669 }, 670 "clientExtensionResults": {}, 671 "type": "public-key" 672 }) 673 .to_string() 674 .as_str() 675 ) 676 .unwrap_err() 677 .to_string() 678 .into_bytes() 679 .get(..err.len()), 680 Some(err.as_slice()) 681 ); 682 // Missing `authenticatorData`. 683 err = Error::missing_field("authenticatorData") 684 .to_string() 685 .into_bytes(); 686 assert_eq!( 687 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 688 serde_json::json!({ 689 "id": "AAAAAAAAAAAAAAAAAAAAAA", 690 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 691 "response": { 692 "clientDataJSON": b64_cdata_json, 693 "signature": b64_sig, 694 "userHandle": b64_user, 695 }, 696 "clientExtensionResults": {}, 697 "type": "public-key" 698 }) 699 .to_string() 700 .as_str() 701 ) 702 .unwrap_err() 703 .to_string() 704 .into_bytes() 705 .get(..err.len()), 706 Some(err.as_slice()) 707 ); 708 // `null` `authenticatorData`. 709 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorData") 710 .to_string() 711 .into_bytes(); 712 assert_eq!( 713 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 714 serde_json::json!({ 715 "id": "AAAAAAAAAAAAAAAAAAAAAA", 716 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 717 "response": { 718 "clientDataJSON": b64_cdata_json, 719 "authenticatorData": null, 720 "signature": b64_sig, 721 "userHandle": b64_user, 722 }, 723 "clientExtensionResults": {}, 724 "type": "public-key" 725 }) 726 .to_string() 727 .as_str() 728 ) 729 .unwrap_err() 730 .to_string() 731 .into_bytes() 732 .get(..err.len()), 733 Some(err.as_slice()) 734 ); 735 // Missing `signature`. 736 err = Error::missing_field("signature").to_string().into_bytes(); 737 assert_eq!( 738 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 739 serde_json::json!({ 740 "id": "AAAAAAAAAAAAAAAAAAAAAA", 741 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 742 "response": { 743 "clientDataJSON": b64_cdata_json, 744 "authenticatorData": b64_adata, 745 "userHandle": b64_user, 746 }, 747 "clientExtensionResults": {}, 748 "type": "public-key" 749 }) 750 .to_string() 751 .as_str() 752 ) 753 .unwrap_err() 754 .to_string() 755 .into_bytes() 756 .get(..err.len()), 757 Some(err.as_slice()) 758 ); 759 // `null` `signature`. 760 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 761 .to_string() 762 .into_bytes(); 763 assert_eq!( 764 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 765 serde_json::json!({ 766 "id": "AAAAAAAAAAAAAAAAAAAAAA", 767 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 768 "response": { 769 "clientDataJSON": b64_cdata_json, 770 "authenticatorData": b64_adata, 771 "signature": null, 772 "userHandle": b64_user, 773 }, 774 "clientExtensionResults": {}, 775 "type": "public-key" 776 }) 777 .to_string() 778 .as_str() 779 ) 780 .unwrap_err() 781 .to_string() 782 .into_bytes() 783 .get(..err.len()), 784 Some(err.as_slice()) 785 ); 786 // Missing `userHandle`. 787 drop( 788 serde_json::from_str::<NonDiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 789 serde_json::json!({ 790 "id": "AAAAAAAAAAAAAAAAAAAAAA", 791 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 792 "response": { 793 "clientDataJSON": b64_cdata_json, 794 "authenticatorData": b64_adata, 795 "signature": b64_sig, 796 }, 797 "clientExtensionResults": {}, 798 "type": "public-key" 799 }) 800 .to_string() 801 .as_str(), 802 ) 803 .unwrap(), 804 ); 805 // `null` `userHandle`. 806 drop( 807 serde_json::from_str::<NonDiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 808 serde_json::json!({ 809 "id": "AAAAAAAAAAAAAAAAAAAAAA", 810 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 811 "response": { 812 "clientDataJSON": b64_cdata_json, 813 "authenticatorData": b64_adata, 814 "signature": b64_sig, 815 "userHandle": null, 816 }, 817 "clientExtensionResults": {}, 818 "type": "public-key" 819 }) 820 .to_string() 821 .as_str(), 822 ) 823 .unwrap(), 824 ); 825 // `null` `authenticatorAttachment`. 826 assert!( 827 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 828 serde_json::json!({ 829 "id": "AAAAAAAAAAAAAAAAAAAAAA", 830 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 831 "response": { 832 "clientDataJSON": b64_cdata_json, 833 "authenticatorData": b64_adata, 834 "signature": b64_sig, 835 "userHandle": b64_user, 836 }, 837 "authenticatorAttachment": null, 838 "clientExtensionResults": {}, 839 "type": "public-key" 840 }) 841 .to_string() 842 .as_str() 843 ) 844 .is_ok_and(|auth| matches!( 845 auth.authenticator_attachment, 846 AuthenticatorAttachment::None 847 )) 848 ); 849 // Unknown `authenticatorAttachment`. 850 err = Error::invalid_value( 851 Unexpected::Str("Platform"), 852 &"'platform' or 'cross-platform'", 853 ) 854 .to_string() 855 .into_bytes(); 856 assert_eq!( 857 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 858 serde_json::json!({ 859 "id": "AAAAAAAAAAAAAAAAAAAAAA", 860 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 861 "response": { 862 "clientDataJSON": b64_cdata_json, 863 "authenticatorData": b64_adata, 864 "signature": b64_sig, 865 "userHandle": b64_user, 866 }, 867 "authenticatorAttachment": "Platform", 868 "clientExtensionResults": {}, 869 "type": "public-key" 870 }) 871 .to_string() 872 .as_str() 873 ) 874 .unwrap_err() 875 .to_string() 876 .into_bytes() 877 .get(..err.len()), 878 Some(err.as_slice()) 879 ); 880 // Missing `clientDataJSON`. 881 err = Error::missing_field("clientDataJSON") 882 .to_string() 883 .into_bytes(); 884 assert_eq!( 885 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 886 serde_json::json!({ 887 "id": "AAAAAAAAAAAAAAAAAAAAAA", 888 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 889 "response": { 890 "authenticatorData": b64_adata, 891 "signature": b64_sig, 892 "userHandle": b64_user, 893 }, 894 "clientExtensionResults": {}, 895 "type": "public-key" 896 }) 897 .to_string() 898 .as_str() 899 ) 900 .unwrap_err() 901 .to_string() 902 .into_bytes() 903 .get(..err.len()), 904 Some(err.as_slice()) 905 ); 906 // `null` `clientDataJSON`. 907 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 908 .to_string() 909 .into_bytes(); 910 assert_eq!( 911 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 912 serde_json::json!({ 913 "id": "AAAAAAAAAAAAAAAAAAAAAA", 914 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 915 "response": { 916 "clientDataJSON": null, 917 "authenticatorData": b64_adata, 918 "signature": b64_sig, 919 "userHandle": b64_user, 920 }, 921 "clientExtensionResults": {}, 922 "type": "public-key" 923 }) 924 .to_string() 925 .as_str() 926 ) 927 .unwrap_err() 928 .to_string() 929 .into_bytes() 930 .get(..err.len()), 931 Some(err.as_slice()) 932 ); 933 // Missing `response`. 934 err = Error::missing_field("response").to_string().into_bytes(); 935 assert_eq!( 936 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 937 serde_json::json!({ 938 "id": "AAAAAAAAAAAAAAAAAAAAAA", 939 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 940 "clientExtensionResults": {}, 941 "type": "public-key" 942 }) 943 .to_string() 944 .as_str() 945 ) 946 .unwrap_err() 947 .to_string() 948 .into_bytes() 949 .get(..err.len()), 950 Some(err.as_slice()) 951 ); 952 // `null` `response`. 953 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAssertion") 954 .to_string() 955 .into_bytes(); 956 assert_eq!( 957 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 958 serde_json::json!({ 959 "id": "AAAAAAAAAAAAAAAAAAAAAA", 960 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 961 "response": null, 962 "clientExtensionResults": {}, 963 "type": "public-key" 964 }) 965 .to_string() 966 .as_str() 967 ) 968 .unwrap_err() 969 .to_string() 970 .into_bytes() 971 .get(..err.len()), 972 Some(err.as_slice()) 973 ); 974 // Empty `response`. 975 err = Error::missing_field("clientDataJSON") 976 .to_string() 977 .into_bytes(); 978 assert_eq!( 979 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 980 serde_json::json!({ 981 "id": "AAAAAAAAAAAAAAAAAAAAAA", 982 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 983 "response": {}, 984 "clientExtensionResults": {}, 985 "type": "public-key" 986 }) 987 .to_string() 988 .as_str() 989 ) 990 .unwrap_err() 991 .to_string() 992 .into_bytes() 993 .get(..err.len()), 994 Some(err.as_slice()) 995 ); 996 // Missing `clientExtensionResults`. 997 err = Error::missing_field("clientExtensionResults") 998 .to_string() 999 .into_bytes(); 1000 assert_eq!( 1001 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1002 serde_json::json!({ 1003 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1004 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1005 "response": { 1006 "clientDataJSON": b64_cdata_json, 1007 "authenticatorData": b64_adata, 1008 "signature": b64_sig, 1009 "userHandle": b64_user, 1010 }, 1011 "type": "public-key" 1012 }) 1013 .to_string() 1014 .as_str() 1015 ) 1016 .unwrap_err() 1017 .to_string() 1018 .into_bytes() 1019 .get(..err.len()), 1020 Some(err.as_slice()) 1021 ); 1022 // `null` `clientExtensionResults`. 1023 err = Error::invalid_type( 1024 Unexpected::Other("null"), 1025 &"clientExtensionResults to be a map of allowed client extensions", 1026 ) 1027 .to_string() 1028 .into_bytes(); 1029 assert_eq!( 1030 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1031 serde_json::json!({ 1032 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1033 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1034 "response": { 1035 "clientDataJSON": b64_cdata_json, 1036 "authenticatorData": b64_adata, 1037 "signature": b64_sig, 1038 "userHandle": b64_user, 1039 }, 1040 "clientExtensionResults": null, 1041 "type": "public-key" 1042 }) 1043 .to_string() 1044 .as_str() 1045 ) 1046 .unwrap_err() 1047 .to_string() 1048 .into_bytes() 1049 .get(..err.len()), 1050 Some(err.as_slice()) 1051 ); 1052 // Missing `type`. 1053 err = Error::missing_field("type").to_string().into_bytes(); 1054 assert_eq!( 1055 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1056 serde_json::json!({ 1057 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1058 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1059 "response": { 1060 "clientDataJSON": b64_cdata_json, 1061 "authenticatorData": b64_adata, 1062 "signature": b64_sig, 1063 "userHandle": b64_user, 1064 }, 1065 "clientExtensionResults": {}, 1066 }) 1067 .to_string() 1068 .as_str() 1069 ) 1070 .unwrap_err() 1071 .to_string() 1072 .into_bytes() 1073 .get(..err.len()), 1074 Some(err.as_slice()) 1075 ); 1076 // `null` `type`. 1077 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 1078 .to_string() 1079 .into_bytes(); 1080 assert_eq!( 1081 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1082 serde_json::json!({ 1083 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1084 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1085 "response": { 1086 "clientDataJSON": b64_cdata_json, 1087 "authenticatorData": b64_adata, 1088 "signature": b64_sig, 1089 "userHandle": b64_user, 1090 }, 1091 "clientExtensionResults": {}, 1092 "type": null 1093 }) 1094 .to_string() 1095 .as_str() 1096 ) 1097 .unwrap_err() 1098 .to_string() 1099 .into_bytes() 1100 .get(..err.len()), 1101 Some(err.as_slice()) 1102 ); 1103 // Not exactly `public-type` `type`. 1104 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 1105 .to_string() 1106 .into_bytes(); 1107 assert_eq!( 1108 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1109 serde_json::json!({ 1110 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1111 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1112 "response": { 1113 "clientDataJSON": b64_cdata_json, 1114 "authenticatorData": b64_adata, 1115 "signature": b64_sig, 1116 "userHandle": b64_user, 1117 }, 1118 "clientExtensionResults": {}, 1119 "type": "Public-key" 1120 }) 1121 .to_string() 1122 .as_str() 1123 ) 1124 .unwrap_err() 1125 .to_string() 1126 .into_bytes() 1127 .get(..err.len()), 1128 Some(err.as_slice()) 1129 ); 1130 // `null`. 1131 err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential") 1132 .to_string() 1133 .into_bytes(); 1134 assert_eq!( 1135 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1136 serde_json::json!(null).to_string().as_str() 1137 ) 1138 .unwrap_err() 1139 .to_string() 1140 .into_bytes() 1141 .get(..err.len()), 1142 Some(err.as_slice()) 1143 ); 1144 // Empty. 1145 err = Error::missing_field("response").to_string().into_bytes(); 1146 assert_eq!( 1147 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1148 serde_json::json!({}).to_string().as_str() 1149 ) 1150 .unwrap_err() 1151 .to_string() 1152 .into_bytes() 1153 .get(..err.len()), 1154 Some(err.as_slice()) 1155 ); 1156 // Unknown field in `response`. 1157 err = Error::unknown_field( 1158 "foo", 1159 [ 1160 "clientDataJSON", 1161 "authenticatorData", 1162 "signature", 1163 "userHandle", 1164 ] 1165 .as_slice(), 1166 ) 1167 .to_string() 1168 .into_bytes(); 1169 assert_eq!( 1170 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1171 serde_json::json!({ 1172 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1173 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1174 "response": { 1175 "clientDataJSON": b64_cdata_json, 1176 "authenticatorData": b64_adata, 1177 "signature": b64_sig, 1178 "userHandle": b64_user, 1179 "foo": true, 1180 }, 1181 "clientExtensionResults": {}, 1182 "type": "public-key" 1183 }) 1184 .to_string() 1185 .as_str() 1186 ) 1187 .unwrap_err() 1188 .to_string() 1189 .into_bytes() 1190 .get(..err.len()), 1191 Some(err.as_slice()) 1192 ); 1193 // Duplicate field in `response`. 1194 err = Error::duplicate_field("userHandle") 1195 .to_string() 1196 .into_bytes(); 1197 assert_eq!( 1198 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1199 format!( 1200 "{{ 1201 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1202 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1203 \"response\": {{ 1204 \"clientDataJSON\": \"{b64_cdata_json}\", 1205 \"authenticatorData\": \"{b64_adata}\", 1206 \"signature\": \"{b64_sig}\", 1207 \"userHandle\": \"{b64_user}\", 1208 \"userHandle\": \"{b64_user}\" 1209 }}, 1210 \"clientExtensionResults\": {{}}, 1211 \"type\": \"public-key\" 1212 1213 }}" 1214 ) 1215 .as_str() 1216 ) 1217 .unwrap_err() 1218 .to_string() 1219 .into_bytes() 1220 .get(..err.len()), 1221 Some(err.as_slice()) 1222 ); 1223 // Unknown field in `PublicKeyCredential`. 1224 err = Error::unknown_field( 1225 "foo", 1226 [ 1227 "id", 1228 "type", 1229 "rawId", 1230 "response", 1231 "authenticatorAttachment", 1232 "clientExtensionResults", 1233 ] 1234 .as_slice(), 1235 ) 1236 .to_string() 1237 .into_bytes(); 1238 assert_eq!( 1239 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1240 serde_json::json!({ 1241 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1242 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1243 "response": { 1244 "clientDataJSON": b64_cdata_json, 1245 "authenticatorData": b64_adata, 1246 "signature": b64_sig, 1247 "userHandle": b64_user, 1248 }, 1249 "clientExtensionResults": {}, 1250 "type": "public-key", 1251 "foo": true, 1252 }) 1253 .to_string() 1254 .as_str() 1255 ) 1256 .unwrap_err() 1257 .to_string() 1258 .into_bytes() 1259 .get(..err.len()), 1260 Some(err.as_slice()) 1261 ); 1262 // Duplicate field in `PublicKeyCredential`. 1263 err = Error::duplicate_field("id").to_string().into_bytes(); 1264 assert_eq!( 1265 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1266 format!( 1267 "{{ 1268 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1269 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1270 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1271 \"response\": {{ 1272 \"clientDataJSON\": \"{b64_cdata_json}\", 1273 \"authenticatorData\": \"{b64_adata}\", 1274 \"signature\": \"{b64_sig}\", 1275 \"userHandle\": \"{b64_user}\" 1276 }}, 1277 \"clientExtensionResults\": {{}}, 1278 \"type\": \"public-key\" 1279 1280 }}" 1281 ) 1282 .as_str() 1283 ) 1284 .unwrap_err() 1285 .to_string() 1286 .into_bytes() 1287 .get(..err.len()), 1288 Some(err.as_slice()) 1289 ); 1290 } 1291 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1292 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 1293 #[expect(clippy::too_many_lines, reason = "a lot to test")] 1294 #[test] 1295 fn client_extensions() { 1296 let c_data_json = serde_json::json!({}).to_string(); 1297 let auth_data: [u8; 37] = [ 1298 // `rpIdHash`. 1299 0, 1300 0, 1301 0, 1302 0, 1303 0, 1304 0, 1305 0, 1306 0, 1307 0, 1308 0, 1309 0, 1310 0, 1311 0, 1312 0, 1313 0, 1314 0, 1315 0, 1316 0, 1317 0, 1318 0, 1319 0, 1320 0, 1321 0, 1322 0, 1323 0, 1324 0, 1325 0, 1326 0, 1327 0, 1328 0, 1329 0, 1330 0, 1331 // `flags`. 1332 0b0000_0101, 1333 // `signCount`. 1334 0, 1335 0, 1336 0, 1337 0, 1338 ]; 1339 let auth_data_len = 37; 1340 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 1341 let b64_adata = base64url_nopad::encode(auth_data.as_slice()); 1342 let b64_sig = base64url_nopad::encode([].as_slice()); 1343 let b64_user = base64url_nopad::encode(b"\x00".as_slice()); 1344 // Base case is valid. 1345 assert!( 1346 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1347 serde_json::json!({ 1348 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1349 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1350 "response": { 1351 "clientDataJSON": b64_cdata_json, 1352 "authenticatorData": b64_adata, 1353 "signature": b64_sig, 1354 "userHandle": b64_user, 1355 }, 1356 "authenticatorAttachment": "cross-platform", 1357 "clientExtensionResults": {}, 1358 "type": "public-key" 1359 }) 1360 .to_string() 1361 .as_str() 1362 ) 1363 .is_ok_and( 1364 |auth| auth.response.client_data_json == c_data_json.as_bytes() 1365 && auth.response.authenticator_data_and_c_data_hash[..auth_data_len] 1366 == auth_data 1367 && auth.response.authenticator_data_and_c_data_hash[auth_data_len..] 1368 == *Sha256::digest(c_data_json.as_bytes()) 1369 && matches!( 1370 auth.authenticator_attachment, 1371 AuthenticatorAttachment::CrossPlatform 1372 ) 1373 ) 1374 ); 1375 // `null` `prf`. 1376 drop( 1377 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1378 serde_json::json!({ 1379 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1380 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1381 "response": { 1382 "clientDataJSON": b64_cdata_json, 1383 "authenticatorData": b64_adata, 1384 "signature": b64_sig, 1385 "userHandle": b64_user, 1386 }, 1387 "clientExtensionResults": { 1388 "prf": null 1389 }, 1390 "type": "public-key" 1391 }) 1392 .to_string() 1393 .as_str(), 1394 ) 1395 .unwrap(), 1396 ); 1397 // Unknown `clientExtensionResults`. 1398 let mut err = Error::unknown_field("Prf", ["prf"].as_slice()) 1399 .to_string() 1400 .into_bytes(); 1401 assert_eq!( 1402 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1403 serde_json::json!({ 1404 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1405 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1406 "response": { 1407 "clientDataJSON": b64_cdata_json, 1408 "authenticatorData": b64_adata, 1409 "signature": b64_sig, 1410 "userHandle": b64_user, 1411 }, 1412 "clientExtensionResults": { 1413 "Prf": null 1414 }, 1415 "type": "public-key" 1416 }) 1417 .to_string() 1418 .as_str() 1419 ) 1420 .unwrap_err() 1421 .to_string() 1422 .into_bytes() 1423 .get(..err.len()), 1424 Some(err.as_slice()) 1425 ); 1426 // Duplicate field. 1427 err = Error::duplicate_field("prf").to_string().into_bytes(); 1428 assert_eq!( 1429 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1430 format!( 1431 "{{ 1432 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1433 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1434 \"response\": {{ 1435 \"clientDataJSON\": \"{b64_cdata_json}\", 1436 \"authenticatorData\": \"{b64_adata}\", 1437 \"signature\": \"{b64_sig}\", 1438 \"userHandle\": \"{b64_user}\" 1439 }}, 1440 \"clientExtensionResults\": {{ 1441 \"prf\": null, 1442 \"prf\": null 1443 }}, 1444 \"type\": \"public-key\" 1445 }}" 1446 ) 1447 .as_str() 1448 ) 1449 .unwrap_err() 1450 .to_string() 1451 .into_bytes() 1452 .get(..err.len()), 1453 Some(err.as_slice()) 1454 ); 1455 // `null` `results`. 1456 drop( 1457 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1458 serde_json::json!({ 1459 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1460 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1461 "response": { 1462 "clientDataJSON": b64_cdata_json, 1463 "authenticatorData": b64_adata, 1464 "signature": b64_sig, 1465 "userHandle": b64_user, 1466 }, 1467 "clientExtensionResults": { 1468 "prf": { 1469 "results": null, 1470 } 1471 }, 1472 "type": "public-key" 1473 }) 1474 .to_string() 1475 .as_str(), 1476 ) 1477 .unwrap(), 1478 ); 1479 // Duplicate field in `prf`. 1480 err = Error::duplicate_field("results").to_string().into_bytes(); 1481 assert_eq!( 1482 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1483 format!( 1484 "{{ 1485 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1486 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1487 \"response\": {{ 1488 \"clientDataJSON\": \"{b64_cdata_json}\", 1489 \"authenticatorData\": \"{b64_adata}\", 1490 \"signature\": \"{b64_sig}\", 1491 \"userHandle\": \"{b64_user}\" 1492 }}, 1493 \"clientExtensionResults\": {{ 1494 \"prf\": {{ 1495 \"results\": null, 1496 \"results\": null 1497 }} 1498 }}, 1499 \"type\": \"public-key\" 1500 }}" 1501 ) 1502 .as_str() 1503 ) 1504 .unwrap_err() 1505 .to_string() 1506 .into_bytes() 1507 .get(..err.len()), 1508 Some(err.as_slice()) 1509 ); 1510 // Missing `first`. 1511 err = Error::missing_field("first").to_string().into_bytes(); 1512 assert_eq!( 1513 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1514 serde_json::json!({ 1515 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1516 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1517 "response": { 1518 "clientDataJSON": b64_cdata_json, 1519 "authenticatorData": b64_adata, 1520 "signature": b64_sig, 1521 "userHandle": b64_user, 1522 }, 1523 "clientExtensionResults": { 1524 "prf": { 1525 "results": {}, 1526 } 1527 }, 1528 "type": "public-key" 1529 }) 1530 .to_string() 1531 .as_str() 1532 ) 1533 .unwrap_err() 1534 .to_string() 1535 .into_bytes() 1536 .get(..err.len()), 1537 Some(err.as_slice()) 1538 ); 1539 // `null` `first`. 1540 drop( 1541 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1542 serde_json::json!({ 1543 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1544 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1545 "response": { 1546 "clientDataJSON": b64_cdata_json, 1547 "authenticatorData": b64_adata, 1548 "signature": b64_sig, 1549 "userHandle": b64_user, 1550 }, 1551 "clientExtensionResults": { 1552 "prf": { 1553 "results": { 1554 "first": null 1555 }, 1556 } 1557 }, 1558 "type": "public-key" 1559 }) 1560 .to_string() 1561 .as_str(), 1562 ) 1563 .unwrap(), 1564 ); 1565 // `null` `second`. 1566 drop( 1567 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1568 serde_json::json!({ 1569 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1570 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1571 "response": { 1572 "clientDataJSON": b64_cdata_json, 1573 "authenticatorData": b64_adata, 1574 "signature": b64_sig, 1575 "userHandle": b64_user, 1576 }, 1577 "clientExtensionResults": { 1578 "prf": { 1579 "results": { 1580 "first": null, 1581 "second": null 1582 }, 1583 } 1584 }, 1585 "type": "public-key" 1586 }) 1587 .to_string() 1588 .as_str(), 1589 ) 1590 .unwrap(), 1591 ); 1592 // Non-`null` `first`. 1593 err = Error::invalid_type(Unexpected::Option, &"null") 1594 .to_string() 1595 .into_bytes(); 1596 assert_eq!( 1597 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1598 serde_json::json!({ 1599 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1600 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1601 "response": { 1602 "clientDataJSON": b64_cdata_json, 1603 "authenticatorData": b64_adata, 1604 "signature": b64_sig, 1605 "userHandle": b64_user, 1606 }, 1607 "clientExtensionResults": { 1608 "prf": { 1609 "results": { 1610 "first": "" 1611 }, 1612 } 1613 }, 1614 "type": "public-key" 1615 }) 1616 .to_string() 1617 .as_str() 1618 ) 1619 .unwrap_err() 1620 .to_string() 1621 .into_bytes() 1622 .get(..err.len()), 1623 Some(err.as_slice()) 1624 ); 1625 // Non-`null` `second`. 1626 err = Error::invalid_type(Unexpected::Option, &"null") 1627 .to_string() 1628 .into_bytes(); 1629 assert_eq!( 1630 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1631 serde_json::json!({ 1632 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1633 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1634 "response": { 1635 "clientDataJSON": b64_cdata_json, 1636 "authenticatorData": b64_adata, 1637 "signature": b64_sig, 1638 "userHandle": b64_user, 1639 }, 1640 "clientExtensionResults": { 1641 "prf": { 1642 "results": { 1643 "first": null, 1644 "second": "" 1645 }, 1646 } 1647 }, 1648 "type": "public-key" 1649 }) 1650 .to_string() 1651 .as_str() 1652 ) 1653 .unwrap_err() 1654 .to_string() 1655 .into_bytes() 1656 .get(..err.len()), 1657 Some(err.as_slice()) 1658 ); 1659 // Unknown `prf` field. 1660 err = Error::unknown_field("enabled", ["results"].as_slice()) 1661 .to_string() 1662 .into_bytes(); 1663 assert_eq!( 1664 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1665 serde_json::json!({ 1666 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1667 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1668 "response": { 1669 "clientDataJSON": b64_cdata_json, 1670 "authenticatorData": b64_adata, 1671 "signature": b64_sig, 1672 "userHandle": b64_user, 1673 }, 1674 "clientExtensionResults": { 1675 "prf": { 1676 "enabled": true, 1677 "results": null 1678 } 1679 }, 1680 "type": "public-key" 1681 }) 1682 .to_string() 1683 .as_str() 1684 ) 1685 .unwrap_err() 1686 .to_string() 1687 .into_bytes() 1688 .get(..err.len()), 1689 Some(err.as_slice()) 1690 ); 1691 // Unknown `results` field. 1692 err = Error::unknown_field("Second", ["first", "second"].as_slice()) 1693 .to_string() 1694 .into_bytes(); 1695 assert_eq!( 1696 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1697 serde_json::json!({ 1698 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1699 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1700 "response": { 1701 "clientDataJSON": b64_cdata_json, 1702 "authenticatorData": b64_adata, 1703 "signature": b64_sig, 1704 "userHandle": b64_user, 1705 }, 1706 "clientExtensionResults": { 1707 "prf": { 1708 "results": { 1709 "first": null, 1710 "Second": null 1711 } 1712 } 1713 }, 1714 "type": "public-key" 1715 }) 1716 .to_string() 1717 .as_str() 1718 ) 1719 .unwrap_err() 1720 .to_string() 1721 .into_bytes() 1722 .get(..err.len()), 1723 Some(err.as_slice()) 1724 ); 1725 // Duplicate field in `results`. 1726 err = Error::duplicate_field("first").to_string().into_bytes(); 1727 assert_eq!( 1728 serde_json::from_str::<DiscoverableAuthentication<USER_HANDLE_MIN_LEN>>( 1729 format!( 1730 "{{ 1731 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1732 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1733 \"response\": {{ 1734 \"clientDataJSON\": \"{b64_cdata_json}\", 1735 \"authenticatorData\": \"{b64_adata}\", 1736 \"signature\": \"{b64_sig}\", 1737 \"userHandle\": \"{b64_user}\" 1738 }}, 1739 \"clientExtensionResults\": {{ 1740 \"prf\": {{ 1741 \"results\": {{ 1742 \"first\": null, 1743 \"first\": null 1744 }} 1745 }} 1746 }}, 1747 \"type\": \"public-key\" 1748 }}" 1749 ) 1750 .as_str() 1751 ) 1752 .unwrap_err() 1753 .to_string() 1754 .into_bytes() 1755 .get(..err.len()), 1756 Some(err.as_slice()) 1757 ); 1758 } 1759 }