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