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