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