ser_relaxed.rs (173423B)
1 #[cfg(doc)] 2 use super::super::{super::request::register::CoseAlgorithmIdentifier, Challenge, CredentialId}; 3 use super::{ 4 super::{ 5 register::ser::{ 6 AUTH_ATTEST_FIELDS, AttObj, AuthenticatorAttestationVisitor, 7 ClientExtensionsOutputsVisitor, EXT_FIELDS, 8 }, 9 ser::{ 10 AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions, 11 PublicKeyCredential, Type, 12 }, 13 ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed, 14 }, 15 AttestationObject, AuthenticationExtensionsPrfOutputs, AuthenticatorAttachment, 16 AuthenticatorAttestation, ClientExtensionsOutputs, CredentialPropertiesOutput, Registration, 17 ser::{AuthAttest, CredentialPropertiesOutputVisitor, PROPS_FIELDS}, 18 }; 19 use core::{ 20 fmt::{self, Formatter}, 21 marker::PhantomData, 22 }; 23 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Unexpected, Visitor}; 24 /// `newtype` around `CredentialPropertiesOutput` with a "relaxed" [`Self::deserialize`] implementation. 25 #[derive(Clone, Copy, Debug)] 26 pub struct CredentialPropertiesOutputRelaxed(pub CredentialPropertiesOutput); 27 impl From<CredentialPropertiesOutputRelaxed> for CredentialPropertiesOutput { 28 #[inline] 29 fn from(value: CredentialPropertiesOutputRelaxed) -> Self { 30 value.0 31 } 32 } 33 impl<'de> Deserialize<'de> for CredentialPropertiesOutputRelaxed { 34 /// Same as [`CredentialPropertiesOutput::deserialize`] except unknown keys are ignored. 35 /// 36 /// Note that duplicate keys are still forbidden. 37 #[inline] 38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 39 where 40 D: Deserializer<'de>, 41 { 42 deserializer 43 .deserialize_struct( 44 "CredentialPropertiesOutputRelaxed", 45 PROPS_FIELDS, 46 CredentialPropertiesOutputVisitor::<true>, 47 ) 48 .map(Self) 49 } 50 } 51 /// `newtype` around `AuthenticationExtensionsPrfOutputs` with a "relaxed" [`Self::deserialize`] implementation. 52 #[derive(Clone, Copy, Debug)] 53 pub struct AuthenticationExtensionsPrfOutputsRelaxed(AuthenticationExtensionsPrfOutputs); 54 impl From<AuthenticationExtensionsPrfOutputsRelaxed> for AuthenticationExtensionsPrfOutputs { 55 #[inline] 56 fn from(value: AuthenticationExtensionsPrfOutputsRelaxed) -> Self { 57 value.0 58 } 59 } 60 impl<'de> Deserialize<'de> for AuthenticationExtensionsPrfOutputsRelaxed { 61 /// Same as [`AuthenticationExtensionsPrfOutputs::deserialize`] except unknown keys are ignored. 62 /// 63 /// Note that duplicate keys are still forbidden; 64 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) must still exist 65 /// (and not be `null`); and 66 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 67 /// be `null`, or be an 68 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 69 /// such that unknown keys are ignored, duplicate keys are forbidden, 70 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but 71 /// if it exists it must be `null`, and 72 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 73 /// must be `null` if so. 74 #[inline] 75 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 76 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 77 where 78 D: Deserializer<'de>, 79 { 80 AuthenticationExtensionsPrfOutputsHelper::< 81 true, 82 true, 83 AuthenticationExtensionsPrfValuesRelaxed, 84 >::deserialize(deserializer) 85 .map(|v| { 86 Self(AuthenticationExtensionsPrfOutputs { 87 enabled: v.0.unwrap_or_else(|| { 88 unreachable!( 89 "there is a bug in AuthenticationExtensionsPrfOutputsHelper::deserialize" 90 ) 91 }), 92 }) 93 }) 94 } 95 } 96 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation. 97 #[derive(Clone, Copy, Debug)] 98 pub struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs); 99 impl ClientExtensions for ClientExtensionsOutputsRelaxed { 100 fn empty() -> Self { 101 Self(ClientExtensionsOutputs::empty()) 102 } 103 } 104 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed { 105 /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored, 106 /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops) is 107 /// `null` or deserialized via [`CredentialPropertiesOutputRelaxed::deserialize`], and 108 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is 109 /// `null` or deserialized via [`AuthenticationExtensionsPrfOutputsRelaxed::deserialize`]. 110 /// 111 /// Note that duplicate keys are still forbidden. 112 #[inline] 113 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 114 where 115 D: Deserializer<'de>, 116 { 117 deserializer 118 .deserialize_struct( 119 "ClientExtensionsOutputsRelaxed", 120 EXT_FIELDS, 121 ClientExtensionsOutputsVisitor::< 122 true, 123 CredentialPropertiesOutputRelaxed, 124 AuthenticationExtensionsPrfOutputsRelaxed, 125 >(PhantomData), 126 ) 127 .map(Self) 128 } 129 } 130 /// `newtype` around `AuthAttest` with a "relaxed" [`Self::deserialize`] implementation. 131 struct AuthAttestRelaxed(pub AuthAttest); 132 impl<'de> Deserialize<'de> for AuthAttestRelaxed { 133 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 134 where 135 D: Deserializer<'de>, 136 { 137 deserializer 138 .deserialize_struct( 139 "AuthenticatorAttestation", 140 AUTH_ATTEST_FIELDS, 141 AuthenticatorAttestationVisitor::<true>, 142 ) 143 .map(Self) 144 } 145 } 146 /// `newtype` around `AuthenticatorAttestation` with a "relaxed" [`Self::deserialize`] implementation. 147 #[derive(Debug)] 148 pub struct AuthenticatorAttestationRelaxed(pub AuthenticatorAttestation); 149 impl<'de> Deserialize<'de> for AuthenticatorAttestationRelaxed { 150 /// Same as [`AuthenticatorAttestation::deserialize`] except unknown keys are ignored and only 151 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-clientdatajson) 152 /// and 153 /// [`attestationObject`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-attestationobject) 154 /// are required (and must not be `null`). For the other fields, they are allowed to not exist or be `null`. 155 /// 156 /// Note that duplicate keys are still forbidden, and data matching still applies when applicable. 157 #[inline] 158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 159 where 160 D: Deserializer<'de>, 161 { 162 AuthAttestRelaxed::deserialize(deserializer).map(|v| Self(v.0.attest)) 163 } 164 } 165 /// `newtype` around `Registration` with a "relaxed" [`Self::deserialize`] implementation. 166 #[derive(Debug)] 167 pub struct RegistrationRelaxed(pub Registration); 168 impl<'de> Deserialize<'de> for RegistrationRelaxed { 169 /// Same as [`Registration::deserialize`] except unknown keys are ignored, 170 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-response) is deserialized 171 /// via [`AuthenticatorAttestationRelaxed::deserialize`], 172 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults) 173 /// is `null` or deserialized via [`ClientExtensionsOutputsRelaxed::deserialize`], and only `response` is required. 174 /// `id`, `rawId`, and `type` are allowed to not exist. For the other fields, they are allowed to not exist or 175 /// be `null`. 176 /// 177 /// Note that duplicate keys are still forbidden, and data matching still applies when applicable. 178 #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")] 179 #[inline] 180 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 181 where 182 D: Deserializer<'de>, 183 { 184 PublicKeyCredential::<true, true, AuthAttestRelaxed, ClientExtensionsOutputsRelaxed>::deserialize(deserializer).and_then(|cred| { 185 cred.id.map_or_else(|| Ok(()), |id| { 186 cred.response.0.cred_info.map_or_else( 187 || AttestationObject::try_from(cred.response.0.attest.attestation_object()).map_err(Error::custom).and_then(|att_obj| { 188 if id == att_obj.auth_data.attested_credential_data.credential_id { 189 Ok(()) 190 } else { 191 Err(Error::invalid_value(Unexpected::Bytes(id.as_ref()), &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", att_obj.auth_data.attested_credential_data.credential_id.0).as_str())) 192 } 193 }), 194 // `start` and `last` were calculated based on `cred.response.attest.attestation_object()` 195 // and represent the starting and ending index of the `CredentialId`; therefore this is correct 196 // let alone won't `panic`. 197 |(start, last)| if id.0 == cred.response.0.attest.attestation_object()[start..last] { 198 Ok(()) 199 } else { 200 Err(Error::invalid_value(Unexpected::Bytes(id.as_ref()), &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", &cred.response.0.attest.attestation_object()[start..last]).as_str())) 201 } 202 ) 203 }).map(|()| { 204 Self(Registration { response: cred.response.0.attest, authenticator_attachment: cred.authenticator_attachment, client_extension_results: cred.client_extension_results.0 }) 205 }) 206 }) 207 } 208 } 209 /// `newtype` around `Registration` with a custom [`Self::deserialize`] implementation. 210 #[derive(Debug)] 211 pub struct CustomRegistration(pub Registration); 212 impl<'de> Deserialize<'de> for CustomRegistration { 213 /// Despite the spec having a 214 /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson) that clients 215 /// can follow, the downside is the superfluous data it contains. 216 /// 217 /// There simply is no reason to send the [`CredentialId`] _four_ times. This redundant data puts RPs in 218 /// a position where they either ignore the data or parse the data to ensure no contradictions exist 219 /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)). 220 /// 221 /// While [`Registration::deserialize`] _strictly_ adheres to the JSON definition (e.g., it requires `publicKey` 222 /// to exist and match with what is in both `authenticatorData` and `attestationObject` when the underlying 223 /// algorithm is not [`CoseAlgorithmIdentifier::Es384`]), this implementation 224 /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate 225 /// and unknown keys are disallowed: 226 /// 227 /// ```json 228 /// { 229 /// "attestationObject": <base64url string>, 230 /// "authenticatorAttachment": null | "platform" | "cross-platform", 231 /// "clientDataJSON": <base64url string>, 232 /// "clientExtensionResults": <see ClientExtensionsOutputs::deserialize>, 233 /// "transports": <see AuthTransports::deserialize>, 234 /// "type": "public-key" 235 /// } 236 /// ``` 237 /// 238 /// All of the above keys are required with the exceptions of `"authenticatorAttachment"` and `"type"`. 239 /// 240 /// # Examples 241 /// 242 /// ``` 243 /// # use webauthn_rp::response::register::ser_relaxed::CustomRegistration; 244 /// assert!( 245 /// // The below payload is technically valid, but `RegistrationServerState::verify` will fail 246 /// // since the attestationObject is not valid. This is true for `Registration::deserialize` 247 /// // as well since attestationObject parsing is always deferred. 248 /// serde_json::from_str::<CustomRegistration>( 249 /// r#"{ 250 /// "transports": ["usb"], 251 /// "attestationObject": "AA", 252 /// "authenticatorAttachment": "cross-platform", 253 /// "clientExtensionResults": {}, 254 /// "clientDataJSON": "AA", 255 /// "type": "public-key" 256 /// }"# 257 /// ).is_ok()); 258 /// ``` 259 #[expect( 260 clippy::too_many_lines, 261 reason = "want to hide; thus don't want to put in an outer scope" 262 )] 263 #[inline] 264 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 265 where 266 D: Deserializer<'de>, 267 { 268 /// `Visitor` for `CustomRegistration`. 269 struct CustomRegistrationVisitor; 270 impl<'d> Visitor<'d> for CustomRegistrationVisitor { 271 type Value = CustomRegistration; 272 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 273 formatter.write_str("CustomRegistration") 274 } 275 #[expect( 276 clippy::too_many_lines, 277 reason = "want to hide; thus don't want to put in an outer scope" 278 )] 279 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 280 where 281 A: MapAccess<'d>, 282 { 283 /// Fields in the JSON. 284 enum Field { 285 /// `attestationObject` key. 286 AttestationObject, 287 /// `authenticatorAttachment` key. 288 AuthenticatorAttachment, 289 /// `clientDataJSON` key. 290 ClientDataJson, 291 /// `clientExtensionResults` key. 292 ClientExtensionResults, 293 /// `transports` key. 294 Transports, 295 /// `type` key. 296 Type, 297 } 298 impl<'e> Deserialize<'e> for Field { 299 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 300 where 301 D: Deserializer<'e>, 302 { 303 /// `Visitor` for `Field`. 304 struct FieldVisitor; 305 impl Visitor<'_> for FieldVisitor { 306 type Value = Field; 307 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 308 write!( 309 formatter, 310 "'{ATTESTATION_OBJECT}', '{AUTHENTICATOR_ATTACHMENT}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{TRANSPORTS}', or '{TYPE}'" 311 ) 312 } 313 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 314 where 315 E: Error, 316 { 317 match v { 318 ATTESTATION_OBJECT => Ok(Field::AttestationObject), 319 AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment), 320 CLIENT_DATA_JSON => Ok(Field::ClientDataJson), 321 CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults), 322 TRANSPORTS => Ok(Field::Transports), 323 TYPE => Ok(Field::Type), 324 _ => Err(E::unknown_field(v, FIELDS)), 325 } 326 } 327 } 328 deserializer.deserialize_identifier(FieldVisitor) 329 } 330 } 331 let mut attestation_object = None; 332 let mut authenticator_attachment = None; 333 let mut client_data_json = None; 334 let mut ext = None; 335 let mut transports = None; 336 let mut typ = false; 337 while let Some(key) = map.next_key()? { 338 match key { 339 Field::AttestationObject => { 340 if attestation_object.is_some() { 341 return Err(Error::duplicate_field(ATTESTATION_OBJECT)); 342 } 343 attestation_object = 344 map.next_value::<AttObj>().map(|val| Some(val.0))?; 345 } 346 Field::AuthenticatorAttachment => { 347 if authenticator_attachment.is_some() { 348 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT)); 349 } 350 authenticator_attachment = map.next_value::<Option<_>>().map(Some)?; 351 } 352 Field::ClientDataJson => { 353 if client_data_json.is_some() { 354 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 355 } 356 client_data_json = map 357 .next_value::<Base64DecodedVal>() 358 .map(|val| Some(val.0))?; 359 } 360 Field::ClientExtensionResults => { 361 if ext.is_some() { 362 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS)); 363 } 364 ext = map.next_value().map(Some)?; 365 } 366 Field::Transports => { 367 if transports.is_some() { 368 return Err(Error::duplicate_field(TRANSPORTS)); 369 } 370 transports = map.next_value().map(Some)?; 371 } 372 Field::Type => { 373 if typ { 374 return Err(Error::duplicate_field(TYPE)); 375 } 376 typ = map.next_value::<Type>().map(|_| true)?; 377 } 378 } 379 } 380 attestation_object 381 .ok_or_else(|| Error::missing_field(ATTESTATION_OBJECT)) 382 .and_then(|att_obj| { 383 client_data_json 384 .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)) 385 .and_then(|c_data| { 386 ext.ok_or_else(|| Error::missing_field(CLIENT_EXTENSION_RESULTS)) 387 .and_then(|client_extension_results| { 388 transports 389 .ok_or_else(|| Error::missing_field(TRANSPORTS)) 390 .map(|trans| { 391 CustomRegistration(Registration { 392 response: AuthenticatorAttestation::new( 393 c_data, att_obj, trans, 394 ), 395 authenticator_attachment: 396 authenticator_attachment.map_or( 397 AuthenticatorAttachment::None, 398 |auth_attach| { 399 auth_attach.unwrap_or( 400 AuthenticatorAttachment::None, 401 ) 402 }, 403 ), 404 client_extension_results, 405 }) 406 }) 407 }) 408 }) 409 }) 410 } 411 } 412 /// `attestationObject` key. 413 const ATTESTATION_OBJECT: &str = "attestationObject"; 414 /// `authenticatorAttachment` key. 415 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment"; 416 /// `clientDataJSON` key. 417 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 418 /// `clientExtensionResults` key. 419 const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults"; 420 /// `transports` key. 421 const TRANSPORTS: &str = "transports"; 422 /// `type` key. 423 const TYPE: &str = "type"; 424 /// Fields. 425 const FIELDS: &[&str; 6] = &[ 426 ATTESTATION_OBJECT, 427 AUTHENTICATOR_ATTACHMENT, 428 CLIENT_DATA_JSON, 429 CLIENT_EXTENSION_RESULTS, 430 TRANSPORTS, 431 TYPE, 432 ]; 433 deserializer.deserialize_struct("CustomRegistration", FIELDS, CustomRegistrationVisitor) 434 } 435 } 436 #[cfg(test)] 437 mod tests { 438 use super::{ 439 super::{ 440 super::super::request::register::CoseAlgorithmIdentifier, ALG, AuthenticatorAttachment, 441 EC2, EDDSA, ES256, ES384, KTY, OKP, RSA, cbor, 442 }, 443 CustomRegistration, RegistrationRelaxed, 444 }; 445 use ed25519_dalek::{VerifyingKey, pkcs8::EncodePublicKey as _}; 446 use p256::{ 447 EncodedPoint as P256Pt, PublicKey as P256PubKey, SecretKey as P256Key, 448 elliptic_curve::sec1::{FromEncodedPoint as _, ToEncodedPoint as _}, 449 }; 450 use p384::{EncodedPoint as P384Pt, PublicKey as P384PubKey, SecretKey as P384Key}; 451 use rsa::{ 452 BigUint, RsaPrivateKey, 453 sha2::{Digest as _, Sha256}, 454 traits::PublicKeyParts as _, 455 }; 456 use serde::de::{Error as _, Unexpected}; 457 use serde_json::Error; 458 #[expect(clippy::unwrap_used, reason = "OK in tests")] 459 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 460 #[expect( 461 clippy::cognitive_complexity, 462 clippy::too_many_lines, 463 reason = "a lot to test" 464 )] 465 #[test] 466 fn eddsa_registration_deserialize_data_mismatch() { 467 let c_data_json = serde_json::json!({}).to_string(); 468 let att_obj: [u8; 143] = [ 469 cbor::MAP_3, 470 cbor::TEXT_3, 471 b'f', 472 b'm', 473 b't', 474 cbor::TEXT_4, 475 b'n', 476 b'o', 477 b'n', 478 b'e', 479 cbor::TEXT_7, 480 b'a', 481 b't', 482 b't', 483 b'S', 484 b't', 485 b'm', 486 b't', 487 cbor::MAP_0, 488 cbor::TEXT_8, 489 b'a', 490 b'u', 491 b't', 492 b'h', 493 b'D', 494 b'a', 495 b't', 496 b'a', 497 cbor::BYTES_INFO_24, 498 113, 499 // `rpIdHash`. 500 0, 501 0, 502 0, 503 0, 504 0, 505 0, 506 0, 507 0, 508 0, 509 0, 510 0, 511 0, 512 0, 513 0, 514 0, 515 0, 516 0, 517 0, 518 0, 519 0, 520 0, 521 0, 522 0, 523 0, 524 0, 525 0, 526 0, 527 0, 528 0, 529 0, 530 0, 531 0, 532 // `flags`. 533 0b0100_0101, 534 // `signCount`. 535 0, 536 0, 537 0, 538 0, 539 // `aaguid`. 540 0, 541 0, 542 0, 543 0, 544 0, 545 0, 546 0, 547 0, 548 0, 549 0, 550 0, 551 0, 552 0, 553 0, 554 0, 555 0, 556 // `credentialIdLength`. 557 0, 558 16, 559 // `credentialId`. 560 0, 561 0, 562 0, 563 0, 564 0, 565 0, 566 0, 567 0, 568 0, 569 0, 570 0, 571 0, 572 0, 573 0, 574 0, 575 0, 576 // Ed25519 COSE key. 577 cbor::MAP_4, 578 KTY, 579 OKP, 580 ALG, 581 EDDSA, 582 // `crv`. 583 cbor::NEG_ONE, 584 // `Ed25519`. 585 cbor::SIX, 586 // `x`. 587 cbor::NEG_TWO, 588 cbor::BYTES_INFO_24, 589 32, 590 // Compressed y-coordinate. 591 1, 592 1, 593 1, 594 1, 595 1, 596 1, 597 1, 598 1, 599 1, 600 1, 601 1, 602 1, 603 1, 604 1, 605 1, 606 1, 607 1, 608 1, 609 1, 610 1, 611 1, 612 1, 613 1, 614 1, 615 1, 616 1, 617 1, 618 1, 619 1, 620 1, 621 1, 622 1, 623 ]; 624 let pub_key = VerifyingKey::from_bytes(&[1; 32]) 625 .unwrap() 626 .to_public_key_der() 627 .unwrap(); 628 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 629 let att_obj_len = att_obj.len(); 630 let auth_data_start = att_obj_len - 113; 631 let b64_adata = base64url_nopad::encode(&att_obj[auth_data_start..]); 632 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 633 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 634 // Base case is valid. 635 assert!( 636 serde_json::from_str::<RegistrationRelaxed>( 637 serde_json::json!({ 638 "id": "AAAAAAAAAAAAAAAAAAAAAA", 639 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 640 "response": { 641 "clientDataJSON": b64_cdata_json, 642 "authenticatorData": b64_adata, 643 "transports": ["ble", "usb", "hybrid", "internal", "nfc", "smart-card"], 644 "publicKey": b64_key, 645 "publicKeyAlgorithm": -8i8, 646 "attestationObject": b64_aobj, 647 }, 648 "authenticatorAttachment": "cross-platform", 649 "clientExtensionResults": {}, 650 "type": "public-key" 651 }) 652 .to_string() 653 .as_str() 654 ) 655 .is_ok_and( 656 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 657 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 658 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 659 == *Sha256::digest(c_data_json.as_bytes()) 660 && reg.0.response.transports.count() == 6 661 && matches!( 662 reg.0.authenticator_attachment, 663 AuthenticatorAttachment::CrossPlatform 664 ) 665 && reg.0.client_extension_results.cred_props.is_none() 666 && reg.0.client_extension_results.prf.is_none() 667 ) 668 ); 669 // `id` and `rawId` mismatch. 670 let mut err = Error::invalid_value( 671 Unexpected::Bytes( 672 base64url_nopad::decode(b"ABABABABABABABABABABAA") 673 .unwrap() 674 .as_slice(), 675 ), 676 &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(), 677 ) 678 .to_string() 679 .into_bytes(); 680 assert_eq!( 681 serde_json::from_str::<RegistrationRelaxed>( 682 serde_json::json!({ 683 "id": "AAAAAAAAAAAAAAAAAAAAAA", 684 "rawId": "ABABABABABABABABABABAA", 685 "response": { 686 "clientDataJSON": b64_cdata_json, 687 "authenticatorData": b64_adata, 688 "transports": [], 689 "publicKey": b64_key, 690 "publicKeyAlgorithm": -8i8, 691 "attestationObject": b64_aobj, 692 }, 693 "clientExtensionResults": {}, 694 "type": "public-key" 695 }) 696 .to_string() 697 .as_str() 698 ) 699 .unwrap_err() 700 .to_string() 701 .into_bytes() 702 .get(..err.len()), 703 Some(err.as_slice()) 704 ); 705 // missing `id`. 706 drop( 707 serde_json::from_str::<RegistrationRelaxed>( 708 serde_json::json!({ 709 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 710 "response": { 711 "clientDataJSON": b64_cdata_json, 712 "authenticatorData": b64_adata, 713 "transports": [], 714 "publicKey": b64_key, 715 "publicKeyAlgorithm": -8i8, 716 "attestationObject": b64_aobj, 717 }, 718 "clientExtensionResults": {}, 719 "type": "public-key" 720 }) 721 .to_string() 722 .as_str(), 723 ) 724 .unwrap(), 725 ); 726 // `null` `id`. 727 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 728 .to_string() 729 .into_bytes(); 730 assert_eq!( 731 serde_json::from_str::<RegistrationRelaxed>( 732 serde_json::json!({ 733 "id": null, 734 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 735 "response": { 736 "clientDataJSON": null, 737 "authenticatorData": b64_adata, 738 "transports": [], 739 "publicKey": b64_key, 740 "publicKeyAlgorithm": -8i8, 741 "attestationObject": b64_aobj, 742 }, 743 "clientExtensionResults": {}, 744 "type": "public-key" 745 }) 746 .to_string() 747 .as_str() 748 ) 749 .unwrap_err() 750 .to_string() 751 .into_bytes() 752 .get(..err.len()), 753 Some(err.as_slice()) 754 ); 755 // Missing `rawId`. 756 drop( 757 serde_json::from_str::<RegistrationRelaxed>( 758 serde_json::json!({ 759 "id": "AAAAAAAAAAAAAAAAAAAAAA", 760 "response": { 761 "clientDataJSON": b64_cdata_json, 762 "authenticatorData": b64_adata, 763 "transports": [], 764 "publicKey": b64_key, 765 "publicKeyAlgorithm": -8i8, 766 "attestationObject": b64_aobj, 767 }, 768 "clientExtensionResults": {}, 769 "type": "public-key" 770 }) 771 .to_string() 772 .as_str(), 773 ) 774 .unwrap(), 775 ); 776 // `null` `rawId`. 777 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 778 .to_string() 779 .into_bytes(); 780 assert_eq!( 781 serde_json::from_str::<RegistrationRelaxed>( 782 serde_json::json!({ 783 "id": "AAAAAAAAAAAAAAAAAAAAAA", 784 "rawId": null, 785 "response": { 786 "clientDataJSON": b64_cdata_json, 787 "authenticatorData": b64_adata, 788 "transports": [], 789 "publicKey": b64_key, 790 "publicKeyAlgorithm": -8i8, 791 "attestationObject": b64_aobj, 792 }, 793 "clientExtensionResults": {}, 794 "type": "public-key" 795 }) 796 .to_string() 797 .as_str() 798 ) 799 .unwrap_err() 800 .to_string() 801 .into_bytes() 802 .get(..err.len()), 803 Some(err.as_slice()) 804 ); 805 // `id` and the credential id in authenticator data mismatch. 806 err = Error::invalid_value( 807 Unexpected::Bytes( 808 base64url_nopad 809 ::decode(b"ABABABABABABABABABABAA") 810 .unwrap() 811 .as_slice(), 812 ), 813 &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", [0u8; 16]).as_str(), 814 ) 815 .to_string().into_bytes(); 816 assert_eq!( 817 serde_json::from_str::<RegistrationRelaxed>( 818 serde_json::json!({ 819 "id": "ABABABABABABABABABABAA", 820 "rawId": "ABABABABABABABABABABAA", 821 "response": { 822 "clientDataJSON": b64_cdata_json, 823 "authenticatorData": b64_adata, 824 "transports": [], 825 "publicKey": b64_key, 826 "publicKeyAlgorithm": -8i8, 827 "attestationObject": b64_aobj, 828 }, 829 "clientExtensionResults": {}, 830 "type": "public-key" 831 }) 832 .to_string() 833 .as_str() 834 ) 835 .unwrap_err() 836 .to_string() 837 .into_bytes() 838 .get(..err.len()), 839 Some(err.as_slice()) 840 ); 841 // `authenticatorData` mismatches `authData` in attestation object. 842 let mut bad_auth = [0; 113]; 843 let bad_auth_len = bad_auth.len(); 844 bad_auth.copy_from_slice(&att_obj[auth_data_start..]); 845 bad_auth[bad_auth_len - 32..].copy_from_slice([0; 32].as_slice()); 846 err = Error::invalid_value( 847 Unexpected::Bytes(bad_auth.as_slice()), 848 &format!("authenticator data to match the authenticator data portion of attestation object: {:?}", &att_obj[att_obj_len - bad_auth_len..]).as_str(), 849 ) 850 .to_string().into_bytes(); 851 assert_eq!( 852 serde_json::from_str::<RegistrationRelaxed>( 853 serde_json::json!({ 854 "id": "AAAAAAAAAAAAAAAAAAAAAA", 855 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 856 "response": { 857 "clientDataJSON": b64_cdata_json, 858 "authenticatorData": base64url_nopad::encode(bad_auth.as_slice()), 859 "transports": [], 860 "publicKey": b64_key, 861 "publicKeyAlgorithm": -8i8, 862 "attestationObject": b64_aobj, 863 }, 864 "clientExtensionResults": {}, 865 "type": "public-key" 866 }) 867 .to_string() 868 .as_str() 869 ) 870 .unwrap_err() 871 .to_string() 872 .into_bytes() 873 .get(..err.len()), 874 Some(err.as_slice()) 875 ); 876 // Missing `authenticatorData`. 877 drop( 878 serde_json::from_str::<RegistrationRelaxed>( 879 serde_json::json!({ 880 "id": "AAAAAAAAAAAAAAAAAAAAAA", 881 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 882 "response": { 883 "clientDataJSON": b64_cdata_json, 884 "transports": [], 885 "publicKey": b64_key, 886 "publicKeyAlgorithm": -8i8, 887 "attestationObject": b64_aobj, 888 }, 889 "clientExtensionResults": {}, 890 "type": "public-key" 891 }) 892 .to_string() 893 .as_str(), 894 ) 895 .unwrap(), 896 ); 897 // `null `authenticatorData`. 898 drop( 899 serde_json::from_str::<RegistrationRelaxed>( 900 serde_json::json!({ 901 "id": "AAAAAAAAAAAAAAAAAAAAAA", 902 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 903 "response": { 904 "clientDataJSON": b64_cdata_json, 905 "transports": [], 906 "authenticatorData": null, 907 "publicKey": b64_key, 908 "publicKeyAlgorithm": -8i8, 909 "attestationObject": b64_aobj, 910 }, 911 "clientExtensionResults": {}, 912 "type": "public-key" 913 }) 914 .to_string() 915 .as_str(), 916 ) 917 .unwrap(), 918 ); 919 // `publicKeyAlgorithm` mismatch. 920 err = Error::invalid_value( 921 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 922 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Eddsa).as_str() 923 ) 924 .to_string().into_bytes(); 925 assert_eq!( 926 serde_json::from_str::<RegistrationRelaxed>( 927 serde_json::json!({ 928 "id": "AAAAAAAAAAAAAAAAAAAAAA", 929 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 930 "response": { 931 "clientDataJSON": b64_cdata_json, 932 "authenticatorData": b64_adata, 933 "transports": [], 934 "publicKey": b64_key, 935 "publicKeyAlgorithm": -7i8, 936 "attestationObject": b64_aobj, 937 }, 938 "clientExtensionResults": {}, 939 "type": "public-key" 940 }) 941 .to_string() 942 .as_str() 943 ) 944 .unwrap_err() 945 .to_string() 946 .into_bytes() 947 .get(..err.len()), 948 Some(err.as_slice()) 949 ); 950 // Missing `publicKeyAlgorithm`. 951 drop( 952 serde_json::from_str::<RegistrationRelaxed>( 953 serde_json::json!({ 954 "id": "AAAAAAAAAAAAAAAAAAAAAA", 955 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 956 "response": { 957 "clientDataJSON": b64_cdata_json, 958 "authenticatorData": b64_adata, 959 "transports": [], 960 "publicKey": b64_key, 961 "attestationObject": b64_aobj, 962 }, 963 "clientExtensionResults": {}, 964 "type": "public-key" 965 }) 966 .to_string() 967 .as_str(), 968 ) 969 .unwrap(), 970 ); 971 // `null` `publicKeyAlgorithm`. 972 drop( 973 serde_json::from_str::<RegistrationRelaxed>( 974 serde_json::json!({ 975 "id": "AAAAAAAAAAAAAAAAAAAAAA", 976 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 977 "response": { 978 "clientDataJSON": b64_cdata_json, 979 "authenticatorData": b64_adata, 980 "transports": [], 981 "publicKey": b64_key, 982 "publicKeyAlgorithm": null, 983 "attestationObject": b64_aobj, 984 }, 985 "clientExtensionResults": {}, 986 "type": "public-key" 987 }) 988 .to_string() 989 .as_str(), 990 ) 991 .unwrap(), 992 ); 993 // `publicKey` mismatch. 994 err = Error::invalid_value( 995 Unexpected::Bytes([0; 32].as_slice()), 996 &format!( 997 "DER-encoded public key to match the public key within the attestation object: Ed25519(Ed25519PubKey({:?}))", 998 &att_obj[att_obj_len - 32..], 999 ) 1000 .as_str(), 1001 ) 1002 .to_string().into_bytes(); 1003 assert_eq!(serde_json::from_str::<RegistrationRelaxed>( 1004 serde_json::json!({ 1005 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1006 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1007 "response": { 1008 "clientDataJSON": b64_cdata_json, 1009 "authenticatorData": b64_adata, 1010 "transports": [], 1011 "publicKey": base64url_nopad::encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()), 1012 "publicKeyAlgorithm": -8i8, 1013 "attestationObject": b64_aobj, 1014 }, 1015 "clientExtensionResults": {}, 1016 "type": "public-key" 1017 }) 1018 .to_string() 1019 .as_str() 1020 ) 1021 .unwrap_err().to_string().into_bytes().get(..err.len()), 1022 Some(err.as_slice()) 1023 ); 1024 // Missing `publicKey`. 1025 drop( 1026 serde_json::from_str::<RegistrationRelaxed>( 1027 serde_json::json!({ 1028 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1029 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1030 "response": { 1031 "clientDataJSON": b64_cdata_json, 1032 "authenticatorData": b64_adata, 1033 "transports": [], 1034 "publicKeyAlgorithm": -8i8, 1035 "attestationObject": b64_aobj, 1036 }, 1037 "clientExtensionResults": {}, 1038 "type": "public-key" 1039 }) 1040 .to_string() 1041 .as_str(), 1042 ) 1043 .unwrap(), 1044 ); 1045 // `null` `publicKey`. 1046 drop( 1047 serde_json::from_str::<RegistrationRelaxed>( 1048 serde_json::json!({ 1049 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1050 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1051 "response": { 1052 "clientDataJSON": b64_cdata_json, 1053 "authenticatorData": b64_adata, 1054 "transports": [], 1055 "publicKey": null, 1056 "publicKeyAlgorithm": -8i8, 1057 "attestationObject": b64_aobj, 1058 }, 1059 "clientExtensionResults": {}, 1060 "type": "public-key" 1061 }) 1062 .to_string() 1063 .as_str(), 1064 ) 1065 .unwrap(), 1066 ); 1067 // Missing `transports`. 1068 drop( 1069 serde_json::from_str::<RegistrationRelaxed>( 1070 serde_json::json!({ 1071 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1072 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1073 "response": { 1074 "clientDataJSON": b64_cdata_json, 1075 "authenticatorData": b64_adata, 1076 "publicKey": b64_key, 1077 "publicKeyAlgorithm": -8i8, 1078 "attestationObject": b64_aobj, 1079 }, 1080 "clientExtensionResults": {}, 1081 "type": "public-key" 1082 }) 1083 .to_string() 1084 .as_str(), 1085 ) 1086 .unwrap(), 1087 ); 1088 // Duplicate `transports` are allowed. 1089 assert!( 1090 serde_json::from_str::<RegistrationRelaxed>( 1091 serde_json::json!({ 1092 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1093 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1094 "response": { 1095 "clientDataJSON": b64_cdata_json, 1096 "authenticatorData": b64_adata, 1097 "transports": ["usb", "usb"], 1098 "publicKey": b64_key, 1099 "publicKeyAlgorithm": -8i8, 1100 "attestationObject": b64_aobj, 1101 }, 1102 "clientExtensionResults": {}, 1103 "type": "public-key" 1104 }) 1105 .to_string() 1106 .as_str() 1107 ) 1108 .is_ok_and(|reg| reg.0.response.transports.count() == 1) 1109 ); 1110 // `null` `transports`. 1111 drop( 1112 serde_json::from_str::<RegistrationRelaxed>( 1113 serde_json::json!({ 1114 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1115 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1116 "response": { 1117 "clientDataJSON": b64_cdata_json, 1118 "authenticatorData": b64_adata, 1119 "transports": null, 1120 "publicKey": b64_key, 1121 "publicKeyAlgorithm": -8i8, 1122 "attestationObject": b64_aobj, 1123 }, 1124 "clientExtensionResults": {}, 1125 "type": "public-key" 1126 }) 1127 .to_string() 1128 .as_str(), 1129 ) 1130 .unwrap(), 1131 ); 1132 // Unknown `transports`. 1133 err = Error::invalid_value( 1134 Unexpected::Str("Usb"), 1135 &"'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', or 'usb'", 1136 ) 1137 .to_string() 1138 .into_bytes(); 1139 assert_eq!( 1140 serde_json::from_str::<RegistrationRelaxed>( 1141 serde_json::json!({ 1142 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1143 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1144 "response": { 1145 "clientDataJSON": b64_cdata_json, 1146 "authenticatorData": b64_adata, 1147 "transports": ["Usb"], 1148 "publicKey": b64_key, 1149 "publicKeyAlgorithm": -8i8, 1150 "attestationObject": b64_aobj, 1151 }, 1152 "clientExtensionResults": {}, 1153 "type": "public-key" 1154 }) 1155 .to_string() 1156 .as_str() 1157 ) 1158 .unwrap_err() 1159 .to_string() 1160 .into_bytes() 1161 .get(..err.len()), 1162 Some(err.as_slice()) 1163 ); 1164 // `null` `authenticatorAttachment`. 1165 assert!( 1166 serde_json::from_str::<RegistrationRelaxed>( 1167 serde_json::json!({ 1168 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1169 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1170 "response": { 1171 "clientDataJSON": b64_cdata_json, 1172 "authenticatorData": b64_adata, 1173 "transports": [], 1174 "publicKey": b64_key, 1175 "publicKeyAlgorithm": -8i8, 1176 "attestationObject": b64_aobj, 1177 }, 1178 "authenticatorAttachment": null, 1179 "clientExtensionResults": {}, 1180 "type": "public-key" 1181 }) 1182 .to_string() 1183 .as_str() 1184 ) 1185 .is_ok_and(|reg| matches!( 1186 reg.0.authenticator_attachment, 1187 AuthenticatorAttachment::None 1188 )) 1189 ); 1190 // Unknown `authenticatorAttachment`. 1191 err = Error::invalid_value( 1192 Unexpected::Str("Platform"), 1193 &"'platform' or 'cross-platform'", 1194 ) 1195 .to_string() 1196 .into_bytes(); 1197 assert_eq!( 1198 serde_json::from_str::<RegistrationRelaxed>( 1199 serde_json::json!({ 1200 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1201 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1202 "response": { 1203 "clientDataJSON": b64_cdata_json, 1204 "authenticatorData": b64_adata, 1205 "transports": [], 1206 "publicKey": b64_key, 1207 "publicKeyAlgorithm": -8i8, 1208 "attestationObject": b64_aobj, 1209 }, 1210 "authenticatorAttachment": "Platform", 1211 "clientExtensionResults": {}, 1212 "type": "public-key" 1213 }) 1214 .to_string() 1215 .as_str() 1216 ) 1217 .unwrap_err() 1218 .to_string() 1219 .into_bytes() 1220 .get(..err.len()), 1221 Some(err.as_slice()) 1222 ); 1223 // Missing `clientDataJSON`. 1224 err = Error::missing_field("clientDataJSON") 1225 .to_string() 1226 .into_bytes(); 1227 assert_eq!( 1228 serde_json::from_str::<RegistrationRelaxed>( 1229 serde_json::json!({ 1230 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1231 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1232 "response": { 1233 "authenticatorData": b64_adata, 1234 "transports": [], 1235 "publicKey": b64_key, 1236 "publicKeyAlgorithm": -8i8, 1237 "attestationObject": b64_aobj, 1238 }, 1239 "clientExtensionResults": {}, 1240 "type": "public-key" 1241 }) 1242 .to_string() 1243 .as_str() 1244 ) 1245 .unwrap_err() 1246 .to_string() 1247 .into_bytes() 1248 .get(..err.len()), 1249 Some(err.as_slice()) 1250 ); 1251 // `null` `clientDataJSON`. 1252 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 1253 .to_string() 1254 .into_bytes(); 1255 assert_eq!( 1256 serde_json::from_str::<RegistrationRelaxed>( 1257 serde_json::json!({ 1258 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1259 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1260 "response": { 1261 "clientDataJSON": null, 1262 "authenticatorData": b64_adata, 1263 "transports": [], 1264 "publicKey": b64_key, 1265 "publicKeyAlgorithm": -8i8, 1266 "attestationObject": b64_aobj, 1267 }, 1268 "clientExtensionResults": {}, 1269 "type": "public-key" 1270 }) 1271 .to_string() 1272 .as_str() 1273 ) 1274 .unwrap_err() 1275 .to_string() 1276 .into_bytes() 1277 .get(..err.len()), 1278 Some(err.as_slice()) 1279 ); 1280 // Missing `attestationObject`. 1281 err = Error::missing_field("attestationObject") 1282 .to_string() 1283 .into_bytes(); 1284 assert_eq!( 1285 serde_json::from_str::<RegistrationRelaxed>( 1286 serde_json::json!({ 1287 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1288 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1289 "response": { 1290 "clientDataJSON": b64_cdata_json, 1291 "authenticatorData": b64_adata, 1292 "transports": [], 1293 "publicKey": b64_key, 1294 "publicKeyAlgorithm": -8i8, 1295 }, 1296 "clientExtensionResults": {}, 1297 "type": "public-key" 1298 }) 1299 .to_string() 1300 .as_str() 1301 ) 1302 .unwrap_err() 1303 .to_string() 1304 .into_bytes() 1305 .get(..err.len()), 1306 Some(err.as_slice()) 1307 ); 1308 // `null` `attestationObject`. 1309 err = Error::invalid_type( 1310 Unexpected::Other("null"), 1311 &"base64url-encoded attestation object", 1312 ) 1313 .to_string() 1314 .into_bytes(); 1315 assert_eq!( 1316 serde_json::from_str::<RegistrationRelaxed>( 1317 serde_json::json!({ 1318 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1319 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1320 "response": { 1321 "clientDataJSON": b64_cdata_json, 1322 "authenticatorData": b64_adata, 1323 "transports": [], 1324 "publicKey": b64_key, 1325 "publicKeyAlgorithm": -8i8, 1326 "attestationObject": null, 1327 }, 1328 "clientExtensionResults": {}, 1329 "type": "public-key" 1330 }) 1331 .to_string() 1332 .as_str() 1333 ) 1334 .unwrap_err() 1335 .to_string() 1336 .into_bytes() 1337 .get(..err.len()), 1338 Some(err.as_slice()) 1339 ); 1340 // Missing `response`. 1341 err = Error::missing_field("response").to_string().into_bytes(); 1342 assert_eq!( 1343 serde_json::from_str::<RegistrationRelaxed>( 1344 serde_json::json!({ 1345 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1346 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1347 "clientExtensionResults": {}, 1348 "type": "public-key" 1349 }) 1350 .to_string() 1351 .as_str() 1352 ) 1353 .unwrap_err() 1354 .to_string() 1355 .into_bytes() 1356 .get(..err.len()), 1357 Some(err.as_slice()) 1358 ); 1359 // `null` `response`. 1360 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAttestation") 1361 .to_string() 1362 .into_bytes(); 1363 assert_eq!( 1364 serde_json::from_str::<RegistrationRelaxed>( 1365 serde_json::json!({ 1366 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1367 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1368 "response": null, 1369 "clientExtensionResults": {}, 1370 "type": "public-key" 1371 }) 1372 .to_string() 1373 .as_str() 1374 ) 1375 .unwrap_err() 1376 .to_string() 1377 .into_bytes() 1378 .get(..err.len()), 1379 Some(err.as_slice()) 1380 ); 1381 // Empty `response`. 1382 err = Error::missing_field("clientDataJSON") 1383 .to_string() 1384 .into_bytes(); 1385 assert_eq!( 1386 serde_json::from_str::<RegistrationRelaxed>( 1387 serde_json::json!({ 1388 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1389 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1390 "response": {}, 1391 "clientExtensionResults": {}, 1392 "type": "public-key" 1393 }) 1394 .to_string() 1395 .as_str() 1396 ) 1397 .unwrap_err() 1398 .to_string() 1399 .into_bytes() 1400 .get(..err.len()), 1401 Some(err.as_slice()) 1402 ); 1403 // Missing `clientExtensionResults`. 1404 drop( 1405 serde_json::from_str::<RegistrationRelaxed>( 1406 serde_json::json!({ 1407 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1408 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1409 "response": { 1410 "clientDataJSON": b64_cdata_json, 1411 "authenticatorData": b64_adata, 1412 "transports": [], 1413 "publicKey": b64_key, 1414 "publicKeyAlgorithm": -8i8, 1415 "attestationObject": b64_aobj, 1416 }, 1417 "type": "public-key" 1418 }) 1419 .to_string() 1420 .as_str(), 1421 ) 1422 .unwrap(), 1423 ); 1424 // `null` `clientExtensionResults`. 1425 drop( 1426 serde_json::from_str::<RegistrationRelaxed>( 1427 serde_json::json!({ 1428 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1429 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1430 "response": { 1431 "clientDataJSON": b64_cdata_json, 1432 "authenticatorData": b64_adata, 1433 "transports": [], 1434 "publicKey": b64_key, 1435 "publicKeyAlgorithm": -8i8, 1436 "attestationObject": b64_aobj, 1437 }, 1438 "clientExtensionResults": null, 1439 "type": "public-key" 1440 }) 1441 .to_string() 1442 .as_str(), 1443 ) 1444 .unwrap(), 1445 ); 1446 // Missing `type`. 1447 drop( 1448 serde_json::from_str::<RegistrationRelaxed>( 1449 serde_json::json!({ 1450 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1451 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1452 "response": { 1453 "clientDataJSON": b64_cdata_json, 1454 "authenticatorData": b64_adata, 1455 "transports": [], 1456 "publicKey": b64_key, 1457 "publicKeyAlgorithm": -8i8, 1458 "attestationObject": b64_aobj, 1459 }, 1460 "clientExtensionResults": {}, 1461 }) 1462 .to_string() 1463 .as_str(), 1464 ) 1465 .unwrap(), 1466 ); 1467 // `null` `type`. 1468 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 1469 .to_string() 1470 .into_bytes(); 1471 assert_eq!( 1472 serde_json::from_str::<RegistrationRelaxed>( 1473 serde_json::json!({ 1474 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1475 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1476 "response": { 1477 "clientDataJSON": b64_cdata_json, 1478 "authenticatorData": b64_adata, 1479 "transports": [], 1480 "publicKey": b64_key, 1481 "publicKeyAlgorithm": -8i8, 1482 "attestationObject": b64_aobj, 1483 }, 1484 "clientExtensionResults": {}, 1485 "type": null 1486 }) 1487 .to_string() 1488 .as_str() 1489 ) 1490 .unwrap_err() 1491 .to_string() 1492 .into_bytes() 1493 .get(..err.len()), 1494 Some(err.as_slice()) 1495 ); 1496 // Not exactly `public-type` `type`. 1497 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 1498 .to_string() 1499 .into_bytes(); 1500 assert_eq!( 1501 serde_json::from_str::<RegistrationRelaxed>( 1502 serde_json::json!({ 1503 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1504 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1505 "response": { 1506 "clientDataJSON": b64_cdata_json, 1507 "authenticatorData": b64_adata, 1508 "transports": [], 1509 "publicKey": b64_key, 1510 "publicKeyAlgorithm": -8i8, 1511 "attestationObject": b64_aobj, 1512 }, 1513 "clientExtensionResults": {}, 1514 "type": "Public-key" 1515 }) 1516 .to_string() 1517 .as_str() 1518 ) 1519 .unwrap_err() 1520 .to_string() 1521 .into_bytes() 1522 .get(..err.len()), 1523 Some(err.as_slice()) 1524 ); 1525 // `null`. 1526 err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential") 1527 .to_string() 1528 .into_bytes(); 1529 assert_eq!( 1530 serde_json::from_str::<RegistrationRelaxed>( 1531 serde_json::json!(null).to_string().as_str() 1532 ) 1533 .unwrap_err() 1534 .to_string() 1535 .into_bytes() 1536 .get(..err.len()), 1537 Some(err.as_slice()) 1538 ); 1539 // Empty. 1540 err = Error::missing_field("response").to_string().into_bytes(); 1541 assert_eq!( 1542 serde_json::from_str::<RegistrationRelaxed>(serde_json::json!({}).to_string().as_str()) 1543 .unwrap_err() 1544 .to_string() 1545 .into_bytes() 1546 .get(..err.len()), 1547 Some(err.as_slice()) 1548 ); 1549 // Unknown field in `response`. 1550 drop( 1551 serde_json::from_str::<RegistrationRelaxed>( 1552 serde_json::json!({ 1553 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1554 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1555 "response": { 1556 "clientDataJSON": b64_cdata_json, 1557 "authenticatorData": b64_adata, 1558 "transports": [], 1559 "publicKey": b64_key, 1560 "publicKeyAlgorithm": -8i8, 1561 "attestationObject": b64_aobj, 1562 "foo": true, 1563 }, 1564 "clientExtensionResults": {}, 1565 "type": "public-key" 1566 }) 1567 .to_string() 1568 .as_str(), 1569 ) 1570 .unwrap(), 1571 ); 1572 // Duplicate field in `response`. 1573 err = Error::duplicate_field("transports") 1574 .to_string() 1575 .into_bytes(); 1576 assert_eq!( 1577 serde_json::from_str::<RegistrationRelaxed>( 1578 format!( 1579 "{{ 1580 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1581 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1582 \"response\": {{ 1583 \"clientDataJSON\": \"{b64_cdata_json}\", 1584 \"authenticatorData\": \"{b64_adata}\", 1585 \"transports\": [], 1586 \"publicKey\": \"{b64_key}\", 1587 \"publicKeyAlgorithm\": -8, 1588 \"attestationObject\": \"{b64_aobj}\", 1589 \"transports\": [] 1590 }}, 1591 \"clientExtensionResults\": {{}}, 1592 \"type\": \"public-key\" 1593 1594 }}" 1595 ) 1596 .as_str() 1597 ) 1598 .unwrap_err() 1599 .to_string() 1600 .into_bytes() 1601 .get(..err.len()), 1602 Some(err.as_slice()) 1603 ); 1604 // Unknown field in `PublicKeyCredential`. 1605 drop( 1606 serde_json::from_str::<RegistrationRelaxed>( 1607 serde_json::json!({ 1608 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1609 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1610 "response": { 1611 "clientDataJSON": b64_cdata_json, 1612 "authenticatorData": b64_adata, 1613 "transports": [], 1614 "publicKey": b64_key, 1615 "publicKeyAlgorithm": -8i8, 1616 "attestationObject": b64_aobj 1617 }, 1618 "clientExtensionResults": {}, 1619 "type": "public-key", 1620 "foo": true, 1621 }) 1622 .to_string() 1623 .as_str(), 1624 ) 1625 .unwrap(), 1626 ); 1627 // Duplicate field in `PublicKeyCredential`. 1628 err = Error::duplicate_field("id").to_string().into_bytes(); 1629 assert_eq!( 1630 serde_json::from_str::<RegistrationRelaxed>( 1631 format!( 1632 "{{ 1633 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1634 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1635 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 1636 \"response\": {{ 1637 \"clientDataJSON\": \"{b64_cdata_json}\", 1638 \"authenticatorData\": \"{b64_adata}\", 1639 \"transports\": [], 1640 \"publicKey\": \"{b64_key}\", 1641 \"publicKeyAlgorithm\": -8, 1642 \"attestationObject\": \"{b64_aobj}\" 1643 }}, 1644 \"clientExtensionResults\": {{}}, 1645 \"type\": \"public-key\" 1646 1647 }}" 1648 ) 1649 .as_str() 1650 ) 1651 .unwrap_err() 1652 .to_string() 1653 .into_bytes() 1654 .get(..err.len()), 1655 Some(err.as_slice()) 1656 ); 1657 // Base case is correct. 1658 assert!( 1659 serde_json::from_str::<CustomRegistration>( 1660 serde_json::json!({ 1661 "attestationObject": b64_aobj, 1662 "authenticatorAttachment": "cross-platform", 1663 "clientDataJSON": b64_cdata_json, 1664 "clientExtensionResults": {}, 1665 "transports": ["ble", "usb", "hybrid", "internal", "nfc", "smart-card"], 1666 "type": "public-key" 1667 }) 1668 .to_string() 1669 .as_str() 1670 ) 1671 .is_ok_and( 1672 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 1673 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 1674 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 1675 == *Sha256::digest(c_data_json.as_bytes()) 1676 && reg.0.response.transports.count() == 6 1677 && matches!( 1678 reg.0.authenticator_attachment, 1679 AuthenticatorAttachment::CrossPlatform 1680 ) 1681 && reg.0.client_extension_results.cred_props.is_none() 1682 && reg.0.client_extension_results.prf.is_none() 1683 ) 1684 ); 1685 // Missing `transports`. 1686 err = Error::missing_field("transports").to_string().into_bytes(); 1687 assert_eq!( 1688 serde_json::from_str::<CustomRegistration>( 1689 serde_json::json!({ 1690 "attestationObject": b64_aobj, 1691 "authenticatorAttachment": "cross-platform", 1692 "clientDataJSON": b64_cdata_json, 1693 "clientExtensionResults": {}, 1694 "type": "public-key" 1695 }) 1696 .to_string() 1697 .as_str() 1698 ) 1699 .unwrap_err() 1700 .to_string() 1701 .into_bytes() 1702 .get(..err.len()), 1703 Some(err.as_slice()) 1704 ); 1705 // Duplicate `transports` are allowed. 1706 assert!( 1707 serde_json::from_str::<CustomRegistration>( 1708 serde_json::json!({ 1709 "attestationObject": b64_aobj, 1710 "authenticatorAttachment": "cross-platform", 1711 "clientDataJSON": b64_cdata_json, 1712 "clientExtensionResults": {}, 1713 "transports": ["usb", "usb"], 1714 "type": "public-key" 1715 }) 1716 .to_string() 1717 .as_str() 1718 ) 1719 .is_ok_and(|reg| reg.0.response.transports.count() == 1) 1720 ); 1721 // `null` `transports`. 1722 err = Error::invalid_type(Unexpected::Other("null"), &"AuthTransports") 1723 .to_string() 1724 .into_bytes(); 1725 assert_eq!( 1726 serde_json::from_str::<CustomRegistration>( 1727 serde_json::json!({ 1728 "clientDataJSON": b64_cdata_json, 1729 "transports": null, 1730 "attestationObject": b64_aobj, 1731 "clientExtensionResults": {}, 1732 "type": "public-key" 1733 }) 1734 .to_string() 1735 .as_str() 1736 ) 1737 .unwrap_err() 1738 .to_string() 1739 .into_bytes() 1740 .get(..err.len()), 1741 Some(err.as_slice()) 1742 ); 1743 // Unknown `transports`. 1744 err = Error::invalid_value( 1745 Unexpected::Str("Usb"), 1746 &"'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', or 'usb'", 1747 ) 1748 .to_string() 1749 .into_bytes(); 1750 assert_eq!( 1751 serde_json::from_str::<CustomRegistration>( 1752 serde_json::json!({ 1753 "attestationObject": b64_aobj, 1754 "authenticatorAttachment": "cross-platform", 1755 "clientDataJSON": b64_cdata_json, 1756 "clientExtensionResults": {}, 1757 "transports": ["Usb"], 1758 "type": "public-key" 1759 }) 1760 .to_string() 1761 .as_str() 1762 ) 1763 .unwrap_err() 1764 .to_string() 1765 .into_bytes() 1766 .get(..err.len()), 1767 Some(err.as_slice()) 1768 ); 1769 // `null` `authenticatorAttachment`. 1770 assert!( 1771 serde_json::from_str::<CustomRegistration>( 1772 serde_json::json!({ 1773 "attestationObject": b64_aobj, 1774 "authenticatorAttachment": null, 1775 "clientDataJSON": b64_cdata_json, 1776 "clientExtensionResults": {}, 1777 "transports": [], 1778 "type": "public-key" 1779 }) 1780 .to_string() 1781 .as_str() 1782 ) 1783 .is_ok_and(|reg| matches!( 1784 reg.0.authenticator_attachment, 1785 AuthenticatorAttachment::None 1786 )) 1787 ); 1788 // Unknown `authenticatorAttachment`. 1789 err = Error::invalid_value( 1790 Unexpected::Str("Platform"), 1791 &"'platform' or 'cross-platform'", 1792 ) 1793 .to_string() 1794 .into_bytes(); 1795 assert_eq!( 1796 serde_json::from_str::<CustomRegistration>( 1797 serde_json::json!({ 1798 "attestationObject": b64_aobj, 1799 "authenticatorAttachment": "Platform", 1800 "clientDataJSON": b64_cdata_json, 1801 "clientExtensionResults": {}, 1802 "transports": [], 1803 "type": "public-key" 1804 }) 1805 .to_string() 1806 .as_str() 1807 ) 1808 .unwrap_err() 1809 .to_string() 1810 .into_bytes() 1811 .get(..err.len()), 1812 Some(err.as_slice()) 1813 ); 1814 // Missing `clientDataJSON`. 1815 err = Error::missing_field("clientDataJSON") 1816 .to_string() 1817 .into_bytes(); 1818 assert_eq!( 1819 serde_json::from_str::<CustomRegistration>( 1820 serde_json::json!({ 1821 "transports": [], 1822 "attestationObject": b64_aobj, 1823 "clientExtensionResults": {}, 1824 "type": "public-key" 1825 }) 1826 .to_string() 1827 .as_str() 1828 ) 1829 .unwrap_err() 1830 .to_string() 1831 .into_bytes() 1832 .get(..err.len()), 1833 Some(err.as_slice()) 1834 ); 1835 // `null` `clientDataJSON`. 1836 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 1837 .to_string() 1838 .into_bytes(); 1839 assert_eq!( 1840 serde_json::from_str::<CustomRegistration>( 1841 serde_json::json!({ 1842 "clientDataJSON": null, 1843 "transports": [], 1844 "attestationObject": b64_aobj, 1845 }) 1846 .to_string() 1847 .as_str() 1848 ) 1849 .unwrap_err() 1850 .to_string() 1851 .into_bytes() 1852 .get(..err.len()), 1853 Some(err.as_slice()) 1854 ); 1855 // Missing `attestationObject`. 1856 err = Error::missing_field("attestationObject") 1857 .to_string() 1858 .into_bytes(); 1859 assert_eq!( 1860 serde_json::from_str::<CustomRegistration>( 1861 serde_json::json!({ 1862 "clientDataJSON": b64_cdata_json, 1863 "transports": [], 1864 "clientExtensionResults": {}, 1865 "type": "public-key" 1866 }) 1867 .to_string() 1868 .as_str() 1869 ) 1870 .unwrap_err() 1871 .to_string() 1872 .into_bytes() 1873 .get(..err.len()), 1874 Some(err.as_slice()) 1875 ); 1876 // `null` `attestationObject`. 1877 err = Error::invalid_type( 1878 Unexpected::Other("null"), 1879 &"base64url-encoded attestation object", 1880 ) 1881 .to_string() 1882 .into_bytes(); 1883 assert_eq!( 1884 serde_json::from_str::<CustomRegistration>( 1885 serde_json::json!({ 1886 "clientDataJSON": b64_cdata_json, 1887 "transports": [], 1888 "attestationObject": null, 1889 "clientExtensionResults": {}, 1890 "type": "public-key" 1891 }) 1892 .to_string() 1893 .as_str() 1894 ) 1895 .unwrap_err() 1896 .to_string() 1897 .into_bytes() 1898 .get(..err.len()), 1899 Some(err.as_slice()) 1900 ); 1901 // Missing `clientExtensionResults`. 1902 err = Error::missing_field("clientExtensionResults") 1903 .to_string() 1904 .into_bytes(); 1905 assert_eq!( 1906 serde_json::from_str::<CustomRegistration>( 1907 serde_json::json!({ 1908 "clientDataJSON": b64_cdata_json, 1909 "transports": [], 1910 "attestationObject": b64_aobj, 1911 "type": "public-key" 1912 }) 1913 .to_string() 1914 .as_str() 1915 ) 1916 .unwrap_err() 1917 .to_string() 1918 .into_bytes() 1919 .get(..err.len()), 1920 Some(err.as_slice()) 1921 ); 1922 // `null` `clientExtensionResults`. 1923 err = Error::invalid_type(Unexpected::Other("null"), &"ClientExtensionsOutputs") 1924 .to_string() 1925 .into_bytes(); 1926 assert_eq!( 1927 serde_json::from_str::<CustomRegistration>( 1928 serde_json::json!({ 1929 "clientDataJSON": b64_cdata_json, 1930 "transports": [], 1931 "attestationObject": b64_aobj, 1932 "clientExtensionResults": null, 1933 "type": "public-key" 1934 }) 1935 .to_string() 1936 .as_str() 1937 ) 1938 .unwrap_err() 1939 .to_string() 1940 .into_bytes() 1941 .get(..err.len()), 1942 Some(err.as_slice()) 1943 ); 1944 // Missing `type`. 1945 assert!( 1946 serde_json::from_str::<CustomRegistration>( 1947 serde_json::json!({ 1948 "attestationObject": b64_aobj, 1949 "clientDataJSON": b64_cdata_json, 1950 "clientExtensionResults": {}, 1951 "transports": [] 1952 }) 1953 .to_string() 1954 .as_str() 1955 ) 1956 .is_ok_and(|_| true) 1957 ); 1958 // `null` `type`. 1959 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 1960 .to_string() 1961 .into_bytes(); 1962 assert_eq!( 1963 serde_json::from_str::<CustomRegistration>( 1964 serde_json::json!({ 1965 "attestationObject": b64_aobj, 1966 "clientDataJSON": b64_cdata_json, 1967 "clientExtensionResults": {}, 1968 "transports": [], 1969 "type": null 1970 }) 1971 .to_string() 1972 .as_str() 1973 ) 1974 .unwrap_err() 1975 .to_string() 1976 .into_bytes() 1977 .get(..err.len()), 1978 Some(err.as_slice()) 1979 ); 1980 // Not exactly `public-type` `type`. 1981 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 1982 .to_string() 1983 .into_bytes(); 1984 assert_eq!( 1985 serde_json::from_str::<CustomRegistration>( 1986 serde_json::json!({ 1987 "clientDataJSON": b64_cdata_json, 1988 "transports": [], 1989 "attestationObject": b64_aobj, 1990 "clientExtensionResults": {}, 1991 "type": "Public-key" 1992 }) 1993 .to_string() 1994 .as_str() 1995 ) 1996 .unwrap_err() 1997 .to_string() 1998 .into_bytes() 1999 .get(..err.len()), 2000 Some(err.as_slice()) 2001 ); 2002 // `null`. 2003 err = Error::invalid_type(Unexpected::Other("null"), &"CustomRegistration") 2004 .to_string() 2005 .into_bytes(); 2006 assert_eq!( 2007 serde_json::from_str::<CustomRegistration>( 2008 serde_json::json!(null).to_string().as_str() 2009 ) 2010 .unwrap_err() 2011 .to_string() 2012 .into_bytes() 2013 .get(..err.len()), 2014 Some(err.as_slice()) 2015 ); 2016 // Empty. 2017 err = Error::missing_field("attestationObject") 2018 .to_string() 2019 .into_bytes(); 2020 assert_eq!( 2021 serde_json::from_str::<CustomRegistration>(serde_json::json!({}).to_string().as_str()) 2022 .unwrap_err() 2023 .to_string() 2024 .into_bytes() 2025 .get(..err.len()), 2026 Some(err.as_slice()) 2027 ); 2028 // Unknown field. 2029 err = Error::unknown_field( 2030 "foo", 2031 [ 2032 "attestationObject", 2033 "authenticatorAttachment", 2034 "clientDataJSON", 2035 "clientExtensionResults", 2036 "transports", 2037 "type", 2038 ] 2039 .as_slice(), 2040 ) 2041 .to_string() 2042 .into_bytes(); 2043 assert_eq!( 2044 serde_json::from_str::<CustomRegistration>( 2045 serde_json::json!({ 2046 "clientDataJSON": b64_cdata_json, 2047 "transports": [], 2048 "attestationObject": b64_aobj, 2049 "foo": true, 2050 "clientExtensionResults": {}, 2051 "type": "public-key" 2052 }) 2053 .to_string() 2054 .as_str() 2055 ) 2056 .unwrap_err() 2057 .to_string() 2058 .into_bytes() 2059 .get(..err.len()), 2060 Some(err.as_slice()) 2061 ); 2062 // Duplicate field. 2063 err = Error::duplicate_field("transports") 2064 .to_string() 2065 .into_bytes(); 2066 assert_eq!( 2067 serde_json::from_str::<CustomRegistration>( 2068 format!( 2069 "{{ 2070 \"clientDataJSON\": \"{b64_cdata_json}\", 2071 \"transports\": [], 2072 \"attestationObject\": \"{b64_aobj}\", 2073 \"transports\": [] 2074 \"clientExtensionResults\": {{}}, 2075 \"type\": \"public-key\" 2076 }}" 2077 ) 2078 .as_str() 2079 ) 2080 .unwrap_err() 2081 .to_string() 2082 .into_bytes() 2083 .get(..err.len()), 2084 Some(err.as_slice()) 2085 ); 2086 } 2087 #[expect(clippy::unwrap_used, reason = "OK in tests")] 2088 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 2089 #[expect(clippy::too_many_lines, reason = "a lot to test")] 2090 #[test] 2091 fn client_extensions() { 2092 let c_data_json = serde_json::json!({}).to_string(); 2093 let att_obj: [u8; 143] = [ 2094 cbor::MAP_3, 2095 cbor::TEXT_3, 2096 b'f', 2097 b'm', 2098 b't', 2099 cbor::TEXT_4, 2100 b'n', 2101 b'o', 2102 b'n', 2103 b'e', 2104 cbor::TEXT_7, 2105 b'a', 2106 b't', 2107 b't', 2108 b'S', 2109 b't', 2110 b'm', 2111 b't', 2112 cbor::MAP_0, 2113 cbor::TEXT_8, 2114 b'a', 2115 b'u', 2116 b't', 2117 b'h', 2118 b'D', 2119 b'a', 2120 b't', 2121 b'a', 2122 cbor::BYTES_INFO_24, 2123 113, 2124 // `rpIdHash`. 2125 0, 2126 0, 2127 0, 2128 0, 2129 0, 2130 0, 2131 0, 2132 0, 2133 0, 2134 0, 2135 0, 2136 0, 2137 0, 2138 0, 2139 0, 2140 0, 2141 0, 2142 0, 2143 0, 2144 0, 2145 0, 2146 0, 2147 0, 2148 0, 2149 0, 2150 0, 2151 0, 2152 0, 2153 0, 2154 0, 2155 0, 2156 0, 2157 // `flags`. 2158 0b0100_0101, 2159 // `signCount`. 2160 0, 2161 0, 2162 0, 2163 0, 2164 // `aaguid`. 2165 0, 2166 0, 2167 0, 2168 0, 2169 0, 2170 0, 2171 0, 2172 0, 2173 0, 2174 0, 2175 0, 2176 0, 2177 0, 2178 0, 2179 0, 2180 0, 2181 // `credentialIdLength`. 2182 0, 2183 16, 2184 // `credentialId`. 2185 0, 2186 0, 2187 0, 2188 0, 2189 0, 2190 0, 2191 0, 2192 0, 2193 0, 2194 0, 2195 0, 2196 0, 2197 0, 2198 0, 2199 0, 2200 0, 2201 // Ed25519 COSE key. 2202 cbor::MAP_4, 2203 KTY, 2204 OKP, 2205 ALG, 2206 EDDSA, 2207 // `crv`. 2208 cbor::NEG_ONE, 2209 // `Ed25519`. 2210 cbor::SIX, 2211 // `x`. 2212 cbor::NEG_TWO, 2213 cbor::BYTES_INFO_24, 2214 32, 2215 // Compressed y-coordinate. 2216 1, 2217 1, 2218 1, 2219 1, 2220 1, 2221 1, 2222 1, 2223 1, 2224 1, 2225 1, 2226 1, 2227 1, 2228 1, 2229 1, 2230 1, 2231 1, 2232 1, 2233 1, 2234 1, 2235 1, 2236 1, 2237 1, 2238 1, 2239 1, 2240 1, 2241 1, 2242 1, 2243 1, 2244 1, 2245 1, 2246 1, 2247 1, 2248 ]; 2249 let pub_key = VerifyingKey::from_bytes(&[1; 32]) 2250 .unwrap() 2251 .to_public_key_der() 2252 .unwrap(); 2253 let att_obj_len = att_obj.len(); 2254 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 2255 let auth_data_start = att_obj_len - 113; 2256 let b64_adata = base64url_nopad::encode(&att_obj[auth_data_start..]); 2257 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 2258 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 2259 // Base case is valid. 2260 assert!( 2261 serde_json::from_str::<RegistrationRelaxed>( 2262 serde_json::json!({ 2263 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2264 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2265 "response": { 2266 "clientDataJSON": b64_cdata_json, 2267 "authenticatorData": b64_adata, 2268 "transports": [], 2269 "publicKey": b64_key, 2270 "publicKeyAlgorithm": -8i8, 2271 "attestationObject": b64_aobj, 2272 }, 2273 "clientExtensionResults": {}, 2274 "type": "public-key" 2275 }) 2276 .to_string() 2277 .as_str() 2278 ) 2279 .is_ok_and( 2280 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 2281 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 2282 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 2283 == *Sha256::digest(c_data_json.as_bytes()) 2284 && reg.0.response.transports.is_empty() 2285 && matches!( 2286 reg.0.authenticator_attachment, 2287 AuthenticatorAttachment::None 2288 ) 2289 && reg.0.client_extension_results.cred_props.is_none() 2290 && reg.0.client_extension_results.prf.is_none() 2291 ) 2292 ); 2293 // `null` `credProps`. 2294 assert!( 2295 serde_json::from_str::<RegistrationRelaxed>( 2296 serde_json::json!({ 2297 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2298 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2299 "response": { 2300 "clientDataJSON": b64_cdata_json, 2301 "authenticatorData": b64_adata, 2302 "transports": [], 2303 "publicKey": b64_key, 2304 "publicKeyAlgorithm": -8i8, 2305 "attestationObject": b64_aobj, 2306 }, 2307 "clientExtensionResults": { 2308 "credProps": null 2309 }, 2310 "type": "public-key" 2311 }) 2312 .to_string() 2313 .as_str() 2314 ) 2315 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2316 && reg.0.client_extension_results.prf.is_none()) 2317 ); 2318 // `null` `prf`. 2319 assert!( 2320 serde_json::from_str::<RegistrationRelaxed>( 2321 serde_json::json!({ 2322 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2323 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2324 "response": { 2325 "clientDataJSON": b64_cdata_json, 2326 "authenticatorData": b64_adata, 2327 "transports": [], 2328 "publicKey": b64_key, 2329 "publicKeyAlgorithm": -8i8, 2330 "attestationObject": b64_aobj, 2331 }, 2332 "clientExtensionResults": { 2333 "prf": null 2334 }, 2335 "type": "public-key" 2336 }) 2337 .to_string() 2338 .as_str() 2339 ) 2340 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2341 && reg.0.client_extension_results.prf.is_none()) 2342 ); 2343 // Unknown `clientExtensionResults`. 2344 drop( 2345 serde_json::from_str::<RegistrationRelaxed>( 2346 serde_json::json!({ 2347 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2348 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2349 "response": { 2350 "clientDataJSON": b64_cdata_json, 2351 "authenticatorData": b64_adata, 2352 "transports": [], 2353 "publicKey": b64_key, 2354 "publicKeyAlgorithm": -8i8, 2355 "attestationObject": b64_aobj, 2356 }, 2357 "clientExtensionResults": { 2358 "CredProps": { 2359 "rk": true 2360 } 2361 }, 2362 "type": "public-key" 2363 }) 2364 .to_string() 2365 .as_str(), 2366 ) 2367 .unwrap(), 2368 ); 2369 // Duplicate field. 2370 let mut err = Error::duplicate_field("credProps").to_string().into_bytes(); 2371 assert_eq!( 2372 serde_json::from_str::<RegistrationRelaxed>( 2373 format!( 2374 "{{ 2375 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2376 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2377 \"response\": {{ 2378 \"clientDataJSON\": \"{b64_cdata_json}\", 2379 \"authenticatorData\": \"{b64_adata}\", 2380 \"transports\": [], 2381 \"publicKey\": \"{b64_key}\", 2382 \"publicKeyAlgorithm\": -8, 2383 \"attestationObject\": \"{b64_aobj}\" 2384 }}, 2385 \"clientExtensionResults\": {{ 2386 \"credProps\": null, 2387 \"credProps\": null 2388 }}, 2389 \"type\": \"public-key\" 2390 }}" 2391 ) 2392 .as_str() 2393 ) 2394 .unwrap_err() 2395 .to_string() 2396 .into_bytes() 2397 .get(..err.len()), 2398 Some(err.as_slice()) 2399 ); 2400 // `null` `rk`. 2401 assert!( 2402 serde_json::from_str::<RegistrationRelaxed>( 2403 serde_json::json!({ 2404 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2405 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2406 "response": { 2407 "clientDataJSON": b64_cdata_json, 2408 "authenticatorData": b64_adata, 2409 "transports": [], 2410 "publicKey": b64_key, 2411 "publicKeyAlgorithm": -8i8, 2412 "attestationObject": b64_aobj, 2413 }, 2414 "clientExtensionResults": { 2415 "credProps": { 2416 "rk": null 2417 } 2418 }, 2419 "type": "public-key" 2420 }) 2421 .to_string() 2422 .as_str() 2423 ) 2424 .is_ok_and(|reg| reg 2425 .0 2426 .client_extension_results 2427 .cred_props 2428 .is_some_and(|props| props.rk.is_none()) 2429 && reg.0.client_extension_results.prf.is_none()) 2430 ); 2431 // Missing `rk`. 2432 assert!( 2433 serde_json::from_str::<RegistrationRelaxed>( 2434 serde_json::json!({ 2435 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2436 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2437 "response": { 2438 "clientDataJSON": b64_cdata_json, 2439 "authenticatorData": b64_adata, 2440 "transports": [], 2441 "publicKey": b64_key, 2442 "publicKeyAlgorithm": -8i8, 2443 "attestationObject": b64_aobj, 2444 }, 2445 "clientExtensionResults": { 2446 "credProps": {} 2447 }, 2448 "type": "public-key" 2449 }) 2450 .to_string() 2451 .as_str() 2452 ) 2453 .is_ok_and(|reg| reg 2454 .0 2455 .client_extension_results 2456 .cred_props 2457 .is_some_and(|props| props.rk.is_none()) 2458 && reg.0.client_extension_results.prf.is_none()) 2459 ); 2460 // `true` rk`. 2461 assert!( 2462 serde_json::from_str::<RegistrationRelaxed>( 2463 serde_json::json!({ 2464 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2465 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2466 "response": { 2467 "clientDataJSON": b64_cdata_json, 2468 "authenticatorData": b64_adata, 2469 "transports": [], 2470 "publicKey": b64_key, 2471 "publicKeyAlgorithm": -8i8, 2472 "attestationObject": b64_aobj, 2473 }, 2474 "clientExtensionResults": { 2475 "credProps": { 2476 "rk": true 2477 } 2478 }, 2479 "type": "public-key" 2480 }) 2481 .to_string() 2482 .as_str() 2483 ) 2484 .is_ok_and(|reg| reg 2485 .0 2486 .client_extension_results 2487 .cred_props 2488 .is_some_and(|props| props.rk.unwrap_or_default()) 2489 && reg.0.client_extension_results.prf.is_none()) 2490 ); 2491 // `false` rk`. 2492 assert!( 2493 serde_json::from_str::<RegistrationRelaxed>( 2494 serde_json::json!({ 2495 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2496 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2497 "response": { 2498 "clientDataJSON": b64_cdata_json, 2499 "authenticatorData": b64_adata, 2500 "transports": [], 2501 "publicKey": b64_key, 2502 "publicKeyAlgorithm": -8i8, 2503 "attestationObject": b64_aobj, 2504 }, 2505 "clientExtensionResults": { 2506 "credProps": { 2507 "rk": false 2508 } 2509 }, 2510 "type": "public-key" 2511 }) 2512 .to_string() 2513 .as_str() 2514 ) 2515 .is_ok_and(|reg| reg 2516 .0 2517 .client_extension_results 2518 .cred_props 2519 .is_some_and(|props| props.rk.is_some_and(|rk| !rk)) 2520 && reg.0.client_extension_results.prf.is_none()) 2521 ); 2522 // Invalid `rk`. 2523 err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean") 2524 .to_string() 2525 .into_bytes(); 2526 assert_eq!( 2527 serde_json::from_str::<RegistrationRelaxed>( 2528 serde_json::json!({ 2529 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2530 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2531 "response": { 2532 "clientDataJSON": b64_cdata_json, 2533 "authenticatorData": b64_adata, 2534 "transports": [], 2535 "publicKey": b64_key, 2536 "publicKeyAlgorithm": -8i8, 2537 "attestationObject": b64_aobj, 2538 }, 2539 "clientExtensionResults": { 2540 "credProps": { 2541 "rk": 3u8 2542 } 2543 }, 2544 "type": "public-key" 2545 }) 2546 .to_string() 2547 .as_str() 2548 ) 2549 .unwrap_err() 2550 .to_string() 2551 .into_bytes() 2552 .get(..err.len()), 2553 Some(err.as_slice()) 2554 ); 2555 // Unknown `credProps` field. 2556 drop( 2557 serde_json::from_str::<RegistrationRelaxed>( 2558 serde_json::json!({ 2559 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2560 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2561 "response": { 2562 "clientDataJSON": b64_cdata_json, 2563 "authenticatorData": b64_adata, 2564 "transports": [], 2565 "publicKey": b64_key, 2566 "publicKeyAlgorithm": -8i8, 2567 "attestationObject": b64_aobj, 2568 }, 2569 "clientExtensionResults": { 2570 "credProps": { 2571 "Rk": true, 2572 } 2573 }, 2574 "type": "public-key" 2575 }) 2576 .to_string() 2577 .as_str(), 2578 ) 2579 .unwrap(), 2580 ); 2581 // Duplicate field in `credProps`. 2582 err = Error::duplicate_field("rk").to_string().into_bytes(); 2583 assert_eq!( 2584 serde_json::from_str::<RegistrationRelaxed>( 2585 format!( 2586 "{{ 2587 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2588 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2589 \"response\": {{ 2590 \"clientDataJSON\": \"{b64_cdata_json}\", 2591 \"authenticatorData\": \"{b64_adata}\", 2592 \"transports\": [], 2593 \"publicKey\": \"{b64_key}\", 2594 \"publicKeyAlgorithm\": -8, 2595 \"attestationObject\": \"{b64_aobj}\" 2596 }}, 2597 \"clientExtensionResults\": {{ 2598 \"credProps\": {{ 2599 \"rk\": true, 2600 \"rk\": true 2601 }} 2602 }}, 2603 \"type\": \"public-key\" 2604 }}" 2605 ) 2606 .as_str() 2607 ) 2608 .unwrap_err() 2609 .to_string() 2610 .into_bytes() 2611 .get(..err.len()), 2612 Some(err.as_slice()) 2613 ); 2614 // `null` `enabled`. 2615 err = Error::invalid_type(Unexpected::Other("null"), &"a boolean") 2616 .to_string() 2617 .into_bytes(); 2618 assert_eq!( 2619 serde_json::from_str::<RegistrationRelaxed>( 2620 serde_json::json!({ 2621 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2622 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2623 "response": { 2624 "clientDataJSON": b64_cdata_json, 2625 "authenticatorData": b64_adata, 2626 "transports": [], 2627 "publicKey": b64_key, 2628 "publicKeyAlgorithm": -8i8, 2629 "attestationObject": b64_aobj, 2630 }, 2631 "clientExtensionResults": { 2632 "prf": { 2633 "enabled": null 2634 } 2635 }, 2636 "type": "public-key" 2637 }) 2638 .to_string() 2639 .as_str() 2640 ) 2641 .unwrap_err() 2642 .to_string() 2643 .into_bytes() 2644 .get(..err.len()), 2645 Some(err.as_slice()) 2646 ); 2647 // Missing `enabled`. 2648 err = Error::missing_field("enabled").to_string().into_bytes(); 2649 assert_eq!( 2650 serde_json::from_str::<RegistrationRelaxed>( 2651 serde_json::json!({ 2652 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2653 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2654 "response": { 2655 "clientDataJSON": b64_cdata_json, 2656 "authenticatorData": b64_adata, 2657 "transports": [], 2658 "publicKey": b64_key, 2659 "publicKeyAlgorithm": -8i8, 2660 "attestationObject": b64_aobj, 2661 }, 2662 "clientExtensionResults": { 2663 "prf": {} 2664 }, 2665 "type": "public-key" 2666 }) 2667 .to_string() 2668 .as_str() 2669 ) 2670 .unwrap_err() 2671 .to_string() 2672 .into_bytes() 2673 .get(..err.len()), 2674 Some(err.as_slice()) 2675 ); 2676 // `true` `enabled`. 2677 assert!( 2678 serde_json::from_str::<RegistrationRelaxed>( 2679 serde_json::json!({ 2680 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2681 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2682 "response": { 2683 "clientDataJSON": b64_cdata_json, 2684 "authenticatorData": b64_adata, 2685 "transports": [], 2686 "publicKey": b64_key, 2687 "publicKeyAlgorithm": -8i8, 2688 "attestationObject": b64_aobj, 2689 }, 2690 "clientExtensionResults": { 2691 "prf": { 2692 "enabled": true 2693 } 2694 }, 2695 "type": "public-key" 2696 }) 2697 .to_string() 2698 .as_str() 2699 ) 2700 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2701 && reg 2702 .0 2703 .client_extension_results 2704 .prf 2705 .is_some_and(|prf| prf.enabled)) 2706 ); 2707 // `false` `enabled`. 2708 assert!( 2709 serde_json::from_str::<RegistrationRelaxed>( 2710 serde_json::json!({ 2711 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2712 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2713 "response": { 2714 "clientDataJSON": b64_cdata_json, 2715 "authenticatorData": b64_adata, 2716 "transports": [], 2717 "publicKey": b64_key, 2718 "publicKeyAlgorithm": -8i8, 2719 "attestationObject": b64_aobj, 2720 }, 2721 "clientExtensionResults": { 2722 "prf": { 2723 "enabled": false, 2724 } 2725 }, 2726 "type": "public-key" 2727 }) 2728 .to_string() 2729 .as_str() 2730 ) 2731 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2732 && reg 2733 .0 2734 .client_extension_results 2735 .prf 2736 .is_some_and(|prf| !prf.enabled)) 2737 ); 2738 // Invalid `enabled`. 2739 err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean") 2740 .to_string() 2741 .into_bytes(); 2742 assert_eq!( 2743 serde_json::from_str::<RegistrationRelaxed>( 2744 serde_json::json!({ 2745 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2746 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2747 "response": { 2748 "clientDataJSON": b64_cdata_json, 2749 "authenticatorData": b64_adata, 2750 "transports": [], 2751 "publicKey": b64_key, 2752 "publicKeyAlgorithm": -8i8, 2753 "attestationObject": b64_aobj, 2754 }, 2755 "clientExtensionResults": { 2756 "prf": { 2757 "enabled": 3u8 2758 } 2759 }, 2760 "type": "public-key" 2761 }) 2762 .to_string() 2763 .as_str() 2764 ) 2765 .unwrap_err() 2766 .to_string() 2767 .into_bytes() 2768 .get(..err.len()), 2769 Some(err.as_slice()) 2770 ); 2771 // `null` `results` with `enabled` `true`. 2772 assert!( 2773 serde_json::from_str::<RegistrationRelaxed>( 2774 serde_json::json!({ 2775 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2776 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2777 "response": { 2778 "clientDataJSON": b64_cdata_json, 2779 "authenticatorData": b64_adata, 2780 "transports": [], 2781 "publicKey": b64_key, 2782 "publicKeyAlgorithm": -8i8, 2783 "attestationObject": b64_aobj, 2784 }, 2785 "clientExtensionResults": { 2786 "prf": { 2787 "enabled": true, 2788 "results": null, 2789 } 2790 }, 2791 "type": "public-key" 2792 }) 2793 .to_string() 2794 .as_str() 2795 ) 2796 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2797 && reg 2798 .0 2799 .client_extension_results 2800 .prf 2801 .is_some_and(|prf| prf.enabled)) 2802 ); 2803 // `null` `results` with `enabled` `false`. 2804 err = Error::custom( 2805 "prf must not have 'results', including a null 'results', if 'enabled' is false", 2806 ) 2807 .to_string() 2808 .into_bytes(); 2809 assert_eq!( 2810 serde_json::from_str::<RegistrationRelaxed>( 2811 serde_json::json!({ 2812 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2813 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2814 "response": { 2815 "clientDataJSON": b64_cdata_json, 2816 "authenticatorData": b64_adata, 2817 "transports": [], 2818 "publicKey": b64_key, 2819 "publicKeyAlgorithm": -8i8, 2820 "attestationObject": b64_aobj, 2821 }, 2822 "clientExtensionResults": { 2823 "prf": { 2824 "enabled": false, 2825 "results": null 2826 } 2827 }, 2828 "type": "public-key" 2829 }) 2830 .to_string() 2831 .as_str() 2832 ) 2833 .unwrap_err() 2834 .to_string() 2835 .into_bytes() 2836 .get(..err.len()), 2837 Some(err.as_slice()) 2838 ); 2839 // Duplicate field in `prf`. 2840 err = Error::duplicate_field("enabled").to_string().into_bytes(); 2841 assert_eq!( 2842 serde_json::from_str::<RegistrationRelaxed>( 2843 format!( 2844 "{{ 2845 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2846 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2847 \"response\": {{ 2848 \"clientDataJSON\": \"{b64_cdata_json}\", 2849 \"authenticatorData\": \"{b64_adata}\", 2850 \"transports\": [], 2851 \"publicKey\": \"{b64_key}\", 2852 \"publicKeyAlgorithm\": -8, 2853 \"attestationObject\": \"{b64_aobj}\" 2854 }}, 2855 \"clientExtensionResults\": {{ 2856 \"prf\": {{ 2857 \"enabled\": true, 2858 \"enabled\": true 2859 }} 2860 }}, 2861 \"type\": \"public-key\" 2862 }}" 2863 ) 2864 .as_str() 2865 ) 2866 .unwrap_err() 2867 .to_string() 2868 .into_bytes() 2869 .get(..err.len()), 2870 Some(err.as_slice()) 2871 ); 2872 // Missing `first`. 2873 drop( 2874 serde_json::from_str::<RegistrationRelaxed>( 2875 serde_json::json!({ 2876 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2877 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2878 "response": { 2879 "clientDataJSON": b64_cdata_json, 2880 "authenticatorData": b64_adata, 2881 "transports": [], 2882 "publicKey": b64_key, 2883 "publicKeyAlgorithm": -8i8, 2884 "attestationObject": b64_aobj, 2885 }, 2886 "clientExtensionResults": { 2887 "prf": { 2888 "enabled": true, 2889 "results": {}, 2890 } 2891 }, 2892 "type": "public-key" 2893 }) 2894 .to_string() 2895 .as_str(), 2896 ) 2897 .unwrap(), 2898 ); 2899 // `null` `first`. 2900 assert!( 2901 serde_json::from_str::<RegistrationRelaxed>( 2902 serde_json::json!({ 2903 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2904 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2905 "response": { 2906 "clientDataJSON": b64_cdata_json, 2907 "authenticatorData": b64_adata, 2908 "transports": [], 2909 "publicKey": b64_key, 2910 "publicKeyAlgorithm": -8i8, 2911 "attestationObject": b64_aobj, 2912 }, 2913 "clientExtensionResults": { 2914 "prf": { 2915 "enabled": true, 2916 "results": { 2917 "first": null 2918 }, 2919 } 2920 }, 2921 "type": "public-key" 2922 }) 2923 .to_string() 2924 .as_str() 2925 ) 2926 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2927 && reg 2928 .0 2929 .client_extension_results 2930 .prf 2931 .is_some_and(|prf| prf.enabled)) 2932 ); 2933 // `null` `second`. 2934 assert!( 2935 serde_json::from_str::<RegistrationRelaxed>( 2936 serde_json::json!({ 2937 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2938 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2939 "response": { 2940 "clientDataJSON": b64_cdata_json, 2941 "authenticatorData": b64_adata, 2942 "transports": [], 2943 "publicKey": b64_key, 2944 "publicKeyAlgorithm": -8i8, 2945 "attestationObject": b64_aobj, 2946 }, 2947 "clientExtensionResults": { 2948 "prf": { 2949 "enabled": true, 2950 "results": { 2951 "first": null, 2952 "second": null 2953 }, 2954 } 2955 }, 2956 "type": "public-key" 2957 }) 2958 .to_string() 2959 .as_str() 2960 ) 2961 .is_ok_and(|reg| reg.0.client_extension_results.cred_props.is_none() 2962 && reg 2963 .0 2964 .client_extension_results 2965 .prf 2966 .is_some_and(|prf| prf.enabled)) 2967 ); 2968 // Non-`null` `first`. 2969 err = Error::invalid_type(Unexpected::Option, &"null") 2970 .to_string() 2971 .into_bytes(); 2972 assert_eq!( 2973 serde_json::from_str::<RegistrationRelaxed>( 2974 serde_json::json!({ 2975 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2976 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2977 "response": { 2978 "clientDataJSON": b64_cdata_json, 2979 "authenticatorData": b64_adata, 2980 "transports": [], 2981 "publicKey": b64_key, 2982 "publicKeyAlgorithm": -8i8, 2983 "attestationObject": b64_aobj, 2984 }, 2985 "clientExtensionResults": { 2986 "prf": { 2987 "enabled": true, 2988 "results": { 2989 "first": "" 2990 }, 2991 } 2992 }, 2993 "type": "public-key" 2994 }) 2995 .to_string() 2996 .as_str() 2997 ) 2998 .unwrap_err() 2999 .to_string() 3000 .into_bytes() 3001 .get(..err.len()), 3002 Some(err.as_slice()) 3003 ); 3004 // Non-`null` `second`. 3005 err = Error::invalid_type(Unexpected::Option, &"null") 3006 .to_string() 3007 .into_bytes(); 3008 assert_eq!( 3009 serde_json::from_str::<RegistrationRelaxed>( 3010 serde_json::json!({ 3011 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3012 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3013 "response": { 3014 "clientDataJSON": b64_cdata_json, 3015 "authenticatorData": b64_adata, 3016 "transports": [], 3017 "publicKey": b64_key, 3018 "publicKeyAlgorithm": -8i8, 3019 "attestationObject": b64_aobj, 3020 }, 3021 "clientExtensionResults": { 3022 "prf": { 3023 "enabled": true, 3024 "results": { 3025 "first": null, 3026 "second": "" 3027 }, 3028 } 3029 }, 3030 "type": "public-key" 3031 }) 3032 .to_string() 3033 .as_str() 3034 ) 3035 .unwrap_err() 3036 .to_string() 3037 .into_bytes() 3038 .get(..err.len()), 3039 Some(err.as_slice()) 3040 ); 3041 // Unknown `prf` field. 3042 drop( 3043 serde_json::from_str::<RegistrationRelaxed>( 3044 serde_json::json!({ 3045 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3046 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3047 "response": { 3048 "clientDataJSON": b64_cdata_json, 3049 "authenticatorData": b64_adata, 3050 "transports": [], 3051 "publicKey": b64_key, 3052 "publicKeyAlgorithm": -8i8, 3053 "attestationObject": b64_aobj, 3054 }, 3055 "clientExtensionResults": { 3056 "prf": { 3057 "enabled": true, 3058 "Results": null 3059 } 3060 }, 3061 "type": "public-key" 3062 }) 3063 .to_string() 3064 .as_str(), 3065 ) 3066 .unwrap(), 3067 ); 3068 // Unknown `results` field. 3069 drop( 3070 serde_json::from_str::<RegistrationRelaxed>( 3071 serde_json::json!({ 3072 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3073 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3074 "response": { 3075 "clientDataJSON": b64_cdata_json, 3076 "authenticatorData": b64_adata, 3077 "transports": [], 3078 "publicKey": b64_key, 3079 "publicKeyAlgorithm": -8i8, 3080 "attestationObject": b64_aobj, 3081 }, 3082 "clientExtensionResults": { 3083 "prf": { 3084 "enabled": true, 3085 "results": { 3086 "first": null, 3087 "Second": null 3088 } 3089 } 3090 }, 3091 "type": "public-key" 3092 }) 3093 .to_string() 3094 .as_str(), 3095 ) 3096 .unwrap(), 3097 ); 3098 // Duplicate field in `results`. 3099 err = Error::duplicate_field("first").to_string().into_bytes(); 3100 assert_eq!( 3101 serde_json::from_str::<RegistrationRelaxed>( 3102 format!( 3103 "{{ 3104 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3105 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3106 \"response\": {{ 3107 \"clientDataJSON\": \"{b64_cdata_json}\", 3108 \"authenticatorData\": \"{b64_adata}\", 3109 \"transports\": [], 3110 \"publicKey\": \"{b64_key}\", 3111 \"publicKeyAlgorithm\": -8, 3112 \"attestationObject\": \"{b64_aobj}\" 3113 }}, 3114 \"clientExtensionResults\": {{ 3115 \"prf\": {{ 3116 \"enabled\": true, 3117 \"results\": {{ 3118 \"first\": null, 3119 \"first\": null 3120 }} 3121 }} 3122 }}, 3123 \"type\": \"public-key\" 3124 }}" 3125 ) 3126 .as_str() 3127 ) 3128 .unwrap_err() 3129 .to_string() 3130 .into_bytes() 3131 .get(..err.len()), 3132 Some(err.as_slice()) 3133 ); 3134 } 3135 #[expect(clippy::unwrap_used, reason = "OK in tests")] 3136 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 3137 #[expect(clippy::too_many_lines, reason = "a lot to test")] 3138 #[test] 3139 fn es256_registration_deserialize_data_mismatch() { 3140 let c_data_json = serde_json::json!({}).to_string(); 3141 let mut att_obj: [u8; 178] = [ 3142 cbor::MAP_3, 3143 cbor::TEXT_3, 3144 b'f', 3145 b'm', 3146 b't', 3147 cbor::TEXT_4, 3148 b'n', 3149 b'o', 3150 b'n', 3151 b'e', 3152 cbor::TEXT_7, 3153 b'a', 3154 b't', 3155 b't', 3156 b'S', 3157 b't', 3158 b'm', 3159 b't', 3160 cbor::MAP_0, 3161 cbor::TEXT_8, 3162 b'a', 3163 b'u', 3164 b't', 3165 b'h', 3166 b'D', 3167 b'a', 3168 b't', 3169 b'a', 3170 cbor::BYTES_INFO_24, 3171 148, 3172 // `rpIdHash`. 3173 0, 3174 0, 3175 0, 3176 0, 3177 0, 3178 0, 3179 0, 3180 0, 3181 0, 3182 0, 3183 0, 3184 0, 3185 0, 3186 0, 3187 0, 3188 0, 3189 0, 3190 0, 3191 0, 3192 0, 3193 0, 3194 0, 3195 0, 3196 0, 3197 0, 3198 0, 3199 0, 3200 0, 3201 0, 3202 0, 3203 0, 3204 0, 3205 // `flags`. 3206 0b0100_0101, 3207 // `signCount`. 3208 0, 3209 0, 3210 0, 3211 0, 3212 // `aaguid`. 3213 0, 3214 0, 3215 0, 3216 0, 3217 0, 3218 0, 3219 0, 3220 0, 3221 0, 3222 0, 3223 0, 3224 0, 3225 0, 3226 0, 3227 0, 3228 0, 3229 // `credentialIdLength`. 3230 0, 3231 16, 3232 // `credentialId`. 3233 0, 3234 0, 3235 0, 3236 0, 3237 0, 3238 0, 3239 0, 3240 0, 3241 0, 3242 0, 3243 0, 3244 0, 3245 0, 3246 0, 3247 0, 3248 0, 3249 // P-256 COSE key. 3250 cbor::MAP_5, 3251 KTY, 3252 EC2, 3253 ALG, 3254 ES256, 3255 // `crv`. 3256 cbor::NEG_ONE, 3257 // `P-256`. 3258 cbor::ONE, 3259 // `x`. 3260 cbor::NEG_TWO, 3261 cbor::BYTES_INFO_24, 3262 32, 3263 // x-coordinate. This will be overwritten later. 3264 0, 3265 0, 3266 0, 3267 0, 3268 0, 3269 0, 3270 0, 3271 0, 3272 0, 3273 0, 3274 0, 3275 0, 3276 0, 3277 0, 3278 0, 3279 0, 3280 0, 3281 0, 3282 0, 3283 0, 3284 0, 3285 0, 3286 0, 3287 0, 3288 0, 3289 0, 3290 0, 3291 0, 3292 0, 3293 0, 3294 0, 3295 0, 3296 // `y`. 3297 cbor::NEG_THREE, 3298 cbor::BYTES_INFO_24, 3299 32, 3300 // y-coordinate. This will be overwritten later. 3301 0, 3302 0, 3303 0, 3304 0, 3305 0, 3306 0, 3307 0, 3308 0, 3309 0, 3310 0, 3311 0, 3312 0, 3313 0, 3314 0, 3315 0, 3316 0, 3317 0, 3318 0, 3319 0, 3320 0, 3321 0, 3322 0, 3323 0, 3324 0, 3325 0, 3326 0, 3327 0, 3328 0, 3329 0, 3330 0, 3331 0, 3332 0, 3333 ]; 3334 let key = P256Key::from_bytes( 3335 &[ 3336 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 3337 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 3338 ] 3339 .into(), 3340 ) 3341 .unwrap() 3342 .public_key(); 3343 let enc_key = key.to_encoded_point(false); 3344 let pub_key = key.to_public_key_der().unwrap(); 3345 let att_obj_len = att_obj.len(); 3346 let x_start = att_obj_len - 67; 3347 let y_meta_start = x_start + 32; 3348 let y_start = y_meta_start + 3; 3349 att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap()); 3350 att_obj[y_start..].copy_from_slice(enc_key.y().unwrap()); 3351 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 3352 let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 148..]); 3353 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 3354 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 3355 // Base case is valid. 3356 assert!( 3357 serde_json::from_str::<RegistrationRelaxed>( 3358 serde_json::json!({ 3359 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3360 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3361 "response": { 3362 "clientDataJSON": b64_cdata_json, 3363 "authenticatorData": b64_adata, 3364 "transports": [], 3365 "publicKey": b64_key, 3366 "publicKeyAlgorithm": -7i8, 3367 "attestationObject": b64_aobj, 3368 }, 3369 "clientExtensionResults": {}, 3370 "type": "public-key" 3371 }) 3372 .to_string() 3373 .as_str() 3374 ) 3375 .is_ok_and( 3376 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 3377 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 3378 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 3379 == *Sha256::digest(c_data_json.as_bytes()) 3380 && reg.0.response.transports.is_empty() 3381 && matches!( 3382 reg.0.authenticator_attachment, 3383 AuthenticatorAttachment::None 3384 ) 3385 && reg.0.client_extension_results.cred_props.is_none() 3386 && reg.0.client_extension_results.prf.is_none() 3387 ) 3388 ); 3389 // `publicKeyAlgorithm` mismatch. 3390 let mut err = Error::invalid_value( 3391 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 3392 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es256).as_str() 3393 ) 3394 .to_string().into_bytes(); 3395 assert_eq!( 3396 serde_json::from_str::<RegistrationRelaxed>( 3397 serde_json::json!({ 3398 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3399 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3400 "response": { 3401 "clientDataJSON": b64_cdata_json, 3402 "authenticatorData": b64_adata, 3403 "transports": [], 3404 "publicKey": b64_key, 3405 "publicKeyAlgorithm": -8i8, 3406 "attestationObject": b64_aobj, 3407 }, 3408 "clientExtensionResults": {}, 3409 "type": "public-key" 3410 }) 3411 .to_string() 3412 .as_str() 3413 ) 3414 .unwrap_err() 3415 .to_string() 3416 .into_bytes() 3417 .get(..err.len()), 3418 Some(err.as_slice()) 3419 ); 3420 // Missing `publicKeyAlgorithm`. 3421 drop( 3422 serde_json::from_str::<RegistrationRelaxed>( 3423 serde_json::json!({ 3424 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3425 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3426 "response": { 3427 "clientDataJSON": b64_cdata_json, 3428 "authenticatorData": b64_adata, 3429 "transports": [], 3430 "publicKey": b64_key, 3431 "attestationObject": b64_aobj, 3432 }, 3433 "clientExtensionResults": {}, 3434 "type": "public-key" 3435 }) 3436 .to_string() 3437 .as_str(), 3438 ) 3439 .unwrap(), 3440 ); 3441 // `null` `publicKeyAlgorithm`. 3442 drop( 3443 serde_json::from_str::<RegistrationRelaxed>( 3444 serde_json::json!({ 3445 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3446 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3447 "response": { 3448 "clientDataJSON": b64_cdata_json, 3449 "authenticatorData": b64_adata, 3450 "transports": [], 3451 "publicKey": b64_key, 3452 "publicKeyAlgorithm": null, 3453 "attestationObject": b64_aobj, 3454 }, 3455 "clientExtensionResults": {}, 3456 "type": "public-key" 3457 }) 3458 .to_string() 3459 .as_str(), 3460 ) 3461 .unwrap(), 3462 ); 3463 // `publicKey` mismatch. 3464 let bad_pub_key = P256PubKey::from_encoded_point(&P256Pt::from_affine_coordinates( 3465 &[ 3466 66, 71, 188, 41, 125, 2, 226, 44, 148, 62, 63, 190, 172, 64, 33, 214, 6, 37, 148, 3467 23, 240, 235, 203, 84, 112, 219, 232, 197, 54, 182, 17, 235, 3468 ] 3469 .into(), 3470 &[ 3471 22, 172, 123, 13, 170, 242, 217, 248, 193, 209, 206, 163, 92, 4, 162, 168, 113, 63, 3472 2, 117, 16, 223, 239, 196, 109, 179, 10, 130, 43, 213, 205, 92, 3473 ] 3474 .into(), 3475 false, 3476 )) 3477 .unwrap(); 3478 err = Error::invalid_value( 3479 Unexpected::Bytes([0; 32].as_slice()), 3480 &format!( 3481 "DER-encoded public key to match the public key within the attestation object: P256(UncompressedP256PubKey({:?}, {:?}))", 3482 &att_obj[x_start..y_meta_start], 3483 &att_obj[y_start..], 3484 ) 3485 .as_str(), 3486 ) 3487 .to_string().into_bytes(); 3488 assert_eq!(serde_json::from_str::<RegistrationRelaxed>( 3489 serde_json::json!({ 3490 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3491 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3492 "response": { 3493 "clientDataJSON": b64_cdata_json, 3494 "authenticatorData": b64_adata, 3495 "transports": [], 3496 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 3497 "publicKeyAlgorithm": -7i8, 3498 "attestationObject": b64_aobj, 3499 }, 3500 "clientExtensionResults": {}, 3501 "type": "public-key" 3502 }) 3503 .to_string() 3504 .as_str() 3505 ) 3506 .unwrap_err().to_string().into_bytes().get(..err.len()), 3507 Some(err.as_slice()) 3508 ); 3509 // Missing `publicKey`. 3510 drop( 3511 serde_json::from_str::<RegistrationRelaxed>( 3512 serde_json::json!({ 3513 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3514 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3515 "response": { 3516 "clientDataJSON": b64_cdata_json, 3517 "authenticatorData": b64_adata, 3518 "transports": [], 3519 "publicKeyAlgorithm": -7i8, 3520 "attestationObject": b64_aobj, 3521 }, 3522 "clientExtensionResults": {}, 3523 "type": "public-key" 3524 }) 3525 .to_string() 3526 .as_str(), 3527 ) 3528 .unwrap(), 3529 ); 3530 // `null` `publicKey`. 3531 drop( 3532 serde_json::from_str::<RegistrationRelaxed>( 3533 serde_json::json!({ 3534 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3535 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3536 "response": { 3537 "clientDataJSON": b64_cdata_json, 3538 "authenticatorData": b64_adata, 3539 "transports": [], 3540 "publicKey": null, 3541 "publicKeyAlgorithm": -7i8, 3542 "attestationObject": b64_aobj, 3543 }, 3544 "clientExtensionResults": {}, 3545 "type": "public-key" 3546 }) 3547 .to_string() 3548 .as_str(), 3549 ) 3550 .unwrap(), 3551 ); 3552 // Base case is valid. 3553 assert!( 3554 serde_json::from_str::<CustomRegistration>( 3555 serde_json::json!({ 3556 "clientDataJSON": b64_cdata_json, 3557 "transports": [], 3558 "attestationObject": b64_aobj, 3559 "clientExtensionResults": {}, 3560 "type": "public-key" 3561 }) 3562 .to_string() 3563 .as_str() 3564 ) 3565 .is_ok_and( 3566 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 3567 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 3568 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 3569 == *Sha256::digest(c_data_json.as_bytes()) 3570 && reg.0.response.transports.is_empty() 3571 && matches!( 3572 reg.0.authenticator_attachment, 3573 AuthenticatorAttachment::None 3574 ) 3575 && reg.0.client_extension_results.cred_props.is_none() 3576 && reg.0.client_extension_results.prf.is_none() 3577 ) 3578 ); 3579 } 3580 #[expect(clippy::unwrap_used, reason = "OK in tests")] 3581 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 3582 #[expect(clippy::too_many_lines, reason = "a lot to test")] 3583 #[test] 3584 fn es384_registration_deserialize_data_mismatch() { 3585 let c_data_json = serde_json::json!({}).to_string(); 3586 let mut att_obj: [u8; 211] = [ 3587 cbor::MAP_3, 3588 cbor::TEXT_3, 3589 b'f', 3590 b'm', 3591 b't', 3592 cbor::TEXT_4, 3593 b'n', 3594 b'o', 3595 b'n', 3596 b'e', 3597 cbor::TEXT_7, 3598 b'a', 3599 b't', 3600 b't', 3601 b'S', 3602 b't', 3603 b'm', 3604 b't', 3605 cbor::MAP_0, 3606 cbor::TEXT_8, 3607 b'a', 3608 b'u', 3609 b't', 3610 b'h', 3611 b'D', 3612 b'a', 3613 b't', 3614 b'a', 3615 cbor::BYTES_INFO_24, 3616 181, 3617 // `rpIdHash`. 3618 0, 3619 0, 3620 0, 3621 0, 3622 0, 3623 0, 3624 0, 3625 0, 3626 0, 3627 0, 3628 0, 3629 0, 3630 0, 3631 0, 3632 0, 3633 0, 3634 0, 3635 0, 3636 0, 3637 0, 3638 0, 3639 0, 3640 0, 3641 0, 3642 0, 3643 0, 3644 0, 3645 0, 3646 0, 3647 0, 3648 0, 3649 0, 3650 // `flags`. 3651 0b0100_0101, 3652 // `signCount`. 3653 0, 3654 0, 3655 0, 3656 0, 3657 // `aaguid`. 3658 0, 3659 0, 3660 0, 3661 0, 3662 0, 3663 0, 3664 0, 3665 0, 3666 0, 3667 0, 3668 0, 3669 0, 3670 0, 3671 0, 3672 0, 3673 0, 3674 // `credentialIdLength`. 3675 0, 3676 16, 3677 // `credentialId`. 3678 0, 3679 0, 3680 0, 3681 0, 3682 0, 3683 0, 3684 0, 3685 0, 3686 0, 3687 0, 3688 0, 3689 0, 3690 0, 3691 0, 3692 0, 3693 0, 3694 // P-384 COSE key. 3695 cbor::MAP_5, 3696 KTY, 3697 EC2, 3698 ALG, 3699 cbor::NEG_INFO_24, 3700 ES384, 3701 // `crv`. 3702 cbor::NEG_ONE, 3703 // `P-384`. 3704 cbor::TWO, 3705 // `x`. 3706 cbor::NEG_TWO, 3707 cbor::BYTES_INFO_24, 3708 48, 3709 // x-coordinate. This will be overwritten later. 3710 0, 3711 0, 3712 0, 3713 0, 3714 0, 3715 0, 3716 0, 3717 0, 3718 0, 3719 0, 3720 0, 3721 0, 3722 0, 3723 0, 3724 0, 3725 0, 3726 0, 3727 0, 3728 0, 3729 0, 3730 0, 3731 0, 3732 0, 3733 0, 3734 0, 3735 0, 3736 0, 3737 0, 3738 0, 3739 0, 3740 0, 3741 0, 3742 0, 3743 0, 3744 0, 3745 0, 3746 0, 3747 0, 3748 0, 3749 0, 3750 0, 3751 0, 3752 0, 3753 0, 3754 0, 3755 0, 3756 0, 3757 0, 3758 // `y`. 3759 cbor::NEG_THREE, 3760 cbor::BYTES_INFO_24, 3761 48, 3762 // y-coordinate. This will be overwritten later. 3763 0, 3764 0, 3765 0, 3766 0, 3767 0, 3768 0, 3769 0, 3770 0, 3771 0, 3772 0, 3773 0, 3774 0, 3775 0, 3776 0, 3777 0, 3778 0, 3779 0, 3780 0, 3781 0, 3782 0, 3783 0, 3784 0, 3785 0, 3786 0, 3787 0, 3788 0, 3789 0, 3790 0, 3791 0, 3792 0, 3793 0, 3794 0, 3795 0, 3796 0, 3797 0, 3798 0, 3799 0, 3800 0, 3801 0, 3802 0, 3803 0, 3804 0, 3805 0, 3806 0, 3807 0, 3808 0, 3809 0, 3810 0, 3811 ]; 3812 let key = P384Key::from_bytes( 3813 &[ 3814 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 3815 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 3816 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 3817 ] 3818 .into(), 3819 ) 3820 .unwrap() 3821 .public_key(); 3822 let enc_key = key.to_encoded_point(false); 3823 let pub_key = key.to_public_key_der().unwrap(); 3824 let att_obj_len = att_obj.len(); 3825 let x_start = att_obj_len - 99; 3826 let y_meta_start = x_start + 48; 3827 let y_start = y_meta_start + 3; 3828 att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap()); 3829 att_obj[y_start..].copy_from_slice(enc_key.y().unwrap()); 3830 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 3831 let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 181..]); 3832 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 3833 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 3834 // Base case is valid. 3835 assert!( 3836 serde_json::from_str::<RegistrationRelaxed>( 3837 serde_json::json!({ 3838 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3839 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3840 "response": { 3841 "clientDataJSON": b64_cdata_json, 3842 "authenticatorData": b64_adata, 3843 "transports": [], 3844 "publicKey": b64_key, 3845 "publicKeyAlgorithm": -35i8, 3846 "attestationObject": b64_aobj, 3847 }, 3848 "clientExtensionResults": {}, 3849 "type": "public-key" 3850 }) 3851 .to_string() 3852 .as_str() 3853 ) 3854 .is_ok_and( 3855 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 3856 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 3857 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 3858 == *Sha256::digest(c_data_json.as_bytes()) 3859 && reg.0.response.transports.is_empty() 3860 && matches!( 3861 reg.0.authenticator_attachment, 3862 AuthenticatorAttachment::None 3863 ) 3864 && reg.0.client_extension_results.cred_props.is_none() 3865 && reg.0.client_extension_results.prf.is_none() 3866 ) 3867 ); 3868 // `publicKeyAlgorithm` mismatch. 3869 let mut err = Error::invalid_value( 3870 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 3871 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 3872 ) 3873 .to_string().into_bytes(); 3874 assert_eq!( 3875 serde_json::from_str::<RegistrationRelaxed>( 3876 serde_json::json!({ 3877 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3878 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3879 "response": { 3880 "clientDataJSON": b64_cdata_json, 3881 "authenticatorData": b64_adata, 3882 "transports": [], 3883 "publicKey": b64_key, 3884 "publicKeyAlgorithm": -7i8, 3885 "attestationObject": b64_aobj, 3886 }, 3887 "clientExtensionResults": {}, 3888 "type": "public-key" 3889 }) 3890 .to_string() 3891 .as_str() 3892 ) 3893 .unwrap_err() 3894 .to_string() 3895 .into_bytes() 3896 .get(..err.len()), 3897 Some(err.as_slice()) 3898 ); 3899 // Missing `publicKeyAlgorithm`. 3900 drop( 3901 serde_json::from_str::<RegistrationRelaxed>( 3902 serde_json::json!({ 3903 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3904 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3905 "response": { 3906 "clientDataJSON": b64_cdata_json, 3907 "authenticatorData": b64_adata, 3908 "transports": [], 3909 "publicKey": b64_key, 3910 "attestationObject": b64_aobj, 3911 }, 3912 "clientExtensionResults": {}, 3913 "type": "public-key" 3914 }) 3915 .to_string() 3916 .as_str(), 3917 ) 3918 .unwrap(), 3919 ); 3920 // `null` `publicKeyAlgorithm`. 3921 drop( 3922 serde_json::from_str::<RegistrationRelaxed>( 3923 serde_json::json!({ 3924 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3925 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3926 "response": { 3927 "clientDataJSON": b64_cdata_json, 3928 "authenticatorData": b64_adata, 3929 "transports": [], 3930 "publicKey": b64_key, 3931 "publicKeyAlgorithm": null, 3932 "attestationObject": b64_aobj, 3933 }, 3934 "clientExtensionResults": {}, 3935 "type": "public-key" 3936 }) 3937 .to_string() 3938 .as_str(), 3939 ) 3940 .unwrap(), 3941 ); 3942 // `publicKey` mismatch. 3943 let bad_pub_key = P384PubKey::from_encoded_point(&P384Pt::from_affine_coordinates( 3944 &[ 3945 192, 10, 27, 46, 66, 67, 80, 98, 33, 230, 156, 95, 1, 135, 150, 110, 64, 243, 22, 3946 118, 5, 255, 107, 44, 234, 111, 217, 105, 125, 114, 39, 7, 126, 2, 191, 111, 48, 3947 93, 234, 175, 18, 172, 59, 28, 97, 106, 178, 152, 3948 ] 3949 .into(), 3950 &[ 3951 57, 36, 196, 12, 109, 129, 253, 115, 88, 154, 6, 43, 195, 85, 169, 5, 230, 51, 28, 3952 205, 142, 28, 150, 35, 24, 222, 170, 253, 14, 248, 84, 151, 109, 191, 152, 111, 3953 222, 70, 134, 247, 109, 171, 211, 33, 214, 217, 200, 111, 3954 ] 3955 .into(), 3956 false, 3957 )) 3958 .unwrap(); 3959 err = Error::invalid_value( 3960 Unexpected::Bytes([0; 32].as_slice()), 3961 &format!( 3962 "DER-encoded public key to match the public key within the attestation object: P384(UncompressedP384PubKey({:?}, {:?}))", 3963 &att_obj[x_start..y_meta_start], 3964 &att_obj[y_start..], 3965 ) 3966 .as_str(), 3967 ) 3968 .to_string().into_bytes(); 3969 assert_eq!(serde_json::from_str::<RegistrationRelaxed>( 3970 serde_json::json!({ 3971 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3972 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3973 "response": { 3974 "clientDataJSON": b64_cdata_json, 3975 "authenticatorData": b64_adata, 3976 "transports": [], 3977 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 3978 "publicKeyAlgorithm": -35i8, 3979 "attestationObject": b64_aobj, 3980 }, 3981 "clientExtensionResults": {}, 3982 "type": "public-key" 3983 }).to_string().as_str() 3984 ).unwrap_err().to_string().into_bytes().get(..err.len()), Some(err.as_slice())); 3985 // Missing `publicKey`. 3986 drop( 3987 serde_json::from_str::<RegistrationRelaxed>( 3988 serde_json::json!({ 3989 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3990 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3991 "response": { 3992 "clientDataJSON": b64_cdata_json, 3993 "authenticatorData": b64_adata, 3994 "transports": [], 3995 "publicKeyAlgorithm": -35i8, 3996 "attestationObject": b64_aobj, 3997 }, 3998 "clientExtensionResults": {}, 3999 "type": "public-key" 4000 }) 4001 .to_string() 4002 .as_str(), 4003 ) 4004 .unwrap(), 4005 ); 4006 // `publicKeyAlgorithm` mismatch when `publicKey` does not exist. 4007 err = Error::invalid_value( 4008 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 4009 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 4010 ) 4011 .to_string().into_bytes(); 4012 assert_eq!( 4013 serde_json::from_str::<RegistrationRelaxed>( 4014 serde_json::json!({ 4015 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4016 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4017 "response": { 4018 "clientDataJSON": b64_cdata_json, 4019 "authenticatorData": b64_adata, 4020 "transports": [], 4021 "publicKeyAlgorithm": -8i8, 4022 "attestationObject": b64_aobj, 4023 }, 4024 "clientExtensionResults": {}, 4025 "type": "public-key" 4026 }) 4027 .to_string() 4028 .as_str() 4029 ) 4030 .unwrap_err() 4031 .to_string() 4032 .into_bytes() 4033 .get(..err.len()), 4034 Some(err.as_slice()) 4035 ); 4036 // `null` `publicKey`. 4037 drop( 4038 serde_json::from_str::<RegistrationRelaxed>( 4039 serde_json::json!({ 4040 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4041 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4042 "response": { 4043 "clientDataJSON": b64_cdata_json, 4044 "authenticatorData": b64_adata, 4045 "transports": [], 4046 "publicKey": null, 4047 "publicKeyAlgorithm": -35i8, 4048 "attestationObject": b64_aobj, 4049 }, 4050 "clientExtensionResults": {}, 4051 "type": "public-key" 4052 }) 4053 .to_string() 4054 .as_str(), 4055 ) 4056 .unwrap(), 4057 ); 4058 // `publicKeyAlgorithm` mismatch when `publicKey` is null. 4059 err = Error::invalid_value( 4060 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 4061 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 4062 ) 4063 .to_string().into_bytes(); 4064 assert_eq!( 4065 serde_json::from_str::<RegistrationRelaxed>( 4066 serde_json::json!({ 4067 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4068 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4069 "response": { 4070 "clientDataJSON": b64_cdata_json, 4071 "authenticatorData": b64_adata, 4072 "transports": [], 4073 "publicKey": null, 4074 "publicKeyAlgorithm": -8i8, 4075 "attestationObject": b64_aobj, 4076 }, 4077 "clientExtensionResults": {}, 4078 "type": "public-key" 4079 }) 4080 .to_string() 4081 .as_str() 4082 ) 4083 .unwrap_err() 4084 .to_string() 4085 .into_bytes() 4086 .get(..err.len()), 4087 Some(err.as_slice()) 4088 ); 4089 // Base case is valid. 4090 assert!( 4091 serde_json::from_str::<CustomRegistration>( 4092 serde_json::json!({ 4093 "clientDataJSON": b64_cdata_json, 4094 "transports": [], 4095 "attestationObject": b64_aobj, 4096 "clientExtensionResults": {}, 4097 "type": "public-key" 4098 }) 4099 .to_string() 4100 .as_str() 4101 ) 4102 .is_ok_and( 4103 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 4104 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 4105 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 4106 == *Sha256::digest(c_data_json.as_bytes()) 4107 && reg.0.response.transports.is_empty() 4108 && matches!( 4109 reg.0.authenticator_attachment, 4110 AuthenticatorAttachment::None 4111 ) 4112 && reg.0.client_extension_results.cred_props.is_none() 4113 && reg.0.client_extension_results.prf.is_none() 4114 ) 4115 ); 4116 } 4117 #[expect(clippy::unwrap_used, reason = "OK in tests")] 4118 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 4119 #[expect(clippy::too_many_lines, reason = "a lot to test")] 4120 #[test] 4121 fn rs256_registration_deserialize_data_mismatch() { 4122 let c_data_json = serde_json::json!({}).to_string(); 4123 let mut att_obj: [u8; 374] = [ 4124 cbor::MAP_3, 4125 cbor::TEXT_3, 4126 b'f', 4127 b'm', 4128 b't', 4129 cbor::TEXT_4, 4130 b'n', 4131 b'o', 4132 b'n', 4133 b'e', 4134 cbor::TEXT_7, 4135 b'a', 4136 b't', 4137 b't', 4138 b'S', 4139 b't', 4140 b'm', 4141 b't', 4142 cbor::MAP_0, 4143 cbor::TEXT_8, 4144 b'a', 4145 b'u', 4146 b't', 4147 b'h', 4148 b'D', 4149 b'a', 4150 b't', 4151 b'a', 4152 cbor::BYTES_INFO_25, 4153 1, 4154 87, 4155 // `rpIdHash`. 4156 0, 4157 0, 4158 0, 4159 0, 4160 0, 4161 0, 4162 0, 4163 0, 4164 0, 4165 0, 4166 0, 4167 0, 4168 0, 4169 0, 4170 0, 4171 0, 4172 0, 4173 0, 4174 0, 4175 0, 4176 0, 4177 0, 4178 0, 4179 0, 4180 0, 4181 0, 4182 0, 4183 0, 4184 0, 4185 0, 4186 0, 4187 0, 4188 // `flags`. 4189 0b0100_0101, 4190 // `signCount`. 4191 0, 4192 0, 4193 0, 4194 0, 4195 // `aaguid`. 4196 0, 4197 0, 4198 0, 4199 0, 4200 0, 4201 0, 4202 0, 4203 0, 4204 0, 4205 0, 4206 0, 4207 0, 4208 0, 4209 0, 4210 0, 4211 0, 4212 // `credentialIdLength`. 4213 0, 4214 16, 4215 // `credentialId`. 4216 0, 4217 0, 4218 0, 4219 0, 4220 0, 4221 0, 4222 0, 4223 0, 4224 0, 4225 0, 4226 0, 4227 0, 4228 0, 4229 0, 4230 0, 4231 0, 4232 // RSA COSE key. 4233 cbor::MAP_4, 4234 KTY, 4235 RSA, 4236 ALG, 4237 cbor::NEG_INFO_25, 4238 // RS256. 4239 1, 4240 0, 4241 // `n`. 4242 cbor::NEG_ONE, 4243 cbor::BYTES_INFO_25, 4244 1, 4245 0, 4246 // n. This will be overwritten later. 4247 0, 4248 0, 4249 0, 4250 0, 4251 0, 4252 0, 4253 0, 4254 0, 4255 0, 4256 0, 4257 0, 4258 0, 4259 0, 4260 0, 4261 0, 4262 0, 4263 0, 4264 0, 4265 0, 4266 0, 4267 0, 4268 0, 4269 0, 4270 0, 4271 0, 4272 0, 4273 0, 4274 0, 4275 0, 4276 0, 4277 0, 4278 0, 4279 0, 4280 0, 4281 0, 4282 0, 4283 0, 4284 0, 4285 0, 4286 0, 4287 0, 4288 0, 4289 0, 4290 0, 4291 0, 4292 0, 4293 0, 4294 0, 4295 0, 4296 0, 4297 0, 4298 0, 4299 0, 4300 0, 4301 0, 4302 0, 4303 0, 4304 0, 4305 0, 4306 0, 4307 0, 4308 0, 4309 0, 4310 0, 4311 0, 4312 0, 4313 0, 4314 0, 4315 0, 4316 0, 4317 0, 4318 0, 4319 0, 4320 0, 4321 0, 4322 0, 4323 0, 4324 0, 4325 0, 4326 0, 4327 0, 4328 0, 4329 0, 4330 0, 4331 0, 4332 0, 4333 0, 4334 0, 4335 0, 4336 0, 4337 0, 4338 0, 4339 0, 4340 0, 4341 0, 4342 0, 4343 0, 4344 0, 4345 0, 4346 0, 4347 0, 4348 0, 4349 0, 4350 0, 4351 0, 4352 0, 4353 0, 4354 0, 4355 0, 4356 0, 4357 0, 4358 0, 4359 0, 4360 0, 4361 0, 4362 0, 4363 0, 4364 0, 4365 0, 4366 0, 4367 0, 4368 0, 4369 0, 4370 0, 4371 0, 4372 0, 4373 0, 4374 0, 4375 0, 4376 0, 4377 0, 4378 0, 4379 0, 4380 0, 4381 0, 4382 0, 4383 0, 4384 0, 4385 0, 4386 0, 4387 0, 4388 0, 4389 0, 4390 0, 4391 0, 4392 0, 4393 0, 4394 0, 4395 0, 4396 0, 4397 0, 4398 0, 4399 0, 4400 0, 4401 0, 4402 0, 4403 0, 4404 0, 4405 0, 4406 0, 4407 0, 4408 0, 4409 0, 4410 0, 4411 0, 4412 0, 4413 0, 4414 0, 4415 0, 4416 0, 4417 0, 4418 0, 4419 0, 4420 0, 4421 0, 4422 0, 4423 0, 4424 0, 4425 0, 4426 0, 4427 0, 4428 0, 4429 0, 4430 0, 4431 0, 4432 0, 4433 0, 4434 0, 4435 0, 4436 0, 4437 0, 4438 0, 4439 0, 4440 0, 4441 0, 4442 0, 4443 0, 4444 0, 4445 0, 4446 0, 4447 0, 4448 0, 4449 0, 4450 0, 4451 0, 4452 0, 4453 0, 4454 0, 4455 0, 4456 0, 4457 0, 4458 0, 4459 0, 4460 0, 4461 0, 4462 0, 4463 0, 4464 0, 4465 0, 4466 0, 4467 0, 4468 0, 4469 0, 4470 0, 4471 0, 4472 0, 4473 0, 4474 0, 4475 0, 4476 0, 4477 0, 4478 0, 4479 0, 4480 0, 4481 0, 4482 0, 4483 0, 4484 0, 4485 0, 4486 0, 4487 0, 4488 0, 4489 0, 4490 0, 4491 0, 4492 0, 4493 0, 4494 0, 4495 0, 4496 0, 4497 0, 4498 0, 4499 0, 4500 0, 4501 0, 4502 0, 4503 // `e`. 4504 cbor::NEG_TWO, 4505 cbor::BYTES | 3, 4506 // e. 4507 1, 4508 0, 4509 1, 4510 ]; 4511 let n = [ 4512 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 4513 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 4514 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 4515 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 4516 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 4517 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 4518 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 4519 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 4520 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 4521 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 4522 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 4523 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 4524 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 4525 153, 79, 0, 133, 78, 7, 218, 165, 241, 4526 ]; 4527 let e = 0x0001_0001u32; 4528 let d = [ 4529 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 4530 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 4531 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 4532 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 4533 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 4534 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 4535 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 4536 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 4537 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 4538 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 4539 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 4540 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 4541 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 4542 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 4543 ]; 4544 let p = BigUint::from_bytes_le( 4545 [ 4546 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60, 4547 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229, 4548 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161, 4549 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16, 4550 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79, 4551 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191, 4552 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188, 4553 24, 247, 4554 ] 4555 .as_slice(), 4556 ); 4557 let p_2 = BigUint::from_bytes_le( 4558 [ 4559 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78, 4560 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177, 4561 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29, 4562 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11, 4563 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252, 4564 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214, 4565 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90, 4566 250, 4567 ] 4568 .as_slice(), 4569 ); 4570 let key = RsaPrivateKey::from_components( 4571 BigUint::from_bytes_le(n.as_slice()), 4572 e.into(), 4573 BigUint::from_bytes_le(d.as_slice()), 4574 vec![p, p_2], 4575 ) 4576 .unwrap() 4577 .to_public_key(); 4578 let pub_key = key.to_public_key_der().unwrap(); 4579 let att_obj_len = att_obj.len(); 4580 let n_start = att_obj_len - 261; 4581 let e_start = n_start + 256; 4582 att_obj[n_start..e_start].copy_from_slice(key.n().to_bytes_be().as_slice()); 4583 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 4584 let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 343..]); 4585 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 4586 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 4587 // Base case is valid. 4588 assert!( 4589 serde_json::from_str::<RegistrationRelaxed>( 4590 serde_json::json!({ 4591 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4592 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4593 "response": { 4594 "clientDataJSON": b64_cdata_json, 4595 "authenticatorData": b64_adata, 4596 "transports": [], 4597 "publicKey": b64_key, 4598 "publicKeyAlgorithm": -257i16, 4599 "attestationObject": b64_aobj, 4600 }, 4601 "clientExtensionResults": {}, 4602 "type": "public-key" 4603 }) 4604 .to_string() 4605 .as_str() 4606 ) 4607 .is_ok_and( 4608 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 4609 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 4610 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 4611 == *Sha256::digest(c_data_json.as_bytes()) 4612 && reg.0.response.transports.is_empty() 4613 && matches!( 4614 reg.0.authenticator_attachment, 4615 AuthenticatorAttachment::None 4616 ) 4617 && reg.0.client_extension_results.cred_props.is_none() 4618 && reg.0.client_extension_results.prf.is_none() 4619 ) 4620 ); 4621 // `publicKeyAlgorithm` mismatch. 4622 let mut err = Error::invalid_value( 4623 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 4624 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Rs256).as_str() 4625 ) 4626 .to_string().into_bytes(); 4627 assert_eq!( 4628 serde_json::from_str::<RegistrationRelaxed>( 4629 serde_json::json!({ 4630 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4631 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4632 "response": { 4633 "clientDataJSON": b64_cdata_json, 4634 "authenticatorData": b64_adata, 4635 "transports": [], 4636 "publicKey": b64_key, 4637 "publicKeyAlgorithm": -8i8, 4638 "attestationObject": b64_aobj, 4639 }, 4640 "clientExtensionResults": {}, 4641 "type": "public-key" 4642 }) 4643 .to_string() 4644 .as_str() 4645 ) 4646 .unwrap_err() 4647 .to_string() 4648 .into_bytes() 4649 .get(..err.len()), 4650 Some(err.as_slice()) 4651 ); 4652 // Missing `publicKeyAlgorithm`. 4653 drop( 4654 serde_json::from_str::<RegistrationRelaxed>( 4655 serde_json::json!({ 4656 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4657 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4658 "response": { 4659 "clientDataJSON": b64_cdata_json, 4660 "authenticatorData": b64_adata, 4661 "transports": [], 4662 "publicKey": b64_key, 4663 "attestationObject": b64_aobj, 4664 }, 4665 "clientExtensionResults": {}, 4666 "type": "public-key" 4667 }) 4668 .to_string() 4669 .as_str(), 4670 ) 4671 .unwrap(), 4672 ); 4673 // `null` `publicKeyAlgorithm`. 4674 drop( 4675 serde_json::from_str::<RegistrationRelaxed>( 4676 serde_json::json!({ 4677 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4678 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4679 "response": { 4680 "clientDataJSON": b64_cdata_json, 4681 "authenticatorData": b64_adata, 4682 "transports": [], 4683 "publicKey": b64_key, 4684 "publicKeyAlgorithm": null, 4685 "attestationObject": b64_aobj, 4686 }, 4687 "clientExtensionResults": {}, 4688 "type": "public-key" 4689 }) 4690 .to_string() 4691 .as_str(), 4692 ) 4693 .unwrap(), 4694 ); 4695 // `publicKey` mismatch. 4696 let bad_pub_key = RsaPrivateKey::from_components( 4697 BigUint::from_bytes_le( 4698 [ 4699 175, 161, 161, 75, 52, 244, 72, 168, 29, 119, 33, 120, 3, 222, 231, 152, 222, 4700 119, 112, 83, 221, 237, 74, 174, 79, 216, 147, 251, 245, 94, 234, 114, 254, 21, 4701 17, 254, 8, 115, 75, 127, 150, 87, 59, 109, 230, 116, 85, 90, 11, 160, 63, 217, 4702 9, 38, 187, 250, 226, 183, 38, 164, 182, 218, 22, 19, 58, 189, 83, 219, 11, 4703 144, 15, 99, 151, 166, 46, 57, 17, 111, 189, 131, 142, 113, 85, 122, 188, 238, 4704 52, 21, 116, 125, 102, 195, 182, 165, 29, 156, 213, 182, 125, 156, 88, 56, 221, 4705 2, 98, 43, 210, 115, 32, 4, 105, 88, 181, 158, 207, 236, 162, 250, 253, 240, 4706 72, 8, 253, 50, 220, 247, 76, 170, 143, 68, 225, 231, 113, 64, 244, 17, 138, 4707 162, 233, 33, 2, 67, 11, 223, 188, 232, 152, 193, 20, 32, 243, 52, 64, 43, 2, 4708 243, 8, 77, 150, 232, 109, 148, 95, 127, 55, 71, 162, 34, 54, 83, 135, 52, 172, 4709 191, 32, 42, 106, 43, 211, 206, 100, 104, 110, 232, 5, 43, 120, 180, 166, 40, 4710 144, 233, 239, 103, 134, 103, 255, 224, 138, 184, 208, 137, 127, 36, 189, 143, 4711 248, 201, 2, 218, 51, 232, 96, 30, 83, 124, 109, 241, 23, 179, 247, 151, 238, 4712 212, 204, 44, 43, 223, 148, 241, 172, 10, 235, 155, 94, 68, 116, 24, 116, 191, 4713 86, 53, 127, 35, 133, 198, 204, 59, 76, 110, 16, 1, 15, 148, 135, 157, 4714 ] 4715 .as_slice(), 4716 ), 4717 0x0001_0001u32.into(), 4718 BigUint::from_bytes_le( 4719 [ 4720 129, 93, 123, 251, 104, 29, 84, 203, 116, 100, 75, 237, 111, 160, 12, 100, 172, 4721 76, 57, 178, 144, 235, 81, 61, 115, 243, 28, 40, 183, 22, 56, 150, 68, 38, 220, 4722 62, 233, 110, 48, 174, 35, 197, 244, 109, 148, 109, 36, 69, 69, 82, 225, 113, 4723 175, 6, 239, 27, 193, 101, 50, 239, 122, 102, 7, 46, 98, 79, 195, 116, 155, 4724 158, 138, 147, 51, 93, 24, 237, 246, 82, 14, 109, 144, 250, 239, 93, 63, 214, 4725 96, 130, 226, 134, 198, 145, 161, 11, 231, 97, 214, 180, 255, 95, 158, 88, 108, 4726 254, 243, 177, 133, 184, 92, 95, 148, 88, 55, 124, 245, 244, 84, 86, 4, 121, 4727 44, 231, 97, 176, 190, 29, 155, 40, 57, 69, 165, 80, 168, 9, 56, 43, 233, 6, 4728 14, 157, 112, 223, 64, 88, 141, 7, 65, 23, 64, 208, 6, 83, 61, 8, 182, 248, 4729 126, 84, 179, 163, 80, 238, 90, 133, 4, 14, 71, 177, 175, 27, 29, 151, 211, 4730 108, 162, 195, 7, 157, 167, 86, 169, 3, 87, 235, 89, 158, 237, 216, 31, 243, 4731 197, 62, 5, 84, 131, 230, 186, 248, 49, 12, 93, 244, 61, 135, 180, 17, 162, 4732 241, 13, 115, 241, 138, 219, 98, 155, 166, 191, 63, 12, 37, 1, 165, 178, 84, 4733 200, 72, 80, 41, 77, 136, 217, 141, 246, 209, 31, 243, 159, 71, 43, 246, 159, 4734 182, 171, 116, 12, 3, 142, 235, 218, 164, 70, 90, 147, 238, 42, 75, 4735 ] 4736 .as_slice(), 4737 ), 4738 vec![ 4739 BigUint::from_bytes_le( 4740 [ 4741 215, 199, 110, 28, 64, 16, 16, 109, 106, 152, 150, 124, 52, 166, 121, 92, 4742 242, 13, 0, 69, 7, 152, 72, 172, 118, 63, 156, 180, 140, 39, 53, 29, 197, 4743 224, 177, 48, 41, 221, 102, 65, 17, 185, 55, 62, 219, 152, 227, 7, 78, 219, 4744 14, 139, 71, 204, 144, 152, 14, 39, 247, 244, 165, 224, 234, 60, 213, 74, 4745 237, 30, 102, 177, 242, 138, 168, 31, 122, 47, 206, 155, 225, 113, 103, 4746 175, 152, 244, 27, 233, 112, 223, 248, 38, 215, 178, 20, 244, 8, 121, 26, 4747 11, 70, 122, 16, 85, 167, 87, 64, 216, 228, 227, 173, 57, 250, 8, 221, 38, 4748 12, 203, 212, 1, 112, 43, 72, 91, 225, 97, 228, 57, 154, 193, 4749 ] 4750 .as_slice(), 4751 ), 4752 BigUint::from_bytes_le( 4753 [ 4754 233, 89, 204, 152, 31, 242, 8, 110, 38, 190, 111, 159, 105, 105, 45, 85, 4755 15, 244, 30, 250, 174, 226, 219, 111, 107, 191, 196, 135, 17, 123, 186, 4756 167, 85, 13, 120, 197, 159, 129, 78, 237, 152, 31, 230, 26, 229, 253, 197, 4757 211, 105, 204, 126, 142, 250, 55, 26, 172, 65, 160, 45, 6, 99, 86, 66, 238, 4758 107, 6, 98, 171, 93, 224, 201, 160, 31, 204, 82, 120, 228, 158, 238, 6, 4759 190, 12, 150, 153, 239, 95, 57, 71, 100, 239, 235, 155, 73, 200, 5, 225, 4760 127, 185, 46, 48, 243, 84, 33, 142, 17, 19, 20, 23, 215, 16, 114, 58, 211, 4761 14, 73, 148, 168, 252, 159, 252, 125, 57, 101, 211, 188, 12, 77, 208, 4762 ] 4763 .as_slice(), 4764 ), 4765 ], 4766 ) 4767 .unwrap() 4768 .to_public_key(); 4769 err = Error::invalid_value( 4770 Unexpected::Bytes([0; 32].as_slice()), 4771 &format!( 4772 "DER-encoded public key to match the public key within the attestation object: Rsa(RsaPubKey({:?}, 65537))", 4773 &att_obj[n_start..e_start], 4774 ) 4775 .as_str(), 4776 ) 4777 .to_string().into_bytes(); 4778 assert_eq!(serde_json::from_str::<RegistrationRelaxed>( 4779 serde_json::json!({ 4780 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4781 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4782 "response": { 4783 "clientDataJSON": b64_cdata_json, 4784 "authenticatorData": b64_adata, 4785 "transports": [], 4786 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 4787 "publicKeyAlgorithm": -257i16, 4788 "attestationObject": b64_aobj, 4789 }, 4790 "clientExtensionResults": {}, 4791 "type": "public-key" 4792 }) 4793 .to_string() 4794 .as_str() 4795 ) 4796 .unwrap_err().to_string().into_bytes().get(..err.len()), 4797 Some(err.as_slice()) 4798 ); 4799 // Missing `publicKey`. 4800 drop( 4801 serde_json::from_str::<RegistrationRelaxed>( 4802 serde_json::json!({ 4803 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4804 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4805 "response": { 4806 "clientDataJSON": b64_cdata_json, 4807 "authenticatorData": b64_adata, 4808 "transports": [], 4809 "publicKeyAlgorithm": -257i16, 4810 "attestationObject": b64_aobj, 4811 }, 4812 "clientExtensionResults": {}, 4813 "type": "public-key" 4814 }) 4815 .to_string() 4816 .as_str(), 4817 ) 4818 .unwrap(), 4819 ); 4820 // `null` `publicKey`. 4821 drop( 4822 serde_json::from_str::<RegistrationRelaxed>( 4823 serde_json::json!({ 4824 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4825 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4826 "response": { 4827 "clientDataJSON": b64_cdata_json, 4828 "authenticatorData": b64_adata, 4829 "transports": [], 4830 "publicKey": null, 4831 "publicKeyAlgorithm": -257i16, 4832 "attestationObject": b64_aobj, 4833 }, 4834 "clientExtensionResults": {}, 4835 "type": "public-key" 4836 }) 4837 .to_string() 4838 .as_str(), 4839 ) 4840 .unwrap(), 4841 ); 4842 // Base case is valid. 4843 assert!( 4844 serde_json::from_str::<CustomRegistration>( 4845 serde_json::json!({ 4846 "clientDataJSON": b64_cdata_json, 4847 "transports": [], 4848 "attestationObject": b64_aobj, 4849 "clientExtensionResults": {}, 4850 "type": "public-key" 4851 }) 4852 .to_string() 4853 .as_str() 4854 ) 4855 .is_ok_and( 4856 |reg| reg.0.response.client_data_json == c_data_json.as_bytes() 4857 && reg.0.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 4858 && reg.0.response.attestation_object_and_c_data_hash[att_obj_len..] 4859 == *Sha256::digest(c_data_json.as_bytes()) 4860 && reg.0.response.transports.is_empty() 4861 && matches!( 4862 reg.0.authenticator_attachment, 4863 AuthenticatorAttachment::None 4864 ) 4865 && reg.0.client_extension_results.cred_props.is_none() 4866 && reg.0.client_extension_results.prf.is_none() 4867 ) 4868 ); 4869 } 4870 }