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