ser.rs (228137B)
1 use super::{ 2 super::{ 3 super::request::register::CoseAlgorithmIdentifier, 4 ser::{ 5 AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues, 6 Base64DecodedVal, ClientExtensions, PublicKeyCredential, 7 }, 8 }, 9 AttestationObject, AttestedCredentialData, AuthTransports, AuthenticationExtensionsPrfOutputs, 10 AuthenticatorAttestation, ClientExtensionsOutputs, CredentialPropertiesOutput, FromCbor as _, 11 Registration, UncompressedPubKey, 12 }; 13 #[cfg(doc)] 14 use super::{AuthenticatorAttachment, CredentialId}; 15 use core::{ 16 fmt::{self, Formatter}, 17 marker::PhantomData, 18 str, 19 }; 20 use rsa::sha2::{Sha256, digest::OutputSizeUser as _}; 21 use serde::de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor}; 22 /// Functionality for deserializing DER-encoded `SubjectPublicKeyInfo` _without_ making copies of data or 23 /// verifying the key is valid. This exists purely to ensure that the public key we receive in JSON is the same as 24 /// the public key in the attestation object. 25 mod spki { 26 use super::super::{ 27 Ed25519PubKey, RsaPubKey, RsaPubKeyErr, UncompressedP256PubKey, UncompressedP384PubKey, 28 UncompressedPubKey, 29 }; 30 use core::fmt::{self, Display, Formatter}; 31 use p256::{ 32 NistP256, 33 elliptic_curve::{Curve, generic_array::typenum::type_operators::ToInt as _}, 34 }; 35 use p384::NistP384; 36 /// Value assigned to the integer type under the universal tag class per 37 /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en). 38 const INTEGER: u8 = 2; 39 /// Value assigned to the bitstring type under the universal tag class per 40 /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en). 41 const BITSTRING: u8 = 3; 42 /// Value assigned to the null type under the universal tag class per 43 /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en). 44 const NULL: u8 = 5; 45 /// Value assigned to the object identifier type under the universal tag class per 46 /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en). 47 const OID: u8 = 6; 48 /// Value assigned to the sequence type under the universal tag class per 49 /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en). 50 const SEQUENCE: u8 = 16; 51 /// Value assigned to a constructed [`SEQUENCE`] per 52 /// [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en). 53 /// 54 /// All sequences are constructed once encoded, so this will likely always be used instead of 55 /// `SEQUENCE`. 56 const CONSTRUCTED_SEQUENCE: u8 = SEQUENCE | 0b0010_0000; 57 /// Length of the header before the compressed y-coordinate in a DER-encoded ASN.1 `SubjectPublicKeyInfo` 58 /// for an Ed25519 public key. 59 const ED25519_HEADER_LEN: usize = 12; 60 /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for Ed25519 public key. 61 const ED25519_LEN: usize = ED25519_HEADER_LEN + ed25519_dalek::PUBLIC_KEY_LENGTH; 62 /// `ED25519_LEN` as a `u8`. 63 // `44 as u8` is clearly OK. 64 #[expect( 65 clippy::as_conversions, 66 clippy::cast_possible_truncation, 67 reason = "comments above justify their correctness" 68 )] 69 const ED25519_LEN_U8: u8 = ED25519_LEN as u8; 70 /// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo` 71 /// for an uncompressed ECDSA public key based on secp256r1/P-256. 72 const P256_HEADER_LEN: usize = 27; 73 /// Number of bytes the x-coordinate takes in an uncompressed P-256 public key. 74 const P256_X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 75 /// Number of bytes the y-coordinate takes in an uncompressed P-256 public key. 76 const P256_Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 77 /// Number of bytes the x and y coordinates take in an uncompressed P-256 public key when concatenated together. 78 const P256_COORD_LEN: usize = P256_X_LEN + P256_Y_LEN; 79 /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key 80 /// based on secp256r1/P-256. 81 const P256_LEN: usize = P256_HEADER_LEN + P256_COORD_LEN; 82 /// `P256_LEN` as a `u8`. 83 // `91 as u8` is clearly OK. 84 #[expect( 85 clippy::as_conversions, 86 clippy::cast_possible_truncation, 87 reason = "comments above justify their correctness" 88 )] 89 const P256_LEN_U8: u8 = P256_LEN as u8; 90 /// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo` 91 /// for an uncompressed ECDSA public key based on secp384r1/P-384. 92 const P384_HEADER_LEN: usize = 24; 93 /// Number of bytes the x-coordinate takes in an uncompressed P-384 public key. 94 const P384_X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 95 /// Number of bytes the y-coordinate takes in an uncompressed P-384 public key. 96 const P384_Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 97 /// Number of bytes the x and y coordinates take in an uncompressed P-384 public key when concatenated together. 98 const P384_COORD_LEN: usize = P384_X_LEN + P384_Y_LEN; 99 /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key 100 /// based on secp384r1/P-384. 101 const P384_LEN: usize = P384_HEADER_LEN + P384_COORD_LEN; 102 /// `P384_LEN` as a `u8`. 103 // `120 as u8` is clearly OK. 104 #[expect( 105 clippy::as_conversions, 106 clippy::cast_possible_truncation, 107 reason = "comments above justify their correctness" 108 )] 109 const P384_LEN_U8: u8 = P384_LEN as u8; 110 /// Error returned from [`SubjectPublicKeyInfo::from_der`]. 111 pub(super) enum SubjectPublicKeyInfoErr { 112 /// The DER-encoded `SubjectPublicKeyInfo` had an invalid length. 113 Len, 114 /// The length of the DER-encoded Ed25519 key was invalid. 115 Ed25519Len, 116 /// The header of the DER-encoded Ed25519 key was invalid. 117 Ed25519Header, 118 /// The length of the DER-encoded P-256 key was invalid. 119 P256Len, 120 /// The header of the DER-encoded P-256 key was invalid. 121 P256Header, 122 /// The length of the DER-encoded P-384 key was invalid. 123 P384Len, 124 /// The header of the DER-encoded P-384 key was invalid. 125 P384Header, 126 /// The length of the DER-encoded RSA key was invalid. 127 RsaLen, 128 /// The DER-encoding of the RSA key was invalid. 129 RsaEncoding, 130 /// The exponent of the DER-encoded RSA key was too large. 131 RsaExponentTooLarge, 132 /// The DER-encoded RSA key was not a valid [`RsaPubKey`]. 133 RsaPubKey(RsaPubKeyErr), 134 } 135 impl Display for SubjectPublicKeyInfoErr { 136 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 137 match *self { 138 Self::Len => { 139 f.write_str("the DER-encoded SubjectPublicKeyInfo had an invalid length") 140 } 141 Self::Ed25519Len => { 142 f.write_str("length of the DER-encoded Ed25519 key was invalid") 143 } 144 Self::Ed25519Header => { 145 f.write_str("header of the DER-encoded Ed25519 key was invalid") 146 } 147 Self::P256Len => f.write_str("length of the DER-encoded P-256 key was invalid"), 148 Self::P256Header => f.write_str("header of the DER-encoded P-256 key was invalid"), 149 Self::P384Len => f.write_str("length of the DER-encoded P-384 key was invalid"), 150 Self::P384Header => f.write_str("header of the DER-encoded P-384 key was invalid"), 151 Self::RsaLen => f.write_str("length of the DER-encoded RSA key was invalid"), 152 Self::RsaEncoding => { 153 f.write_str("the DER-encoding of the RSA public key was invalid") 154 } 155 Self::RsaExponentTooLarge => { 156 f.write_str("the DER-encoded RSA public key had an exponent that was too large") 157 } 158 Self::RsaPubKey(err) => { 159 write!(f, "the DER-encoded RSA public was not valid: {err}") 160 } 161 } 162 } 163 } 164 /// Types that can be deserialized from the DER-encoded ASN.1 `SubjectPublicKeyInfo` as defined in 165 /// [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1) and other applicable RFCs 166 /// and documents (e.g., [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en)). 167 pub(super) trait SubjectPublicKeyInfo<'a>: Sized { 168 /// Transforms the DER-encoded ASN.1 `SubjectPublicKeyInfo` into `Self`. 169 /// 170 /// # Errors 171 /// 172 /// Errors iff `der` does not conform. 173 #[expect(single_use_lifetimes, reason = "false positive")] 174 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr>; 175 } 176 impl<'a> SubjectPublicKeyInfo<'a> for Ed25519PubKey<&'a [u8]> { 177 #[expect(single_use_lifetimes, reason = "false positive")] 178 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { 179 /// ```asn 180 /// SubjectPublicKeyInfo ::= SEQUENCE { 181 /// algorithm AlgorithmIdentifier, 182 /// subjectPublicKey BIT STRING 183 /// } 184 /// 185 /// AlgorithmIdentifier ::= SEQUENCE { 186 /// algorithm OBJECT IDENTIFIER, 187 /// parameters ANY DEFINED BY algorithm OPTIONAL 188 /// } 189 /// ``` 190 /// 191 /// [RFC 8410](https://www.rfc-editor.org/rfc/rfc8410#section-3) requires parameters to not exist 192 /// in `AlgorithmIdentifier`. 193 /// 194 /// RFC 8410 defines the OID as 1.3.101.112 which is encoded as 43.101.112 195 /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en). 196 /// 197 /// RFC 8410 defines the bitstring as a reinterpretation of the byte string. 198 const HEADER: [u8; ED25519_HEADER_LEN] = [ 199 CONSTRUCTED_SEQUENCE, 200 // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract 201 // the "consumed" length. 202 ED25519_LEN_U8 - 2, 203 CONSTRUCTED_SEQUENCE, 204 // AlgorithmIdentifier only contains the OID; thus it's length is 5. 205 3 + 2, 206 OID, 207 3, 208 43, 209 101, 210 112, 211 BITSTRING, 212 // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract 213 // the "consumed" length. 214 ED25519_LEN_U8 - 11, 215 // The number of unused bits. 216 0, 217 ]; 218 der.split_at_checked(HEADER.len()) 219 .ok_or(SubjectPublicKeyInfoErr::Ed25519Len) 220 .and_then(|(header, rem)| { 221 if header == HEADER { 222 if rem.len() == ed25519_dalek::PUBLIC_KEY_LENGTH { 223 Ok(Self(rem)) 224 } else { 225 Err(SubjectPublicKeyInfoErr::Ed25519Len) 226 } 227 } else { 228 Err(SubjectPublicKeyInfoErr::Ed25519Header) 229 } 230 }) 231 } 232 } 233 impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP256PubKey<'a> { 234 #[expect(single_use_lifetimes, reason = "false positive")] 235 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { 236 // ```asn 237 // SubjectPublicKeyInfo ::= SEQUENCE { 238 // algorithm AlgorithmIdentifier, 239 // subjectPublicKey BIT STRING 240 // } 241 // 242 // AlgorithmIdentifier ::= SEQUENCE { 243 // algorithm OBJECT IDENTIFIER, 244 // parameters ANY DEFINED BY algorithm OPTIONAL 245 // } 246 // 247 // ECParameters ::= CHOICE { 248 // namedCurve OBJECT IDENTIFIER 249 // -- implicitCurve NULL 250 // -- specifiedCurve SpecifiedECDomain 251 // } 252 // ``` 253 // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and 254 // be of the `ECParameters` form with the requirement that `namedCurve` is chosen. 255 // 256 // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve 257 // secp256r1 as 1.2.840.10045.3.1.7. The former OID is encoded as 42.134.72.206.61.2.1 and the latter 258 // is encoded as 42.134.72.206.61.3.1.7 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en). 259 // 260 // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the 261 // uncompressed form and only states that the compressed form MAY be supported. In practice this means 262 // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in 263 // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support 264 // DER-encoded payloads containing uncompressed keys. 265 // 266 // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion 267 // to a bitstring simply requires reinterpreting the octet string as a bitstring. 268 269 /// Header of the DER-encoded payload before the public key. 270 const HEADER: [u8; P256_HEADER_LEN] = [ 271 CONSTRUCTED_SEQUENCE, 272 // `P256_LEN_U8` is the length of the entire payload; thus we subtract 273 // the "consumed" length. 274 P256_LEN_U8 - 2, 275 CONSTRUCTED_SEQUENCE, 276 7 + 2 + 8 + 2, 277 OID, 278 7, 279 42, 280 134, 281 72, 282 206, 283 61, 284 2, 285 1, 286 OID, 287 8, 288 42, 289 134, 290 72, 291 206, 292 61, 293 3, 294 1, 295 7, 296 BITSTRING, 297 // `P256_LEN_U8` is the length of the entire payload; thus we subtract 298 // the "consumed" length. 299 P256_LEN_U8 - 25, 300 // The number of unused bits. 301 0, 302 // SEC-1 tag for an uncompressed key. 303 4, 304 ]; 305 der.split_at_checked(HEADER.len()) 306 .ok_or(SubjectPublicKeyInfoErr::P256Len) 307 .and_then(|(header, header_rem)| { 308 if header == HEADER { 309 header_rem 310 .split_at_checked(P256_X_LEN) 311 .ok_or(SubjectPublicKeyInfoErr::P256Len) 312 .and_then(|(x, y)| { 313 if y.len() == P256_Y_LEN { 314 Ok(Self(x, y)) 315 } else { 316 Err(SubjectPublicKeyInfoErr::P256Len) 317 } 318 }) 319 } else { 320 Err(SubjectPublicKeyInfoErr::P256Header) 321 } 322 }) 323 } 324 } 325 impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP384PubKey<'a> { 326 #[expect(single_use_lifetimes, reason = "false positive")] 327 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { 328 // ```asn 329 // SubjectPublicKeyInfo ::= SEQUENCE { 330 // algorithm AlgorithmIdentifier, 331 // subjectPublicKey BIT STRING 332 // } 333 // 334 // AlgorithmIdentifier ::= SEQUENCE { 335 // algorithm OBJECT IDENTIFIER, 336 // parameters ANY DEFINED BY algorithm OPTIONAL 337 // } 338 // 339 // ECParameters ::= CHOICE { 340 // namedCurve OBJECT IDENTIFIER 341 // -- implicitCurve NULL 342 // -- specifiedCurve SpecifiedECDomain 343 // } 344 // ``` 345 // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and 346 // be of the `ECParameters` form with the requirement that `namedCurve` is chosen. 347 // 348 // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve 349 // secp384r1 as 1.3.132.0.34. The former OID is encoded as 42.134.72.206.61.2.1 and the latter 350 // is encoded as 43.129.4.0.34 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en). 351 // 352 // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the 353 // uncompressed form and only states that the compressed form MAY be supported. In practice this means 354 // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in 355 // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support 356 // DER-encoded payloads containing uncompressed keys. 357 // 358 // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion 359 // to a bitstring simply requires reinterpreting the octet string as a bitstring. 360 361 /// Header of the DER-encoded payload before the public key. 362 const HEADER: [u8; P384_HEADER_LEN] = [ 363 CONSTRUCTED_SEQUENCE, 364 // `P384_LEN_U8` is the length of the entire payload; thus we subtract 365 // the "consumed" length. 366 P384_LEN_U8 - 2, 367 CONSTRUCTED_SEQUENCE, 368 7 + 2 + 5 + 2, 369 OID, 370 7, 371 42, 372 134, 373 72, 374 206, 375 61, 376 2, 377 1, 378 OID, 379 5, 380 43, 381 129, 382 4, 383 0, 384 34, 385 BITSTRING, 386 // `P384_LEN_U8` is the length of the entire payload; thus we subtract 387 // the "consumed" length. 388 P384_LEN_U8 - 22, 389 // The number of unused bits. 390 0, 391 // SEC-1 tag for an uncompressed key. 392 4, 393 ]; 394 der.split_at_checked(HEADER.len()) 395 .ok_or(SubjectPublicKeyInfoErr::P384Len) 396 .and_then(|(header, header_rem)| { 397 if header == HEADER { 398 header_rem 399 .split_at_checked(P384_X_LEN) 400 .ok_or(SubjectPublicKeyInfoErr::P384Len) 401 .and_then(|(x, y)| { 402 if y.len() == P384_Y_LEN { 403 Ok(Self(x, y)) 404 } else { 405 Err(SubjectPublicKeyInfoErr::P384Len) 406 } 407 }) 408 } else { 409 Err(SubjectPublicKeyInfoErr::P384Header) 410 } 411 }) 412 } 413 } 414 impl<'a> SubjectPublicKeyInfo<'a> for RsaPubKey<&'a [u8]> { 415 #[expect(single_use_lifetimes, reason = "false positive")] 416 #[expect( 417 clippy::arithmetic_side_effects, 418 clippy::big_endian_bytes, 419 clippy::indexing_slicing, 420 clippy::missing_asserts_for_indexing, 421 reason = "comments justify their correctness" 422 )] 423 #[expect( 424 clippy::too_many_lines, 425 reason = "rsa keys are the only type that would benefit from a modular SubjectPublicKeyInfo (similar to FromCbor). if more types benefit in the future, then this will be done." 426 )] 427 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { 428 // ```asn 429 // SubjectPublicKeyInfo ::= SEQUENCE { 430 // algorithm AlgorithmIdentifier, 431 // subjectPublicKey BIT STRING 432 // } 433 // 434 // AlgorithmIdentifier ::= SEQUENCE { 435 // algorithm OBJECT IDENTIFIER, 436 // parameters ANY DEFINED BY algorithm OPTIONAL 437 // } 438 // 439 // RSAPublicKey ::= SEQUENCE { 440 // modulus INTEGER, -- n 441 // publicExponent INTEGER --e 442 // } 443 // 444 // pkcs-1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) 445 // rsadsi(113549) pkcs(1) 1 446 // } 447 // 448 // rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1} 449 // ``` 450 // [RFC 3279](https://www.rfc-editor.org/rfc/rfc3279#section-2.3.1) requires parameters to exist and 451 // be null. 452 // 453 // RFC 3279 defines the OID for rsaEncryption as 1.2.840.113549.1.1.1 which is encoded as 454 // 42.134.72.134.247.13.1.1.1 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en). 455 // 456 // Note we only allow moduli that are 256 to 2048 bytes in length inclusively. Additionally 457 // we only allow `u32` exponents; consequently all lengths that include the modulus will always be 458 // encoded with two bytes. 459 460 /// `AlgorithmIdentifier` header including the `BITSTRING` and number of bytes the length 461 /// is encoded in of the `BITSTRING` type of `subjectPublicKey` 462 /// (130-128 = 2 bytes to encode the length). 463 const ALG_OID_HEADER: [u8; 17] = [ 464 CONSTRUCTED_SEQUENCE, 465 9 + 2 + 2, 466 OID, 467 9, 468 42, 469 134, 470 72, 471 134, 472 247, 473 13, 474 1, 475 1, 476 1, 477 NULL, 478 0, 479 BITSTRING, 480 130, 481 ]; 482 /// `CONSTRUCTED_SEQUENCE` whose length is encoded in two bytes. 483 const SEQ_LONG: [u8; 2] = [CONSTRUCTED_SEQUENCE, 130]; 484 /// `INTEGER` whose length is encoded in two bytes. 485 const INT_LONG: [u8; 2] = [INTEGER, 130]; 486 der.split_at_checked(SEQ_LONG.len()) 487 .ok_or(SubjectPublicKeyInfoErr::RsaLen) 488 .and_then(|(seq, seq_rem)| { 489 if seq == SEQ_LONG { 490 seq_rem 491 .split_at_checked(2) 492 .ok_or(SubjectPublicKeyInfoErr::RsaLen) 493 .and_then(|(seq_len, seq_len_rem)| { 494 let mut len = [0; 2]; 495 len.copy_from_slice(seq_len); 496 let rem_len = usize::from(u16::from_be_bytes(len)); 497 if rem_len == seq_len_rem.len() { 498 if rem_len > 255 { 499 // We can safely split here since we know `seq_len_rem` is at least 500 // 256 which is greater than `ALG_OID_HEADER.len()`. 501 let (a_oid, a_oid_rem) = seq_len_rem.split_at(ALG_OID_HEADER.len()); 502 if a_oid == ALG_OID_HEADER { 503 // `a_oid_rem.len()` is at least 239, so splitting is fine. 504 let (bit_str_len_enc, bit_str_val) = a_oid_rem.split_at(2); 505 let mut bit_string_len = [0; 2]; 506 bit_string_len.copy_from_slice(bit_str_len_enc); 507 let bit_str_val_len = usize::from(u16::from_be_bytes(bit_string_len)); 508 if bit_str_val_len == bit_str_val.len() { 509 if bit_str_val_len > 255 { 510 // `bit_str_val.len() > 255`, so splitting is fine. 511 let (unused_bits, bits_rem) = bit_str_val.split_at(1); 512 if unused_bits == [0] { 513 // We can safely split here since we know `bits_rem.len()` is at least 514 // 255. 515 let (rsa_seq, rsa_seq_rem) = bits_rem.split_at(SEQ_LONG.len()); 516 if rsa_seq == SEQ_LONG { 517 // `rsa_seq_rem.len()` is at least 253, so splitting is fine. 518 let (rsa_seq_len_enc, rsa_seq_len_enc_rem) = rsa_seq_rem.split_at(2); 519 let mut rsa_seq_len = [0; 2]; 520 rsa_seq_len.copy_from_slice(rsa_seq_len_enc); 521 let rsa_key_info_len = usize::from(u16::from_be_bytes(rsa_seq_len)); 522 if rsa_key_info_len == rsa_seq_len_enc_rem.len() { 523 if rsa_key_info_len > 255 { 524 // We can safely split here since we know `rsa_seq_len_enc_rem.len()` 525 // is at least 256. 526 let (n_meta, n_meta_rem) = rsa_seq_len_enc_rem.split_at(INT_LONG.len()); 527 if n_meta == INT_LONG { 528 // `n_meta_rem.len()` is at least 254, so splitting is fine. 529 let (n_len_enc, n_len_enc_rem) = n_meta_rem.split_at(2); 530 let mut n_len = [0; 2]; 531 n_len.copy_from_slice(n_len_enc); 532 let mod_len = usize::from(u16::from_be_bytes(n_len)); 533 if mod_len > 255 { 534 n_len_enc_rem.split_at_checked(mod_len).ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(mut n, n_rem)| { 535 // `n.len() > 255`, so indexing is fine. 536 let n_first = n[0]; 537 // DER integers are signed; thus the most significant bit must be 0. 538 // DER integers are minimally encoded; thus when a leading 0 exists, 539 // the second byte must be at least 128. 540 // `n.len() > 255`, so indexing is fine. 541 if n_first < 128 && (n_first != 0 || n[1] > 127) { 542 if n_first == 0 { 543 // `n.len() > 255`, so indexing is fine. 544 // We must remove the leading 0. 545 n = &n[1..]; 546 } 547 n_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_type, e_type_rem)| { 548 if *e_type == INTEGER { 549 e_type_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_len, e_len_rem)| { 550 let e_len_usize = usize::from(*e_len); 551 if e_len_usize == e_len_rem.len() { 552 e_len_rem.first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|&e_first| { 553 // DER integers are signed; thus the most significant bit must be 0. 554 if e_first < 128 { 555 // `RsaPubKey` only allows `u32` exponents, which means we only care 556 // about lengths up to 5. 557 match e_len_usize { 558 1 => Ok(u32::from(e_first)), 559 2..=5 => if e_first == 0 { 560 // DER integers are minimally encoded; thus when a leading 561 // 0 exists, the second byte must be at least 128. 562 // We know the length is at least 2; thus this won't `panic`. 563 if e_len_rem[1] > 127 { 564 let mut e = [0; 4]; 565 if e_len_usize == 5 { 566 // We know the length is at least 2; thus this won't `panic`. 567 e.copy_from_slice(&e_len_rem[1..]); 568 } else { 569 // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow 570 // won't occur nor will indexing `panic`. `e` is big-endian, 571 // so we start from the right. 572 e[4 - e_len_usize..].copy_from_slice(e_len_rem); 573 } 574 Ok(u32::from_be_bytes(e)) 575 } else { 576 Err(SubjectPublicKeyInfoErr::RsaEncoding) 577 } 578 } else if e_len_usize == 5 { 579 // 5 bytes are only possible for `INTEGER`s that 580 // are greater than `i32::MAX`, which will be encoded 581 // with a leading 0. 582 Err(SubjectPublicKeyInfoErr::RsaEncoding) 583 } else { 584 let mut e = [0; 4]; 585 // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow 586 // won't occur nor will indexing `panic`. `e` is big-endian, 587 // so we start from the right. 588 e[4 - e_len_usize..].copy_from_slice(e_len_rem); 589 Ok(u32::from_be_bytes(e)) 590 }, 591 _ => Err(SubjectPublicKeyInfoErr::RsaExponentTooLarge), 592 }.and_then(|e| Self::try_from((n, e)).map_err(SubjectPublicKeyInfoErr::RsaPubKey)) 593 } else { 594 Err(SubjectPublicKeyInfoErr::RsaEncoding) 595 } 596 }) 597 } else { 598 Err(SubjectPublicKeyInfoErr::RsaLen) 599 } 600 }) 601 } else { 602 Err(SubjectPublicKeyInfoErr::RsaEncoding) 603 } 604 }) 605 } else { 606 Err(SubjectPublicKeyInfoErr::RsaEncoding) 607 } 608 }) 609 } else { 610 Err(SubjectPublicKeyInfoErr::RsaEncoding) 611 } 612 } else { 613 Err(SubjectPublicKeyInfoErr::RsaEncoding) 614 } 615 } else { 616 Err(SubjectPublicKeyInfoErr::RsaEncoding) 617 } 618 } else { 619 Err(SubjectPublicKeyInfoErr::RsaLen) 620 } 621 } else { 622 Err(SubjectPublicKeyInfoErr::RsaEncoding) 623 } 624 } else { 625 Err(SubjectPublicKeyInfoErr::RsaEncoding) 626 } 627 } else { 628 Err(SubjectPublicKeyInfoErr::RsaEncoding) 629 } 630 } else { 631 Err(SubjectPublicKeyInfoErr::RsaLen) 632 } 633 } else { 634 Err(SubjectPublicKeyInfoErr::RsaEncoding) 635 } 636 } else { 637 Err(SubjectPublicKeyInfoErr::RsaEncoding) 638 } 639 } else { 640 Err(SubjectPublicKeyInfoErr::RsaLen) 641 } 642 }) 643 } else { 644 Err(SubjectPublicKeyInfoErr::RsaEncoding) 645 } 646 }) 647 } 648 } 649 impl<'a> SubjectPublicKeyInfo<'a> for UncompressedPubKey<'a> { 650 #[expect(single_use_lifetimes, reason = "false positive")] 651 fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { 652 // The lengths of the three key types do not overlap. 653 match der.len() { 654 // The minimum modulus we support for RSA is 2048 bits which is 256 bytes; 655 // thus clearly its encoding will be at least 256 which is greater than 656 // all of the other values. 657 ED25519_LEN => Ed25519PubKey::from_der(der).map(Self::Ed25519), 658 P256_LEN => UncompressedP256PubKey::from_der(der).map(Self::P256), 659 P384_LEN => UncompressedP384PubKey::from_der(der).map(Self::P384), 660 256.. => RsaPubKey::from_der(der).map(Self::Rsa), 661 _ => Err(SubjectPublicKeyInfoErr::Len), 662 } 663 } 664 } 665 } 666 /// Helper type returned from [`AuthenticatorAttestationVisitor::visit_map`]. 667 /// 668 /// The purpose of this type is to hopefully avoid re-parsing the raw attestation object multiple times. In 669 /// particular [`Registration`] and [`super::ser_relaxed::RegistrationRelaxed`] will attempt to validate `id` is the 670 /// same as the [`CredentialId`] within the attestation object. 671 pub(super) struct AuthAttest { 672 /// The data we care about. 673 pub attest: AuthenticatorAttestation, 674 /// [`CredentialId`] information. This is `None` iff `authenticatorData`, `publicKey`, and 675 /// `publicKeyAlgorithm` do not exist and we are performing a `RELAXED` parsing. When `Some`, the first 676 /// `usize` is the starting index of `CredentialId` within the attestation object; and the second `usize` is 677 /// 1 past the last index of `CredentialId`. 678 pub cred_info: Option<(usize, usize)>, 679 } 680 /// Fields in `AuthenticatorAttestationResponseJSON`. 681 enum AttestField<const IGNORE_UNKNOWN: bool> { 682 /// `clientDataJSON`. 683 ClientDataJson, 684 /// `attestationObject`. 685 AttestationObject, 686 /// `authenticatorData`. 687 AuthenticatorData, 688 /// `transports`. 689 Transports, 690 /// `publicKey`. 691 PublicKey, 692 /// `publicKeyAlgorithm`. 693 PublicKeyAlgorithm, 694 /// Unknown fields. 695 Other, 696 } 697 impl<'e, const I: bool> Deserialize<'e> for AttestField<I> { 698 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 699 where 700 D: Deserializer<'e>, 701 { 702 /// `Visitor` for `AttestField`. 703 struct AttestFieldVisitor<const IGNORE_UNKNOWN: bool>; 704 impl<const IG: bool> Visitor<'_> for AttestFieldVisitor<IG> { 705 type Value = AttestField<IG>; 706 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 707 write!( 708 formatter, 709 "'{CLIENT_DATA_JSON}', '{ATTESTATION_OBJECT}', '{AUTHENTICATOR_DATA}', '{TRANSPORTS}', '{PUBLIC_KEY}', or '{PUBLIC_KEY_ALGORITHM}'" 710 ) 711 } 712 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 713 where 714 E: Error, 715 { 716 match v { 717 CLIENT_DATA_JSON => Ok(AttestField::ClientDataJson), 718 ATTESTATION_OBJECT => Ok(AttestField::AttestationObject), 719 AUTHENTICATOR_DATA => Ok(AttestField::AuthenticatorData), 720 TRANSPORTS => Ok(AttestField::Transports), 721 PUBLIC_KEY => Ok(AttestField::PublicKey), 722 PUBLIC_KEY_ALGORITHM => Ok(AttestField::PublicKeyAlgorithm), 723 _ => { 724 if IG { 725 Ok(AttestField::Other) 726 } else { 727 Err(E::unknown_field(v, AUTH_ATTEST_FIELDS)) 728 } 729 } 730 } 731 } 732 } 733 deserializer.deserialize_identifier(AttestFieldVisitor::<I>) 734 } 735 } 736 /// Attestation object. We use this instead of `Base64DecodedVal` since we want to manually 737 /// allocate the `Vec` in order to avoid re-allocation. Internally `AuthenticatorAttestation::new` 738 /// appends the SHA-256 hash to the passed attestation object `Vec` to avoid temporarily allocating 739 /// a `Vec` that contains the attestation object and hash for signature verification. Calling code 740 /// can avoid any reallocation that would occur when the capacity is not large enough by ensuring the 741 /// passed `Vec` has at least 32 bytes of available capacity. 742 pub(super) struct AttObj(pub Vec<u8>); 743 impl<'e> Deserialize<'e> for AttObj { 744 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 745 where 746 D: Deserializer<'e>, 747 { 748 /// `Visitor` for `AttObj`. 749 struct AttObjVisitor; 750 impl Visitor<'_> for AttObjVisitor { 751 type Value = AttObj; 752 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 753 formatter.write_str("base64url-encoded attestation object") 754 } 755 #[expect( 756 clippy::arithmetic_side_effects, 757 reason = "comment justifies their correctness" 758 )] 759 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 760 where 761 E: Error, 762 { 763 base64url_nopad::decode_len(v.len()) 764 .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value")) 765 .and_then(|len| { 766 // The decoded length is 3/4 of the encoded length, so overflow could only occur 767 // if usize::MAX / 4 < 32 => usize::MAX < 128 < u8::MAX; thus overflow is not 768 // possible. We add 32 since the SHA-256 hash of `clientDataJSON` will be added to 769 // the raw attestation object by `AuthenticatorAttestation::new`. 770 let mut att_obj = vec![0; len + Sha256::output_size()]; 771 att_obj.truncate(len); 772 base64url_nopad::decode_buffer_exact(v.as_bytes(), &mut att_obj) 773 .map_err(E::custom) 774 .map(|()| AttObj(att_obj)) 775 }) 776 } 777 } 778 deserializer.deserialize_str(AttObjVisitor) 779 } 780 } 781 /// `Visitor` for `AuthenticatorAttestation`. 782 /// 783 /// Unknown fields are ignored and only `clientDataJSON` and `attestationObject` are required iff `RELAXED`. 784 pub(super) struct AuthenticatorAttestationVisitor<const RELAXED: bool>; 785 impl<'d, const R: bool> Visitor<'d> for AuthenticatorAttestationVisitor<R> { 786 type Value = AuthAttest; 787 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 788 formatter.write_str("AuthenticatorAttestation") 789 } 790 #[expect(clippy::too_many_lines, reason = "find it easier to reason about")] 791 #[expect( 792 clippy::arithmetic_side_effects, 793 clippy::indexing_slicing, 794 reason = "comments justify their correctness" 795 )] 796 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 797 where 798 A: MapAccess<'d>, 799 { 800 use spki::SubjectPublicKeyInfo as _; 801 let mut client_data = None; 802 let mut attest = None; 803 let mut auth = None; 804 let mut pub_key = None; 805 let mut key_alg = None; 806 let mut trans = None; 807 while let Some(key) = map.next_key::<AttestField<R>>()? { 808 match key { 809 AttestField::ClientDataJson => { 810 if client_data.is_some() { 811 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 812 } 813 client_data = map 814 .next_value::<Base64DecodedVal>() 815 .map(|c_data| Some(c_data.0))?; 816 } 817 AttestField::AttestationObject => { 818 if attest.is_some() { 819 return Err(Error::duplicate_field(ATTESTATION_OBJECT)); 820 } 821 attest = map.next_value::<AttObj>().map(|att_obj| Some(att_obj.0))?; 822 } 823 AttestField::AuthenticatorData => { 824 if auth.is_some() { 825 return Err(Error::duplicate_field(AUTHENTICATOR_DATA)); 826 } 827 auth = map.next_value::<Option<Base64DecodedVal>>().map(Some)?; 828 } 829 AttestField::Transports => { 830 if trans.is_some() { 831 return Err(Error::duplicate_field(TRANSPORTS)); 832 } 833 trans = map.next_value::<Option<_>>().map(Some)?; 834 } 835 AttestField::PublicKey => { 836 if pub_key.is_some() { 837 return Err(Error::duplicate_field(PUBLIC_KEY)); 838 } 839 pub_key = map.next_value::<Option<Base64DecodedVal>>().map(Some)?; 840 } 841 AttestField::PublicKeyAlgorithm => { 842 if key_alg.is_some() { 843 return Err(Error::duplicate_field(PUBLIC_KEY_ALGORITHM)); 844 } 845 key_alg = map 846 .next_value::<Option<CoseAlgorithmIdentifier>>() 847 .map(Some)?; 848 } 849 AttestField::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 850 } 851 } 852 // Note the order of this matters from a performance perspective. In particular `auth` must be evaluated 853 // before `pub_key` which must be evaluated before `key_alg` as this allows us to parse the attestation 854 // object at most once and allow us to prioritize parsing `authenticatorData` over the attestation object. 855 client_data.ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)).and_then(|client_data_json| attest.ok_or_else(|| Error::missing_field(ATTESTATION_OBJECT)).and_then(|attestation_object| { 856 trans.ok_or(false).and_then(|opt_trans| opt_trans.ok_or(true)).or_else( 857 |flag| { 858 if R { 859 Ok(AuthTransports::new()) 860 } else if flag { 861 Err(Error::invalid_type(Unexpected::Other("null"), &format!("{TRANSPORTS} to be a sequence of AuthenticatorTransports").as_str())) 862 } else { 863 Err(Error::missing_field(TRANSPORTS)) 864 } 865 }, 866 ).and_then(|transports| { 867 auth.ok_or(false).and_then(|opt_auth| opt_auth.ok_or(true)).as_ref().map_or_else( 868 |flag| { 869 if R { 870 Ok(None) 871 } else if *flag { 872 Err(Error::invalid_type(Unexpected::Other("null"), &format!("{AUTHENTICATOR_DATA} to be a base64url-encoded AuthenticatorData").as_str())) 873 } else { 874 Err(Error::missing_field(AUTHENTICATOR_DATA)) 875 } 876 }, 877 |a_data| { 878 if a_data.0.len() > 37 { 879 // The last portion of attestation object is always authenticator data. 880 attestation_object.len().checked_sub(a_data.0.len()).ok_or_else(|| Error::invalid_value(Unexpected::Bytes(a_data.0.as_slice()), &format!("authenticator data to match the authenticator data portion of attestation object: {attestation_object:?}").as_str())).and_then(|idx| { 881 // Indexing is fine; otherwise the above check would have returned `None`. 882 if *a_data.0 == attestation_object[idx..] { 883 // We know `a_data.len() > 37`; thus indexing is fine. 884 // We start at 37 since that is the beginning of `attestedCredentialData`. 885 // Recall the first 32 bytes are `rpIdHash`, then a 1 byte `flags`, then a 886 // 4-byte big-endian integer `signCount`. 887 // The starting index of `credentialId` is 18 within `attestedCredentialData`. 888 // Recall the first 16 bytes are `aaguid`, then a 2-byte big-endian integer 889 // `credentialIdLength`. Consequently the starting index within 890 // `attestation_object` is `idx + 37 + 18` = `idx + 55`. Overflow cannot occur 891 // since we successfully parsed `AttestedCredentialData`. 892 AttestedCredentialData::from_cbor(&a_data.0[37..]).map_err(Error::custom).map(|success| Some((success.value, idx + 55))) 893 } else { 894 Err(Error::invalid_value(Unexpected::Bytes(a_data.0.as_slice()), &format!("authenticator data to match the authenticator data portion of attestation object: {:?}", &attestation_object[idx..]).as_str())) 895 } 896 }) 897 } else { 898 Err(Error::invalid_value(Unexpected::Bytes(a_data.0.as_slice()), &"authenticator data to be long enough to contain attested credential data")) 899 } 900 } 901 ).and_then(|attested_info| { 902 pub_key.ok_or(false).and_then(|opt_key| opt_key.ok_or(true)).map_or_else( 903 |flag| { 904 if R { 905 attested_info.as_ref().map_or(Ok(None), |&(ref attested_data, cred_id_start)| Ok(Some((match attested_data.credential_public_key { 906 UncompressedPubKey::Ed25519(_) => CoseAlgorithmIdentifier::Eddsa, 907 UncompressedPubKey::P256(_) => CoseAlgorithmIdentifier::Es256, 908 UncompressedPubKey::P384(_) => CoseAlgorithmIdentifier::Es384, 909 UncompressedPubKey::Rsa(_) => CoseAlgorithmIdentifier::Rs256, 910 // Overflow won't occur since this is correct as 911 // `AttestedCredentialData::from_cbor` would have erred if not. 912 }, cred_id_start, cred_id_start + attested_data.credential_id.0.len())))) 913 } else { 914 // `publicKey` is only allowed to not exist when `CoseAlgorithmIdentifier::Eddsa`, 915 // `CoseAlgorithmIdentifier::Es256`, or `CoseAlgorithmIdentifier::Rs256` is not 916 // used. 917 attested_info.as_ref().map_or_else( 918 || AttestationObject::parse_data(attestation_object.as_slice()).map_err(Error::custom).and_then(|(att_obj, auth_idx)| { 919 match att_obj.auth_data.attested_credential_data.credential_public_key { 920 UncompressedPubKey::P384(_) => { 921 // This won't overflow since `AttestationObject::parse_data` succeeded and `auth_idx` 922 // is the start of the raw authenticator data which itself contains the raw Credential ID. 923 Ok(Some((CoseAlgorithmIdentifier::Es384, auth_idx, auth_idx + att_obj.auth_data.attested_credential_data.credential_id.0.len()))) 924 } 925 UncompressedPubKey::Ed25519(_) | UncompressedPubKey::P256(_) | UncompressedPubKey::Rsa(_) => Err(Error::missing_field(PUBLIC_KEY)), 926 } 927 }), 928 |&(ref attested_data, cred_id_start)| { 929 match attested_data.credential_public_key { 930 UncompressedPubKey::P384(_) => { 931 // Overflow won't occur since this is correct. This is correct since we successfully parsed 932 // `AttestedCredentialData` and calculated `cred_id_start` from it. 933 Ok(Some((CoseAlgorithmIdentifier::Es384, cred_id_start, cred_id_start + attested_data.credential_id.0.len()))) 934 } 935 UncompressedPubKey::Ed25519(_) | UncompressedPubKey::P256(_) | UncompressedPubKey::Rsa(_) => if flag { Err(Error::invalid_type(Unexpected::Other("null"), &format!("{PUBLIC_KEY} to be a base64url-encoded DER-encoded SubjectPublicKeyInfo").as_str())) } else { Err(Error::missing_field(PUBLIC_KEY)) }, 936 } 937 } 938 ) 939 } 940 }, 941 |der| { 942 UncompressedPubKey::from_der(der.0.as_slice()).map_err(Error::custom).and_then(|key| { 943 attested_info.as_ref().map_or_else( 944 || AttestationObject::parse_data(attestation_object.as_slice()).map_err(Error::custom).and_then(|(att_obj, auth_idx)| { 945 if key == att_obj.auth_data.attested_credential_data.credential_public_key { 946 let alg = match att_obj.auth_data.attested_credential_data.credential_public_key { 947 UncompressedPubKey::Ed25519(_) => CoseAlgorithmIdentifier::Eddsa, 948 UncompressedPubKey::P256(_) => CoseAlgorithmIdentifier::Es256, 949 UncompressedPubKey::P384(_) => CoseAlgorithmIdentifier::Es384, 950 UncompressedPubKey::Rsa(_) => CoseAlgorithmIdentifier::Rs256, 951 }; 952 // This won't overflow since `AttestationObject::parse_data` succeeded and `auth_idx` 953 // is the start of the raw authenticator data which itself contains the raw Credential ID. 954 Ok(Some((alg, auth_idx, auth_idx+ att_obj.auth_data.attested_credential_data.credential_id.0.len()))) 955 } else { 956 Err(Error::invalid_value(Unexpected::Bytes(der.0.as_slice()), &format!("DER-encoded public key to match the public key within the attestation object: {:?}", att_obj.auth_data.attested_credential_data.credential_public_key).as_str())) 957 } 958 }), 959 |&(ref attested_data, cred_id_start)| { 960 if key == attested_data.credential_public_key { 961 let alg = match attested_data.credential_public_key { 962 UncompressedPubKey::Ed25519(_) => CoseAlgorithmIdentifier::Eddsa, 963 UncompressedPubKey::P256(_) => CoseAlgorithmIdentifier::Es256, 964 UncompressedPubKey::P384(_) => CoseAlgorithmIdentifier::Es384, 965 UncompressedPubKey::Rsa(_) => CoseAlgorithmIdentifier::Rs256, 966 }; 967 // Overflow won't occur since this is correct. This is correct since we successfully parsed 968 // `AttestedCredentialData` and calculated `cred_id_start` from it. 969 Ok(Some((alg, cred_id_start, cred_id_start + attested_data.credential_id.0.len()))) 970 } else { 971 Err(Error::invalid_value(Unexpected::Bytes(der.0.as_slice()), &format!("DER-encoded public key to match the public key within the attestation object: {:?}", attested_data.credential_public_key).as_str())) 972 } 973 } 974 ) 975 }) 976 } 977 ).and_then(|cred_key_alg_cred_info| { 978 key_alg.ok_or(false).and_then(|opt_alg| opt_alg.ok_or(true)).map_or_else( 979 |flag| { 980 if R { 981 Ok(cred_key_alg_cred_info.map(|info| (info.1, info.2))) 982 } else if flag { 983 Err(Error::invalid_type(Unexpected::Other("null"), &format!("{PUBLIC_KEY_ALGORITHM} to be a base64url-encoded DER-encoded SubjectPublicKeyInfo").as_str())) 984 } else { 985 Err(Error::missing_field(PUBLIC_KEY_ALGORITHM)) 986 } 987 }, 988 |alg| { 989 cred_key_alg_cred_info.map_or_else( 990 || AttestationObject::parse_data(attestation_object.as_slice()).map_err(Error::custom).and_then(|(att_obj, auth_idx)| { 991 let att_obj_alg = match att_obj.auth_data.attested_credential_data.credential_public_key { 992 UncompressedPubKey::Ed25519(_) => CoseAlgorithmIdentifier::Eddsa, 993 UncompressedPubKey::P256(_) => CoseAlgorithmIdentifier::Es256, 994 UncompressedPubKey::P384(_) => CoseAlgorithmIdentifier::Es384, 995 UncompressedPubKey::Rsa(_) => CoseAlgorithmIdentifier::Rs256, 996 }; 997 if alg == att_obj_alg { 998 // This won't overflow since `AttestationObject::parse_data` succeeded and `auth_idx` 999 // is the start of the raw authenticator data which itself contains the raw Credential ID. 1000 Ok(Some((auth_idx, auth_idx + att_obj.auth_data.attested_credential_data.credential_id.0.len()))) 1001 } else { 1002 Err(Error::invalid_value(Unexpected::Other(format!("{alg:?}").as_str()), &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {att_obj_alg:?}").as_str())) 1003 } 1004 }), 1005 |(a, start, last)| if alg == a { 1006 Ok(Some((start, last))) 1007 } else { 1008 Err(Error::invalid_value(Unexpected::Other(format!("{alg:?}").as_str()), &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {a:?}").as_str())) 1009 }, 1010 ) 1011 } 1012 ).map(|cred_info| AuthAttest{ attest: AuthenticatorAttestation::new(client_data_json, attestation_object, transports), cred_info, }) 1013 }) 1014 }) 1015 }) 1016 })) 1017 } 1018 } 1019 /// `"clientDataJSON"` 1020 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 1021 /// `"attestationObject"` 1022 const ATTESTATION_OBJECT: &str = "attestationObject"; 1023 /// `"authenticatorData"` 1024 const AUTHENTICATOR_DATA: &str = "authenticatorData"; 1025 /// `"transports"` 1026 const TRANSPORTS: &str = "transports"; 1027 /// `"publicKey"` 1028 const PUBLIC_KEY: &str = "publicKey"; 1029 /// `"publicKeyAlgorithm"` 1030 const PUBLIC_KEY_ALGORITHM: &str = "publicKeyAlgorithm"; 1031 /// Fields in `AuthenticatorAttestationResponseJSON`. 1032 pub(super) const AUTH_ATTEST_FIELDS: &[&str; 6] = &[ 1033 CLIENT_DATA_JSON, 1034 ATTESTATION_OBJECT, 1035 AUTHENTICATOR_DATA, 1036 TRANSPORTS, 1037 PUBLIC_KEY, 1038 PUBLIC_KEY_ALGORITHM, 1039 ]; 1040 impl<'de> Deserialize<'de> for AuthAttest { 1041 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1042 where 1043 D: Deserializer<'de>, 1044 { 1045 deserializer.deserialize_struct( 1046 "AuthenticatorAttestation", 1047 AUTH_ATTEST_FIELDS, 1048 AuthenticatorAttestationVisitor::<false>, 1049 ) 1050 } 1051 } 1052 impl<'de> Deserialize<'de> for AuthenticatorAttestation { 1053 /// Deserializes a `struct` based on 1054 /// [`AuthenticatorAttestationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorattestationresponsejson). 1055 /// 1056 /// Note unknown keys and duplicate keys are forbidden; 1057 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-clientdatajson), 1058 /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-authenticatordata), 1059 /// [`publicKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-publickey) 1060 /// and 1061 /// [`attestationObject`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-attestationobject) 1062 /// are base64url-decoded; 1063 /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-transports) 1064 /// is deserialized via [`AuthTransports::deserialize`]; the decoded `publicKey` is parsed according to the 1065 /// applicable DER-encoded ASN.1 `SubjectPublicKeyInfo` schema; 1066 /// [`publicKeyAlgorithm`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-publickeyalgorithm) 1067 /// is deserialized according to 1068 /// [`CoseAlgorithmIdentifier`](https://www.w3.org/TR/webauthn-3/#typedefdef-cosealgorithmidentifier); all `required` 1069 /// fields in the `AuthenticatorAttestationResponseJSON` Web IDL `dictionary` exist (and must not be `null`); `publicKey` 1070 /// exists when Ed25519, P-256 with SHA-256, or RSASSA-PKCS1-v1_5 with SHA-256 is used (and must not be `null`) 1071 /// [per WebAuthn](https://www.w3.org/TR/webauthn-3/#sctn-public-key-easy); the `publicKeyAlgorithm` aligns 1072 /// with 1073 /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey) 1074 /// within 1075 /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata) within the 1076 /// decoded `authenticatorData`; the decoded `publicKey` is the same as `credentialPublicKey` within 1077 /// `attestedCredentialData` within the decoded `authenticatorData`; and the decoded `authenticatorData` is the 1078 /// same as [`authData`](https://www.w3.org/TR/webauthn-3/#attestation-object) within the decoded 1079 /// `attestationObject`. 1080 #[inline] 1081 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1082 where 1083 D: Deserializer<'de>, 1084 { 1085 AuthAttest::deserialize(deserializer).map(|val| val.attest) 1086 } 1087 } 1088 /// `Visitor` for `CredentialPropertiesOutput`. 1089 /// 1090 /// Unknown fields are ignored iff `RELAXED`. 1091 pub(super) struct CredentialPropertiesOutputVisitor<const RELAXED: bool>; 1092 impl<'d, const R: bool> Visitor<'d> for CredentialPropertiesOutputVisitor<R> { 1093 type Value = CredentialPropertiesOutput; 1094 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1095 formatter.write_str("CredentialPropertiesOutput") 1096 } 1097 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1098 where 1099 A: MapAccess<'d>, 1100 { 1101 /// Allowed fields. 1102 enum Field<const IGNORE_UNKNOWN: bool> { 1103 /// `rk` field. 1104 Rk, 1105 /// Unknown field. 1106 Other, 1107 } 1108 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 1109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1110 where 1111 D: Deserializer<'e>, 1112 { 1113 /// `Visitor` for `Field`. 1114 /// 1115 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 1116 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 1117 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 1118 type Value = Field<IG>; 1119 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1120 write!(formatter, "'{RK}'") 1121 } 1122 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1123 where 1124 E: Error, 1125 { 1126 match v { 1127 RK => Ok(Field::Rk), 1128 _ => { 1129 if IG { 1130 Ok(Field::Other) 1131 } else { 1132 Err(E::unknown_field(v, PROPS_FIELDS)) 1133 } 1134 } 1135 } 1136 } 1137 } 1138 deserializer.deserialize_identifier(FieldVisitor) 1139 } 1140 } 1141 let mut rk = None; 1142 while let Some(key) = map.next_key::<Field<R>>()? { 1143 match key { 1144 Field::Rk => { 1145 if rk.is_some() { 1146 return Err(Error::duplicate_field(RK)); 1147 } 1148 rk = map.next_value().map(Some)?; 1149 } 1150 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 1151 } 1152 } 1153 Ok(CredentialPropertiesOutput { rk: rk.flatten() }) 1154 } 1155 } 1156 /// `"rk"` 1157 const RK: &str = "rk"; 1158 /// `CredentialPropertiesOutput` fields. 1159 pub(super) const PROPS_FIELDS: &[&str; 1] = &[RK]; 1160 impl<'de> Deserialize<'de> for CredentialPropertiesOutput { 1161 /// Deserializes a `struct` based on 1162 /// [`CredentialPropertiesOutput`](https://www.w3.org/TR/webauthn-3/#dictdef-credentialpropertiesoutput). 1163 /// 1164 /// Note unknown and duplicate keys are forbidden. 1165 #[inline] 1166 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1167 where 1168 D: Deserializer<'de>, 1169 { 1170 deserializer.deserialize_struct( 1171 "CredentialPropertiesOutput", 1172 PROPS_FIELDS, 1173 CredentialPropertiesOutputVisitor::<false>, 1174 ) 1175 } 1176 } 1177 impl<'de> Deserialize<'de> for AuthenticationExtensionsPrfOutputs { 1178 /// Deserializes a `struct` based on 1179 /// [`AuthenticationExtensionsPRFOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputsjson). 1180 /// 1181 /// Note unknown and duplicate keys are forbidden; 1182 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 1183 /// must exist (and not be `null`); and 1184 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 1185 /// be `null`, or be an 1186 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 1187 /// with no unknown or duplicate keys, 1188 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) must exist but be 1189 /// `null`, and 1190 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 1191 /// must be `null` if so. 1192 #[inline] 1193 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 1194 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1195 where 1196 D: Deserializer<'de>, 1197 { 1198 AuthenticationExtensionsPrfOutputsHelper::<false, true, AuthenticationExtensionsPrfValues>::deserialize(deserializer).map(|val| Self { 1199 enabled: val.0.unwrap_or_else(|| { 1200 unreachable!( 1201 "there is a bug in AuthenticationExtensionsPrfOutputsHelper::deserialize" 1202 ) 1203 }), 1204 }) 1205 } 1206 } 1207 /// `Visitor` for `ClientExtensionsOutputs`. 1208 /// 1209 /// Unknown fields are ignored iff `RELAXED`. 1210 pub(super) struct ClientExtensionsOutputsVisitor<const RELAXED: bool, PROPS, PRF>( 1211 pub PhantomData<fn() -> (PROPS, PRF)>, 1212 ); 1213 impl<'d, const R: bool, C, P> Visitor<'d> for ClientExtensionsOutputsVisitor<R, C, P> 1214 where 1215 C: for<'a> Deserialize<'a> + Into<CredentialPropertiesOutput>, 1216 P: for<'a> Deserialize<'a> + Into<AuthenticationExtensionsPrfOutputs>, 1217 { 1218 type Value = ClientExtensionsOutputs; 1219 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1220 formatter.write_str("ClientExtensionsOutputs") 1221 } 1222 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1223 where 1224 A: MapAccess<'d>, 1225 { 1226 /// Allowed fields. 1227 enum Field<const IGNORE_UNKNOWN: bool> { 1228 /// `credProps` field. 1229 CredProps, 1230 /// `prf` field. 1231 Prf, 1232 /// Unknown field. 1233 Other, 1234 } 1235 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 1236 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1237 where 1238 D: Deserializer<'e>, 1239 { 1240 /// `Visitor` for `Field`. 1241 /// 1242 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 1243 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 1244 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 1245 type Value = Field<IG>; 1246 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1247 write!(formatter, "'{CRED_PROPS}' or '{PRF}'") 1248 } 1249 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1250 where 1251 E: Error, 1252 { 1253 match v { 1254 CRED_PROPS => Ok(Field::CredProps), 1255 PRF => Ok(Field::Prf), 1256 _ => { 1257 if IG { 1258 Ok(Field::Other) 1259 } else { 1260 Err(E::unknown_field(v, EXT_FIELDS)) 1261 } 1262 } 1263 } 1264 } 1265 } 1266 deserializer.deserialize_identifier(FieldVisitor) 1267 } 1268 } 1269 let mut cred_props = None; 1270 let mut prf = None; 1271 while let Some(key) = map.next_key::<Field<R>>()? { 1272 match key { 1273 Field::CredProps => { 1274 if cred_props.is_some() { 1275 return Err(Error::duplicate_field(CRED_PROPS)); 1276 } 1277 cred_props = map.next_value::<Option<C>>().map(Some)?; 1278 } 1279 Field::Prf => { 1280 if prf.is_some() { 1281 return Err(Error::duplicate_field(PRF)); 1282 } 1283 prf = map.next_value::<Option<P>>().map(Some)?; 1284 } 1285 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 1286 } 1287 } 1288 Ok(ClientExtensionsOutputs { 1289 cred_props: cred_props.flatten().map(Into::into), 1290 prf: prf.flatten().map(Into::into), 1291 }) 1292 } 1293 } 1294 impl ClientExtensions for ClientExtensionsOutputs { 1295 fn empty() -> Self { 1296 Self { 1297 prf: None, 1298 cred_props: None, 1299 } 1300 } 1301 } 1302 /// `"credProps"` 1303 const CRED_PROPS: &str = "credProps"; 1304 /// `"prf"` 1305 const PRF: &str = "prf"; 1306 /// `AuthenticationExtensionsClientOutputsJSON` fields. 1307 pub(super) const EXT_FIELDS: &[&str; 2] = &[CRED_PROPS, PRF]; 1308 impl<'de> Deserialize<'de> for ClientExtensionsOutputs { 1309 /// Deserializes a `struct` based on 1310 /// [`AuthenticationExtensionsClientOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputsjson). 1311 /// 1312 /// Note that unknown and duplicate keys are forbidden; 1313 /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops) is 1314 /// `null` or deserialized via [`CredentialPropertiesOutput::deserialize`]; and 1315 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` 1316 /// or deserialized via [`AuthenticationExtensionsPrfOutputs::deserialize`]. 1317 #[inline] 1318 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1319 where 1320 D: Deserializer<'de>, 1321 { 1322 deserializer.deserialize_struct( 1323 "ClientExtensionsOutputs", 1324 EXT_FIELDS, 1325 ClientExtensionsOutputsVisitor::< 1326 false, 1327 CredentialPropertiesOutput, 1328 AuthenticationExtensionsPrfOutputs, 1329 >(PhantomData), 1330 ) 1331 } 1332 } 1333 impl<'de> Deserialize<'de> for Registration { 1334 /// Deserializes a `struct` based on 1335 /// [`RegistrationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson). 1336 /// 1337 /// Note that unknown and duplicate keys are forbidden; 1338 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-id) and 1339 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-rawid) are deserialized 1340 /// via [`CredentialId::deserialize`]; 1341 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-response) is deserialized 1342 /// via [`AuthenticatorAttestation::deserialize`]; 1343 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-authenticatorattachment) 1344 /// is `null` or deserialized via [`AuthenticatorAttachment::deserialize`]; 1345 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults) 1346 /// is deserialized via [`ClientExtensionsOutputs::deserialize`]; all `required` fields in the 1347 /// `RegistrationResponseJSON` Web IDL `dictionary` exist (and are not `null`); 1348 /// [`type`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-type) is `"public-key"`; 1349 /// and the decoded `id`, decoded `rawId`, and 1350 /// [`credentialId`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialid) within 1351 /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata) within 1352 /// [`authData`](https://www.w3.org/TR/webauthn-3/#attestation-object) within the decoded 1353 /// [`attestationObject`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-attestationobject) 1354 /// are all the same. 1355 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 1356 #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")] 1357 #[inline] 1358 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1359 where 1360 D: Deserializer<'de>, 1361 { 1362 PublicKeyCredential::<false, true, AuthAttest, ClientExtensionsOutputs>::deserialize(deserializer).and_then(|cred| { 1363 let id = cred.id.unwrap_or_else(|| unreachable!("there is a bug in PublicKeyCredential::deserialize")); 1364 cred.response.cred_info.map_or_else( 1365 || AttestationObject::try_from(cred.response.attest.attestation_object()).map_err(Error::custom).and_then(|att_obj| { 1366 if id == att_obj.auth_data.attested_credential_data.credential_id { 1367 Ok(()) 1368 } else { 1369 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())) 1370 } 1371 }), 1372 // `start` and `last` were calculated based on `cred.response.attest.attestation_object()` 1373 // and represent the starting and ending index of the `CredentialId`; therefore this is correct 1374 // let alone won't `panic`. 1375 |(start, last)| if id.0 == cred.response.attest.attestation_object()[start..last] { 1376 Ok(()) 1377 } else { 1378 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.attest.attestation_object()[start..last]).as_str())) 1379 }, 1380 ).map(|()| Self { response: cred.response.attest, authenticator_attachment: cred.authenticator_attachment, client_extension_results: cred.client_extension_results, }) 1381 }) 1382 } 1383 } 1384 #[cfg(test)] 1385 mod tests { 1386 use super::{ 1387 super::{ 1388 ALG, AuthenticatorAttachment, EC2, EDDSA, ES256, ES384, Ed25519PubKey, KTY, OKP, RSA, 1389 Registration, RsaPubKey, UncompressedP256PubKey, UncompressedP384PubKey, cbor, 1390 }, 1391 CoseAlgorithmIdentifier, 1392 spki::SubjectPublicKeyInfo as _, 1393 }; 1394 use ed25519_dalek::{VerifyingKey, pkcs8::EncodePublicKey as _}; 1395 use p256::{ 1396 EncodedPoint as P256Pt, PublicKey as P256PubKey, SecretKey as P256Key, 1397 elliptic_curve::sec1::{FromEncodedPoint as _, ToEncodedPoint as _}, 1398 }; 1399 use p384::{EncodedPoint as P384Pt, PublicKey as P384PubKey, SecretKey as P384Key}; 1400 use rsa::{ 1401 BigUint, RsaPrivateKey, 1402 sha2::{Digest as _, Sha256}, 1403 traits::PublicKeyParts as _, 1404 }; 1405 use serde::de::{Error as _, Unexpected}; 1406 use serde_json::Error; 1407 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1408 #[test] 1409 fn ed25519_spki() { 1410 assert!( 1411 Ed25519PubKey::from_der( 1412 VerifyingKey::from_bytes(&[1; 32]) 1413 .unwrap() 1414 .to_public_key_der() 1415 .unwrap() 1416 .as_bytes() 1417 ) 1418 .is_ok_and(|k| k.0 == [1; 32]) 1419 ); 1420 } 1421 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1422 #[test] 1423 fn p256_spki() { 1424 let key = P256Key::from_bytes( 1425 &[ 1426 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 1427 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 1428 ] 1429 .into(), 1430 ) 1431 .unwrap() 1432 .public_key(); 1433 let enc_key = key.to_encoded_point(false); 1434 assert!( 1435 UncompressedP256PubKey::from_der(key.to_public_key_der().unwrap().as_bytes()) 1436 .is_ok_and(|k| *k.0 == **enc_key.x().unwrap() && *k.1 == **enc_key.y().unwrap()) 1437 ); 1438 } 1439 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1440 #[test] 1441 fn p384_spki() { 1442 let key = P384Key::from_bytes( 1443 &[ 1444 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 1445 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 1446 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 1447 ] 1448 .into(), 1449 ) 1450 .unwrap() 1451 .public_key(); 1452 let enc_key = key.to_encoded_point(false); 1453 assert!( 1454 UncompressedP384PubKey::from_der(key.to_public_key_der().unwrap().as_bytes()) 1455 .is_ok_and(|k| *k.0 == **enc_key.x().unwrap() && *k.1 == **enc_key.y().unwrap()) 1456 ); 1457 } 1458 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1459 #[test] 1460 fn rsa_spki() { 1461 let n = [ 1462 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 1463 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 1464 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 1465 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 1466 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 1467 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 1468 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 1469 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 1470 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 1471 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 1472 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 1473 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 1474 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 1475 153, 79, 0, 133, 78, 7, 218, 165, 241, 1476 ]; 1477 let e = 0x0001_0001u32; 1478 let d = [ 1479 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 1480 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 1481 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 1482 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 1483 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 1484 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 1485 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 1486 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 1487 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 1488 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 1489 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 1490 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 1491 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 1492 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 1493 ]; 1494 let p = BigUint::from_bytes_le( 1495 [ 1496 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60, 1497 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229, 1498 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161, 1499 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16, 1500 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79, 1501 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191, 1502 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188, 1503 24, 247, 1504 ] 1505 .as_slice(), 1506 ); 1507 let p_2 = BigUint::from_bytes_le( 1508 [ 1509 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78, 1510 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177, 1511 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29, 1512 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11, 1513 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252, 1514 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214, 1515 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90, 1516 250, 1517 ] 1518 .as_slice(), 1519 ); 1520 let key = RsaPrivateKey::from_components( 1521 BigUint::from_bytes_le(n.as_slice()), 1522 e.into(), 1523 BigUint::from_bytes_le(d.as_slice()), 1524 vec![p, p_2], 1525 ) 1526 .unwrap() 1527 .to_public_key(); 1528 assert!( 1529 RsaPubKey::from_der(key.to_public_key_der().unwrap().as_bytes()) 1530 .is_ok_and(|k| k.0 == key.n().to_bytes_be() && BigUint::from(k.1) == *key.e()) 1531 ); 1532 } 1533 #[expect(clippy::unwrap_used, reason = "OK in tests")] 1534 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 1535 #[expect( 1536 clippy::cognitive_complexity, 1537 clippy::too_many_lines, 1538 reason = "a lot to test" 1539 )] 1540 #[test] 1541 fn eddsa_registration_deserialize_data_mismatch() { 1542 let c_data_json = serde_json::json!({}).to_string(); 1543 let att_obj: [u8; 143] = [ 1544 cbor::MAP_3, 1545 cbor::TEXT_3, 1546 b'f', 1547 b'm', 1548 b't', 1549 cbor::TEXT_4, 1550 b'n', 1551 b'o', 1552 b'n', 1553 b'e', 1554 cbor::TEXT_7, 1555 b'a', 1556 b't', 1557 b't', 1558 b'S', 1559 b't', 1560 b'm', 1561 b't', 1562 cbor::MAP_0, 1563 cbor::TEXT_8, 1564 b'a', 1565 b'u', 1566 b't', 1567 b'h', 1568 b'D', 1569 b'a', 1570 b't', 1571 b'a', 1572 cbor::BYTES_INFO_24, 1573 115, 1574 // `rpIdHash`. 1575 0, 1576 0, 1577 0, 1578 0, 1579 0, 1580 0, 1581 0, 1582 0, 1583 0, 1584 0, 1585 0, 1586 0, 1587 0, 1588 0, 1589 0, 1590 0, 1591 0, 1592 0, 1593 0, 1594 0, 1595 0, 1596 0, 1597 0, 1598 0, 1599 0, 1600 0, 1601 0, 1602 0, 1603 0, 1604 0, 1605 0, 1606 0, 1607 // `flags`. 1608 0b0100_0101, 1609 // `signCount`. 1610 0, 1611 0, 1612 0, 1613 0, 1614 // `aaguid`. 1615 0, 1616 0, 1617 0, 1618 0, 1619 0, 1620 0, 1621 0, 1622 0, 1623 0, 1624 0, 1625 0, 1626 0, 1627 0, 1628 0, 1629 0, 1630 0, 1631 // `credentialIdLength`. 1632 0, 1633 16, 1634 // `credentialId`. 1635 0, 1636 0, 1637 0, 1638 0, 1639 0, 1640 0, 1641 0, 1642 0, 1643 0, 1644 0, 1645 0, 1646 0, 1647 0, 1648 0, 1649 0, 1650 0, 1651 // Ed25519 COSE key. 1652 cbor::MAP_4, 1653 KTY, 1654 OKP, 1655 ALG, 1656 EDDSA, 1657 // `crv`. 1658 cbor::NEG_ONE, 1659 // `Ed25519`. 1660 cbor::SIX, 1661 // `x`. 1662 cbor::NEG_TWO, 1663 cbor::BYTES_INFO_24, 1664 32, 1665 // Compressed y-coordinate. 1666 1, 1667 1, 1668 1, 1669 1, 1670 1, 1671 1, 1672 1, 1673 1, 1674 1, 1675 1, 1676 1, 1677 1, 1678 1, 1679 1, 1680 1, 1681 1, 1682 1, 1683 1, 1684 1, 1685 1, 1686 1, 1687 1, 1688 1, 1689 1, 1690 1, 1691 1, 1692 1, 1693 1, 1694 1, 1695 1, 1696 1, 1697 1, 1698 ]; 1699 let pub_key = VerifyingKey::from_bytes(&[1; 32]) 1700 .unwrap() 1701 .to_public_key_der() 1702 .unwrap(); 1703 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 1704 let att_obj_len = att_obj.len(); 1705 let auth_data_start = att_obj_len - 113; 1706 let b64_adata = base64url_nopad::encode(&att_obj[auth_data_start..]); 1707 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 1708 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 1709 // Base case is valid. 1710 assert!( 1711 serde_json::from_str::<Registration>( 1712 serde_json::json!({ 1713 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1714 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1715 "response": { 1716 "clientDataJSON": b64_cdata_json, 1717 "authenticatorData": b64_adata, 1718 "transports": ["ble", "usb", "hybrid", "internal", "nfc", "smart-card"], 1719 "publicKey": b64_key, 1720 "publicKeyAlgorithm": -8i8, 1721 "attestationObject": b64_aobj, 1722 }, 1723 "authenticatorAttachment": "cross-platform", 1724 "clientExtensionResults": {}, 1725 "type": "public-key" 1726 }) 1727 .to_string() 1728 .as_str() 1729 ) 1730 .is_ok_and( 1731 |reg| reg.response.client_data_json == c_data_json.as_bytes() 1732 && reg.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 1733 && reg.response.attestation_object_and_c_data_hash[att_obj_len..] 1734 == *Sha256::digest(c_data_json.as_bytes()) 1735 && reg.response.transports.count() == 6 1736 && matches!( 1737 reg.authenticator_attachment, 1738 AuthenticatorAttachment::CrossPlatform 1739 ) 1740 && reg.client_extension_results.cred_props.is_none() 1741 && reg.client_extension_results.prf.is_none() 1742 ) 1743 ); 1744 // `id` and `rawId` mismatch. 1745 let mut err = Error::invalid_value( 1746 Unexpected::Bytes( 1747 base64url_nopad::decode(b"ABABABABABABABABABABAA") 1748 .unwrap() 1749 .as_slice(), 1750 ), 1751 &format!("id and rawId to match: CredentialId({:?})", [0u8; 16]).as_str(), 1752 ) 1753 .to_string() 1754 .into_bytes(); 1755 assert_eq!( 1756 serde_json::from_str::<Registration>( 1757 serde_json::json!({ 1758 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1759 "rawId": "ABABABABABABABABABABAA", 1760 "response": { 1761 "clientDataJSON": b64_cdata_json, 1762 "authenticatorData": b64_adata, 1763 "transports": [], 1764 "publicKey": b64_key, 1765 "publicKeyAlgorithm": -8i8, 1766 "attestationObject": b64_aobj, 1767 }, 1768 "clientExtensionResults": {}, 1769 "type": "public-key" 1770 }) 1771 .to_string() 1772 .as_str() 1773 ) 1774 .unwrap_err() 1775 .to_string() 1776 .into_bytes() 1777 .get(..err.len()), 1778 Some(err.as_slice()) 1779 ); 1780 // missing `id`. 1781 err = Error::missing_field("id").to_string().into_bytes(); 1782 assert_eq!( 1783 serde_json::from_str::<Registration>( 1784 serde_json::json!({ 1785 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1786 "response": { 1787 "clientDataJSON": b64_cdata_json, 1788 "authenticatorData": b64_adata, 1789 "transports": [], 1790 "publicKey": b64_key, 1791 "publicKeyAlgorithm": -8i8, 1792 "attestationObject": b64_aobj, 1793 }, 1794 "clientExtensionResults": {}, 1795 "type": "public-key" 1796 }) 1797 .to_string() 1798 .as_str() 1799 ) 1800 .unwrap_err() 1801 .to_string() 1802 .into_bytes() 1803 .get(..err.len()), 1804 Some(err.as_slice()) 1805 ); 1806 // `null` `id`. 1807 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 1808 .to_string() 1809 .into_bytes(); 1810 assert_eq!( 1811 serde_json::from_str::<Registration>( 1812 serde_json::json!({ 1813 "id": null, 1814 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1815 "response": { 1816 "clientDataJSON": b64_cdata_json, 1817 "authenticatorData": b64_adata, 1818 "transports": [], 1819 "publicKey": b64_key, 1820 "publicKeyAlgorithm": -8i8, 1821 "attestationObject": b64_aobj, 1822 }, 1823 "clientExtensionResults": {}, 1824 "type": "public-key" 1825 }) 1826 .to_string() 1827 .as_str() 1828 ) 1829 .unwrap_err() 1830 .to_string() 1831 .into_bytes() 1832 .get(..err.len()), 1833 Some(err.as_slice()) 1834 ); 1835 // missing `rawId`. 1836 err = Error::missing_field("rawId").to_string().into_bytes(); 1837 assert_eq!( 1838 serde_json::from_str::<Registration>( 1839 serde_json::json!({ 1840 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1841 "response": { 1842 "clientDataJSON": b64_cdata_json, 1843 "authenticatorData": b64_adata, 1844 "transports": [], 1845 "publicKey": b64_key, 1846 "publicKeyAlgorithm": -8i8, 1847 "attestationObject": b64_aobj, 1848 }, 1849 "clientExtensionResults": {}, 1850 "type": "public-key" 1851 }) 1852 .to_string() 1853 .as_str() 1854 ) 1855 .unwrap_err() 1856 .to_string() 1857 .into_bytes() 1858 .get(..err.len()), 1859 Some(err.as_slice()) 1860 ); 1861 // `null` `rawId`. 1862 err = Error::invalid_type(Unexpected::Other("null"), &"CredentialId") 1863 .to_string() 1864 .into_bytes(); 1865 assert_eq!( 1866 serde_json::from_str::<Registration>( 1867 serde_json::json!({ 1868 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1869 "rawId": null, 1870 "response": { 1871 "clientDataJSON": b64_cdata_json, 1872 "authenticatorData": b64_adata, 1873 "transports": [], 1874 "publicKey": b64_key, 1875 "publicKeyAlgorithm": -8i8, 1876 "attestationObject": b64_aobj, 1877 }, 1878 "clientExtensionResults": {}, 1879 "type": "public-key" 1880 }) 1881 .to_string() 1882 .as_str() 1883 ) 1884 .unwrap_err() 1885 .to_string() 1886 .into_bytes() 1887 .get(..err.len()), 1888 Some(err.as_slice()) 1889 ); 1890 // `id` and the credential id in authenticator data mismatch. 1891 err = Error::invalid_value( 1892 Unexpected::Bytes( 1893 base64url_nopad 1894 ::decode(b"ABABABABABABABABABABAA") 1895 .unwrap() 1896 .as_slice(), 1897 ), 1898 &format!("id, rawId, and the credential id in the attested credential data to all match: {:?}", [0u8; 16]).as_str(), 1899 ) 1900 .to_string().into_bytes(); 1901 assert_eq!( 1902 serde_json::from_str::<Registration>( 1903 serde_json::json!({ 1904 "id": "ABABABABABABABABABABAA", 1905 "rawId": "ABABABABABABABABABABAA", 1906 "response": { 1907 "clientDataJSON": b64_cdata_json, 1908 "authenticatorData": b64_adata, 1909 "transports": [], 1910 "publicKey": b64_key, 1911 "publicKeyAlgorithm": -8i8, 1912 "attestationObject": b64_aobj, 1913 }, 1914 "clientExtensionResults": {}, 1915 "type": "public-key" 1916 }) 1917 .to_string() 1918 .as_str() 1919 ) 1920 .unwrap_err() 1921 .to_string() 1922 .into_bytes() 1923 .get(..err.len()), 1924 Some(err.as_slice()) 1925 ); 1926 // `authenticatorData` mismatches `authData` in attestation object. 1927 let mut bad_auth = [0; 113]; 1928 bad_auth.copy_from_slice(&att_obj[auth_data_start..]); 1929 bad_auth[113 - 32..].copy_from_slice([0; 32].as_slice()); 1930 err = Error::invalid_value( 1931 Unexpected::Bytes(bad_auth.as_slice()), 1932 &format!("authenticator data to match the authenticator data portion of attestation object: {:?}", &att_obj[att_obj_len - bad_auth.len()..]).as_str(), 1933 ) 1934 .to_string().into_bytes(); 1935 assert_eq!( 1936 serde_json::from_str::<Registration>( 1937 serde_json::json!({ 1938 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1939 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1940 "response": { 1941 "clientDataJSON": b64_cdata_json, 1942 "authenticatorData": base64url_nopad::encode(bad_auth.as_slice()), 1943 "transports": [], 1944 "publicKey": b64_key, 1945 "publicKeyAlgorithm": -8i8, 1946 "attestationObject": b64_aobj, 1947 }, 1948 "clientExtensionResults": {}, 1949 "type": "public-key" 1950 }) 1951 .to_string() 1952 .as_str() 1953 ) 1954 .unwrap_err() 1955 .to_string() 1956 .into_bytes() 1957 .get(..err.len()), 1958 Some(err.as_slice()) 1959 ); 1960 // Missing `authenticatorData`. 1961 err = Error::missing_field("authenticatorData") 1962 .to_string() 1963 .into_bytes(); 1964 assert_eq!( 1965 serde_json::from_str::<Registration>( 1966 serde_json::json!({ 1967 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1968 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1969 "response": { 1970 "clientDataJSON": b64_cdata_json, 1971 "transports": [], 1972 "publicKey": b64_key, 1973 "publicKeyAlgorithm": -8i8, 1974 "attestationObject": b64_aobj, 1975 }, 1976 "clientExtensionResults": {}, 1977 "type": "public-key" 1978 }) 1979 .to_string() 1980 .as_str() 1981 ) 1982 .unwrap_err() 1983 .to_string() 1984 .into_bytes() 1985 .get(..err.len()), 1986 Some(err.as_slice()) 1987 ); 1988 // `null` `authenticatorData`. 1989 err = Error::invalid_type(Unexpected::Other("null"), &"authenticatorData") 1990 .to_string() 1991 .into_bytes(); 1992 assert_eq!( 1993 serde_json::from_str::<Registration>( 1994 serde_json::json!({ 1995 "id": "AAAAAAAAAAAAAAAAAAAAAA", 1996 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 1997 "response": { 1998 "clientDataJSON": b64_cdata_json, 1999 "transports": [], 2000 "authenticatorData": null, 2001 "publicKey": b64_key, 2002 "publicKeyAlgorithm": -8i8, 2003 "attestationObject": b64_aobj, 2004 }, 2005 "clientExtensionResults": {}, 2006 "type": "public-key" 2007 }) 2008 .to_string() 2009 .as_str() 2010 ) 2011 .unwrap_err() 2012 .to_string() 2013 .into_bytes() 2014 .get(..err.len()), 2015 Some(err.as_slice()) 2016 ); 2017 // `publicKeyAlgorithm` mismatch. 2018 err = Error::invalid_value( 2019 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 2020 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Eddsa).as_str() 2021 ) 2022 .to_string().into_bytes(); 2023 assert_eq!( 2024 serde_json::from_str::<Registration>( 2025 serde_json::json!({ 2026 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2027 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2028 "response": { 2029 "clientDataJSON": b64_cdata_json, 2030 "authenticatorData": b64_adata, 2031 "transports": [], 2032 "publicKey": b64_key, 2033 "publicKeyAlgorithm": -7i8, 2034 "attestationObject": b64_aobj, 2035 }, 2036 "clientExtensionResults": {}, 2037 "type": "public-key" 2038 }) 2039 .to_string() 2040 .as_str() 2041 ) 2042 .unwrap_err() 2043 .to_string() 2044 .into_bytes() 2045 .get(..err.len()), 2046 Some(err.as_slice()) 2047 ); 2048 // Missing `publicKeyAlgorithm`. 2049 err = Error::missing_field("publicKeyAlgorithm") 2050 .to_string() 2051 .into_bytes(); 2052 assert_eq!( 2053 serde_json::from_str::<Registration>( 2054 serde_json::json!({ 2055 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2056 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2057 "response": { 2058 "clientDataJSON": b64_cdata_json, 2059 "authenticatorData": b64_adata, 2060 "transports": [], 2061 "publicKey": b64_key, 2062 "attestationObject": b64_aobj, 2063 }, 2064 "clientExtensionResults": {}, 2065 "type": "public-key" 2066 }) 2067 .to_string() 2068 .as_str() 2069 ) 2070 .unwrap_err() 2071 .to_string() 2072 .into_bytes() 2073 .get(..err.len()), 2074 Some(err.as_slice()) 2075 ); 2076 // `null` `publicKeyAlgorithm`. 2077 err = Error::invalid_type(Unexpected::Other("null"), &"publicKeyAlgorithm") 2078 .to_string() 2079 .into_bytes(); 2080 assert_eq!( 2081 serde_json::from_str::<Registration>( 2082 serde_json::json!({ 2083 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2084 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2085 "response": { 2086 "clientDataJSON": b64_cdata_json, 2087 "authenticatorData": b64_adata, 2088 "transports": [], 2089 "publicKey": b64_key, 2090 "publicKeyAlgorithm": null, 2091 "attestationObject": b64_aobj, 2092 }, 2093 "clientExtensionResults": {}, 2094 "type": "public-key" 2095 }) 2096 .to_string() 2097 .as_str() 2098 ) 2099 .unwrap_err() 2100 .to_string() 2101 .into_bytes() 2102 .get(..err.len()), 2103 Some(err.as_slice()) 2104 ); 2105 // `publicKey` mismatch. 2106 err = Error::invalid_value( 2107 Unexpected::Bytes([0; 32].as_slice()), 2108 &format!( 2109 "DER-encoded public key to match the public key within the attestation object: Ed25519(Ed25519PubKey({:?}))", 2110 &att_obj[att_obj.len() - 32..], 2111 ) 2112 .as_str(), 2113 ) 2114 .to_string().into_bytes(); 2115 assert_eq!(serde_json::from_str::<Registration>( 2116 serde_json::json!({ 2117 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2118 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2119 "response": { 2120 "clientDataJSON": b64_cdata_json, 2121 "authenticatorData": b64_adata, 2122 "transports": [], 2123 "publicKey": base64url_nopad::encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()), 2124 "publicKeyAlgorithm": -8i8, 2125 "attestationObject": b64_aobj, 2126 }, 2127 "clientExtensionResults": {}, 2128 "type": "public-key" 2129 }) 2130 .to_string() 2131 .as_str() 2132 ) 2133 .unwrap_err().to_string().into_bytes().get(..err.len()), 2134 Some(err.as_slice()) 2135 ); 2136 // Missing `publicKey` when using EdDSA, ES256, or RS256. 2137 err = Error::missing_field("publicKey").to_string().into_bytes(); 2138 assert_eq!( 2139 serde_json::from_str::<Registration>( 2140 serde_json::json!({ 2141 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2142 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2143 "response": { 2144 "clientDataJSON": b64_cdata_json, 2145 "authenticatorData": b64_adata, 2146 "transports": [], 2147 "publicKeyAlgorithm": -8i8, 2148 "attestationObject": b64_aobj, 2149 }, 2150 "clientExtensionResults": {}, 2151 "type": "public-key" 2152 }) 2153 .to_string() 2154 .as_str() 2155 ) 2156 .unwrap_err() 2157 .to_string() 2158 .into_bytes() 2159 .get(..err.len()), 2160 Some(err.as_slice()) 2161 ); 2162 // `null` `publicKey` when using EdDSA, ES256, or RS256. 2163 err = Error::invalid_type(Unexpected::Other("null"), &"publicKey") 2164 .to_string() 2165 .into_bytes(); 2166 assert_eq!( 2167 serde_json::from_str::<Registration>( 2168 serde_json::json!({ 2169 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2170 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2171 "response": { 2172 "clientDataJSON": b64_cdata_json, 2173 "authenticatorData": b64_adata, 2174 "transports": [], 2175 "publicKey": null, 2176 "publicKeyAlgorithm": -8i8, 2177 "attestationObject": b64_aobj, 2178 }, 2179 "clientExtensionResults": {}, 2180 "type": "public-key" 2181 }) 2182 .to_string() 2183 .as_str() 2184 ) 2185 .unwrap_err() 2186 .to_string() 2187 .into_bytes() 2188 .get(..err.len()), 2189 Some(err.as_slice()) 2190 ); 2191 // Missing `transports`. 2192 err = Error::missing_field("transports").to_string().into_bytes(); 2193 assert_eq!( 2194 serde_json::from_str::<Registration>( 2195 serde_json::json!({ 2196 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2197 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2198 "response": { 2199 "clientDataJSON": b64_cdata_json, 2200 "authenticatorData": b64_adata, 2201 "publicKey": b64_key, 2202 "publicKeyAlgorithm": -8i8, 2203 "attestationObject": b64_aobj, 2204 }, 2205 "clientExtensionResults": {}, 2206 "type": "public-key" 2207 }) 2208 .to_string() 2209 .as_str() 2210 ) 2211 .unwrap_err() 2212 .to_string() 2213 .into_bytes() 2214 .get(..err.len()), 2215 Some(err.as_slice()) 2216 ); 2217 // Duplicate `transports` are allowed. 2218 assert!( 2219 serde_json::from_str::<Registration>( 2220 serde_json::json!({ 2221 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2222 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2223 "response": { 2224 "clientDataJSON": b64_cdata_json, 2225 "authenticatorData": b64_adata, 2226 "transports": ["usb", "usb"], 2227 "publicKey": b64_key, 2228 "publicKeyAlgorithm": -8i8, 2229 "attestationObject": b64_aobj, 2230 }, 2231 "clientExtensionResults": {}, 2232 "type": "public-key" 2233 }) 2234 .to_string() 2235 .as_str() 2236 ) 2237 .is_ok_and(|reg| reg.response.transports.count() == 1) 2238 ); 2239 // `null` `transports`. 2240 err = Error::invalid_type(Unexpected::Other("null"), &"transports") 2241 .to_string() 2242 .into_bytes(); 2243 assert_eq!( 2244 serde_json::from_str::<Registration>( 2245 serde_json::json!({ 2246 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2247 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2248 "response": { 2249 "clientDataJSON": b64_cdata_json, 2250 "authenticatorData": b64_adata, 2251 "transports": null, 2252 "publicKey": b64_key, 2253 "publicKeyAlgorithm": -8i8, 2254 "attestationObject": b64_aobj, 2255 }, 2256 "clientExtensionResults": {}, 2257 "type": "public-key" 2258 }) 2259 .to_string() 2260 .as_str() 2261 ) 2262 .unwrap_err() 2263 .to_string() 2264 .into_bytes() 2265 .get(..err.len()), 2266 Some(err.as_slice()) 2267 ); 2268 // Unknown `transports`. 2269 err = Error::invalid_value( 2270 Unexpected::Str("Usb"), 2271 &"'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', or 'usb'", 2272 ) 2273 .to_string() 2274 .into_bytes(); 2275 assert_eq!( 2276 serde_json::from_str::<Registration>( 2277 serde_json::json!({ 2278 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2279 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2280 "response": { 2281 "clientDataJSON": b64_cdata_json, 2282 "authenticatorData": b64_adata, 2283 "transports": ["Usb"], 2284 "publicKey": b64_key, 2285 "publicKeyAlgorithm": -8i8, 2286 "attestationObject": b64_aobj, 2287 }, 2288 "clientExtensionResults": {}, 2289 "type": "public-key" 2290 }) 2291 .to_string() 2292 .as_str() 2293 ) 2294 .unwrap_err() 2295 .to_string() 2296 .into_bytes() 2297 .get(..err.len()), 2298 Some(err.as_slice()) 2299 ); 2300 // `null` `authenticatorAttachment`. 2301 assert!( 2302 serde_json::from_str::<Registration>( 2303 serde_json::json!({ 2304 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2305 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2306 "response": { 2307 "clientDataJSON": b64_cdata_json, 2308 "authenticatorData": b64_adata, 2309 "transports": [], 2310 "publicKey": b64_key, 2311 "publicKeyAlgorithm": -8i8, 2312 "attestationObject": b64_aobj, 2313 }, 2314 "authenticatorAttachment": null, 2315 "clientExtensionResults": {}, 2316 "type": "public-key" 2317 }) 2318 .to_string() 2319 .as_str() 2320 ) 2321 .is_ok_and(|reg| matches!(reg.authenticator_attachment, AuthenticatorAttachment::None)) 2322 ); 2323 // Unknown `authenticatorAttachment`. 2324 err = Error::invalid_value( 2325 Unexpected::Str("Platform"), 2326 &"'platform' or 'cross-platform'", 2327 ) 2328 .to_string() 2329 .into_bytes(); 2330 assert_eq!( 2331 serde_json::from_str::<Registration>( 2332 serde_json::json!({ 2333 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2334 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2335 "response": { 2336 "clientDataJSON": b64_cdata_json, 2337 "authenticatorData": b64_adata, 2338 "transports": [], 2339 "publicKey": b64_key, 2340 "publicKeyAlgorithm": -8i8, 2341 "attestationObject": b64_aobj, 2342 }, 2343 "authenticatorAttachment": "Platform", 2344 "clientExtensionResults": {}, 2345 "type": "public-key" 2346 }) 2347 .to_string() 2348 .as_str() 2349 ) 2350 .unwrap_err() 2351 .to_string() 2352 .into_bytes() 2353 .get(..err.len()), 2354 Some(err.as_slice()) 2355 ); 2356 // Missing `clientDataJSON`. 2357 err = Error::missing_field("clientDataJSON") 2358 .to_string() 2359 .into_bytes(); 2360 assert_eq!( 2361 serde_json::from_str::<Registration>( 2362 serde_json::json!({ 2363 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2364 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2365 "response": { 2366 "authenticatorData": b64_adata, 2367 "transports": [], 2368 "publicKey": b64_key, 2369 "publicKeyAlgorithm": -8i8, 2370 "attestationObject": b64_aobj, 2371 }, 2372 "clientExtensionResults": {}, 2373 "type": "public-key" 2374 }) 2375 .to_string() 2376 .as_str() 2377 ) 2378 .unwrap_err() 2379 .to_string() 2380 .into_bytes() 2381 .get(..err.len()), 2382 Some(err.as_slice()) 2383 ); 2384 // `null` `clientDataJSON`. 2385 err = Error::invalid_type(Unexpected::Other("null"), &"base64url-encoded data") 2386 .to_string() 2387 .into_bytes(); 2388 assert_eq!( 2389 serde_json::from_str::<Registration>( 2390 serde_json::json!({ 2391 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2392 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2393 "response": { 2394 "clientDataJSON": null, 2395 "authenticatorData": b64_adata, 2396 "transports": [], 2397 "publicKey": b64_key, 2398 "publicKeyAlgorithm": -8i8, 2399 "attestationObject": b64_aobj, 2400 }, 2401 "clientExtensionResults": {}, 2402 "type": "public-key" 2403 }) 2404 .to_string() 2405 .as_str() 2406 ) 2407 .unwrap_err() 2408 .to_string() 2409 .into_bytes() 2410 .get(..err.len()), 2411 Some(err.as_slice()) 2412 ); 2413 // Missing `attestationObject`. 2414 err = Error::missing_field("attestationObject") 2415 .to_string() 2416 .into_bytes(); 2417 assert_eq!( 2418 serde_json::from_str::<Registration>( 2419 serde_json::json!({ 2420 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2421 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2422 "response": { 2423 "clientDataJSON": b64_cdata_json, 2424 "authenticatorData": b64_adata, 2425 "transports": [], 2426 "publicKey": b64_key, 2427 "publicKeyAlgorithm": -8i8, 2428 }, 2429 "clientExtensionResults": {}, 2430 "type": "public-key" 2431 }) 2432 .to_string() 2433 .as_str() 2434 ) 2435 .unwrap_err() 2436 .to_string() 2437 .into_bytes() 2438 .get(..err.len()), 2439 Some(err.as_slice()) 2440 ); 2441 // `null` `attestationObject`. 2442 err = Error::invalid_type( 2443 Unexpected::Other("null"), 2444 &"base64url-encoded attestation object", 2445 ) 2446 .to_string() 2447 .into_bytes(); 2448 assert_eq!( 2449 serde_json::from_str::<Registration>( 2450 serde_json::json!({ 2451 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2452 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2453 "response": { 2454 "clientDataJSON": b64_cdata_json, 2455 "authenticatorData": b64_adata, 2456 "transports": [], 2457 "publicKey": b64_key, 2458 "publicKeyAlgorithm": -8i8, 2459 "attestationObject": null, 2460 }, 2461 "clientExtensionResults": {}, 2462 "type": "public-key" 2463 }) 2464 .to_string() 2465 .as_str() 2466 ) 2467 .unwrap_err() 2468 .to_string() 2469 .into_bytes() 2470 .get(..err.len()), 2471 Some(err.as_slice()) 2472 ); 2473 // Missing `response`. 2474 err = Error::missing_field("response").to_string().into_bytes(); 2475 assert_eq!( 2476 serde_json::from_str::<Registration>( 2477 serde_json::json!({ 2478 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2479 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2480 "clientExtensionResults": {}, 2481 "type": "public-key" 2482 }) 2483 .to_string() 2484 .as_str() 2485 ) 2486 .unwrap_err() 2487 .to_string() 2488 .into_bytes() 2489 .get(..err.len()), 2490 Some(err.as_slice()) 2491 ); 2492 // `null` `response`. 2493 err = Error::invalid_type(Unexpected::Other("null"), &"AuthenticatorAttestation") 2494 .to_string() 2495 .into_bytes(); 2496 assert_eq!( 2497 serde_json::from_str::<Registration>( 2498 serde_json::json!({ 2499 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2500 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2501 "response": null, 2502 "clientExtensionResults": {}, 2503 "type": "public-key" 2504 }) 2505 .to_string() 2506 .as_str() 2507 ) 2508 .unwrap_err() 2509 .to_string() 2510 .into_bytes() 2511 .get(..err.len()), 2512 Some(err.as_slice()) 2513 ); 2514 // Empty `response`. 2515 err = Error::missing_field("clientDataJSON") 2516 .to_string() 2517 .into_bytes(); 2518 assert_eq!( 2519 serde_json::from_str::<Registration>( 2520 serde_json::json!({ 2521 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2522 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2523 "response": {}, 2524 "clientExtensionResults": {}, 2525 "type": "public-key" 2526 }) 2527 .to_string() 2528 .as_str() 2529 ) 2530 .unwrap_err() 2531 .to_string() 2532 .into_bytes() 2533 .get(..err.len()), 2534 Some(err.as_slice()) 2535 ); 2536 // Missing `clientExtensionResults`. 2537 err = Error::missing_field("clientExtensionResults") 2538 .to_string() 2539 .into_bytes(); 2540 assert_eq!( 2541 serde_json::from_str::<Registration>( 2542 serde_json::json!({ 2543 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2544 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2545 "response": { 2546 "clientDataJSON": b64_cdata_json, 2547 "authenticatorData": b64_adata, 2548 "transports": [], 2549 "publicKey": b64_key, 2550 "publicKeyAlgorithm": -8i8, 2551 "attestationObject": b64_aobj, 2552 }, 2553 "type": "public-key" 2554 }) 2555 .to_string() 2556 .as_str() 2557 ) 2558 .unwrap_err() 2559 .to_string() 2560 .into_bytes() 2561 .get(..err.len()), 2562 Some(err.as_slice()) 2563 ); 2564 // `null` `clientExtensionResults`. 2565 err = Error::invalid_type( 2566 Unexpected::Other("null"), 2567 &"clientExtensionResults to be a map of allowed client extensions", 2568 ) 2569 .to_string() 2570 .into_bytes(); 2571 assert_eq!( 2572 serde_json::from_str::<Registration>( 2573 serde_json::json!({ 2574 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2575 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2576 "response": { 2577 "clientDataJSON": b64_cdata_json, 2578 "authenticatorData": b64_adata, 2579 "transports": [], 2580 "publicKey": b64_key, 2581 "publicKeyAlgorithm": -8i8, 2582 "attestationObject": b64_aobj, 2583 }, 2584 "clientExtensionResults": null, 2585 "type": "public-key" 2586 }) 2587 .to_string() 2588 .as_str() 2589 ) 2590 .unwrap_err() 2591 .to_string() 2592 .into_bytes() 2593 .get(..err.len()), 2594 Some(err.as_slice()) 2595 ); 2596 // Missing `type`. 2597 err = Error::missing_field("type").to_string().into_bytes(); 2598 assert_eq!( 2599 serde_json::from_str::<Registration>( 2600 serde_json::json!({ 2601 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2602 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2603 "response": { 2604 "clientDataJSON": b64_cdata_json, 2605 "authenticatorData": b64_adata, 2606 "transports": [], 2607 "publicKey": b64_key, 2608 "publicKeyAlgorithm": -8i8, 2609 "attestationObject": b64_aobj, 2610 }, 2611 "clientExtensionResults": {}, 2612 }) 2613 .to_string() 2614 .as_str() 2615 ) 2616 .unwrap_err() 2617 .to_string() 2618 .into_bytes() 2619 .get(..err.len()), 2620 Some(err.as_slice()) 2621 ); 2622 // `null` `type`. 2623 err = Error::invalid_type(Unexpected::Other("null"), &"public-key") 2624 .to_string() 2625 .into_bytes(); 2626 assert_eq!( 2627 serde_json::from_str::<Registration>( 2628 serde_json::json!({ 2629 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2630 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2631 "response": { 2632 "clientDataJSON": b64_cdata_json, 2633 "authenticatorData": b64_adata, 2634 "transports": [], 2635 "publicKey": b64_key, 2636 "publicKeyAlgorithm": -8i8, 2637 "attestationObject": b64_aobj, 2638 }, 2639 "clientExtensionResults": {}, 2640 "type": null 2641 }) 2642 .to_string() 2643 .as_str() 2644 ) 2645 .unwrap_err() 2646 .to_string() 2647 .into_bytes() 2648 .get(..err.len()), 2649 Some(err.as_slice()) 2650 ); 2651 // Not exactly `public-type` `type`. 2652 err = Error::invalid_value(Unexpected::Str("Public-key"), &"public-key") 2653 .to_string() 2654 .into_bytes(); 2655 assert_eq!( 2656 serde_json::from_str::<Registration>( 2657 serde_json::json!({ 2658 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2659 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2660 "response": { 2661 "clientDataJSON": b64_cdata_json, 2662 "authenticatorData": b64_adata, 2663 "transports": [], 2664 "publicKey": b64_key, 2665 "publicKeyAlgorithm": -8i8, 2666 "attestationObject": b64_aobj, 2667 }, 2668 "clientExtensionResults": {}, 2669 "type": "Public-key" 2670 }) 2671 .to_string() 2672 .as_str() 2673 ) 2674 .unwrap_err() 2675 .to_string() 2676 .into_bytes() 2677 .get(..err.len()), 2678 Some(err.as_slice()) 2679 ); 2680 // `null`. 2681 err = Error::invalid_type(Unexpected::Other("null"), &"PublicKeyCredential") 2682 .to_string() 2683 .into_bytes(); 2684 assert_eq!( 2685 serde_json::from_str::<Registration>(serde_json::json!(null).to_string().as_str()) 2686 .unwrap_err() 2687 .to_string() 2688 .into_bytes() 2689 .get(..err.len()), 2690 Some(err.as_slice()) 2691 ); 2692 // Empty. 2693 err = Error::missing_field("response").to_string().into_bytes(); 2694 assert_eq!( 2695 serde_json::from_str::<Registration>(serde_json::json!({}).to_string().as_str()) 2696 .unwrap_err() 2697 .to_string() 2698 .into_bytes() 2699 .get(..err.len()), 2700 Some(err.as_slice()) 2701 ); 2702 // Unknown field in `response`. 2703 err = Error::unknown_field( 2704 "foo", 2705 [ 2706 "clientDataJSON", 2707 "attestationObject", 2708 "authenticatorData", 2709 "transports", 2710 "publicKey", 2711 "publicKeyAlgorithm", 2712 ] 2713 .as_slice(), 2714 ) 2715 .to_string() 2716 .into_bytes(); 2717 assert_eq!( 2718 serde_json::from_str::<Registration>( 2719 serde_json::json!({ 2720 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2721 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2722 "response": { 2723 "clientDataJSON": b64_cdata_json, 2724 "authenticatorData": b64_adata, 2725 "transports": [], 2726 "publicKey": b64_key, 2727 "publicKeyAlgorithm": -8i8, 2728 "attestationObject": b64_aobj, 2729 "foo": true, 2730 }, 2731 "clientExtensionResults": {}, 2732 "type": "public-key" 2733 }) 2734 .to_string() 2735 .as_str() 2736 ) 2737 .unwrap_err() 2738 .to_string() 2739 .into_bytes() 2740 .get(..err.len()), 2741 Some(err.as_slice()) 2742 ); 2743 // Duplicate field in `response`. 2744 err = Error::duplicate_field("transports") 2745 .to_string() 2746 .into_bytes(); 2747 assert_eq!( 2748 serde_json::from_str::<Registration>( 2749 format!( 2750 "{{ 2751 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2752 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2753 \"response\": {{ 2754 \"clientDataJSON\": \"{b64_cdata_json}\", 2755 \"authenticatorData\": \"{b64_adata}\", 2756 \"transports\": [], 2757 \"publicKey\": \"{b64_key}\", 2758 \"publicKeyAlgorithm\": -8, 2759 \"attestationObject\": \"{b64_aobj}\", 2760 \"transports\": [] 2761 }}, 2762 \"clientExtensionResults\": {{}}, 2763 \"type\": \"public-key\" 2764 2765 }}" 2766 ) 2767 .as_str() 2768 ) 2769 .unwrap_err() 2770 .to_string() 2771 .into_bytes() 2772 .get(..err.len()), 2773 Some(err.as_slice()) 2774 ); 2775 // Unknown field in `PublicKeyCredential`. 2776 err = Error::unknown_field( 2777 "foo", 2778 [ 2779 "id", 2780 "type", 2781 "rawId", 2782 "response", 2783 "authenticatorAttachment", 2784 "clientExtensionResults", 2785 ] 2786 .as_slice(), 2787 ) 2788 .to_string() 2789 .into_bytes(); 2790 assert_eq!( 2791 serde_json::from_str::<Registration>( 2792 serde_json::json!({ 2793 "id": "AAAAAAAAAAAAAAAAAAAAAA", 2794 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 2795 "response": { 2796 "clientDataJSON": b64_cdata_json, 2797 "authenticatorData": b64_adata, 2798 "transports": [], 2799 "publicKey": b64_key, 2800 "publicKeyAlgorithm": -8i8, 2801 "attestationObject": b64_aobj 2802 }, 2803 "clientExtensionResults": {}, 2804 "type": "public-key", 2805 "foo": true, 2806 }) 2807 .to_string() 2808 .as_str() 2809 ) 2810 .unwrap_err() 2811 .to_string() 2812 .into_bytes() 2813 .get(..err.len()), 2814 Some(err.as_slice()) 2815 ); 2816 // Duplicate field in `PublicKeyCredential`. 2817 err = Error::duplicate_field("id").to_string().into_bytes(); 2818 assert_eq!( 2819 serde_json::from_str::<Registration>( 2820 format!( 2821 "{{ 2822 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2823 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2824 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 2825 \"response\": {{ 2826 \"clientDataJSON\": \"{b64_cdata_json}\", 2827 \"authenticatorData\": \"{b64_adata}\", 2828 \"transports\": [], 2829 \"publicKey\": \"{b64_key}\", 2830 \"publicKeyAlgorithm\": -8, 2831 \"attestationObject\": \"{b64_aobj}\" 2832 }}, 2833 \"clientExtensionResults\": {{}}, 2834 \"type\": \"public-key\" 2835 2836 }}" 2837 ) 2838 .as_str() 2839 ) 2840 .unwrap_err() 2841 .to_string() 2842 .into_bytes() 2843 .get(..err.len()), 2844 Some(err.as_slice()) 2845 ); 2846 } 2847 #[expect(clippy::unwrap_used, reason = "OK in tests")] 2848 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 2849 #[expect( 2850 clippy::cognitive_complexity, 2851 clippy::too_many_lines, 2852 reason = "a lot to test" 2853 )] 2854 #[test] 2855 fn client_extensions() { 2856 let c_data_json = serde_json::json!({}).to_string(); 2857 let att_obj: [u8; 143] = [ 2858 cbor::MAP_3, 2859 cbor::TEXT_3, 2860 b'f', 2861 b'm', 2862 b't', 2863 cbor::TEXT_4, 2864 b'n', 2865 b'o', 2866 b'n', 2867 b'e', 2868 cbor::TEXT_7, 2869 b'a', 2870 b't', 2871 b't', 2872 b'S', 2873 b't', 2874 b'm', 2875 b't', 2876 cbor::MAP_0, 2877 cbor::TEXT_8, 2878 b'a', 2879 b'u', 2880 b't', 2881 b'h', 2882 b'D', 2883 b'a', 2884 b't', 2885 b'a', 2886 cbor::BYTES_INFO_24, 2887 113, 2888 // `rpIdHash`. 2889 0, 2890 0, 2891 0, 2892 0, 2893 0, 2894 0, 2895 0, 2896 0, 2897 0, 2898 0, 2899 0, 2900 0, 2901 0, 2902 0, 2903 0, 2904 0, 2905 0, 2906 0, 2907 0, 2908 0, 2909 0, 2910 0, 2911 0, 2912 0, 2913 0, 2914 0, 2915 0, 2916 0, 2917 0, 2918 0, 2919 0, 2920 0, 2921 // `flags`. 2922 0b0100_0101, 2923 // `signCount`. 2924 0, 2925 0, 2926 0, 2927 0, 2928 // `aaguid`. 2929 0, 2930 0, 2931 0, 2932 0, 2933 0, 2934 0, 2935 0, 2936 0, 2937 0, 2938 0, 2939 0, 2940 0, 2941 0, 2942 0, 2943 0, 2944 0, 2945 // `credentialIdLength`. 2946 0, 2947 16, 2948 // `credentialId`. 2949 0, 2950 0, 2951 0, 2952 0, 2953 0, 2954 0, 2955 0, 2956 0, 2957 0, 2958 0, 2959 0, 2960 0, 2961 0, 2962 0, 2963 0, 2964 0, 2965 // Ed25519 COSE key. 2966 cbor::MAP_4, 2967 KTY, 2968 OKP, 2969 ALG, 2970 EDDSA, 2971 // `crv`. 2972 cbor::NEG_ONE, 2973 // `Ed25519`. 2974 cbor::SIX, 2975 // `x`. 2976 cbor::NEG_TWO, 2977 cbor::BYTES_INFO_24, 2978 32, 2979 // Compressed y-coordinate. 2980 1, 2981 1, 2982 1, 2983 1, 2984 1, 2985 1, 2986 1, 2987 1, 2988 1, 2989 1, 2990 1, 2991 1, 2992 1, 2993 1, 2994 1, 2995 1, 2996 1, 2997 1, 2998 1, 2999 1, 3000 1, 3001 1, 3002 1, 3003 1, 3004 1, 3005 1, 3006 1, 3007 1, 3008 1, 3009 1, 3010 1, 3011 1, 3012 ]; 3013 let pub_key = VerifyingKey::from_bytes(&[1; 32]) 3014 .unwrap() 3015 .to_public_key_der() 3016 .unwrap(); 3017 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 3018 let b64_adata = base64url_nopad::encode(&att_obj[att_obj.len() - 113..]); 3019 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 3020 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 3021 // Base case is valid. 3022 assert!( 3023 serde_json::from_str::<Registration>( 3024 serde_json::json!({ 3025 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3026 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3027 "response": { 3028 "clientDataJSON": b64_cdata_json, 3029 "authenticatorData": b64_adata, 3030 "transports": [], 3031 "publicKey": b64_key, 3032 "publicKeyAlgorithm": -8i8, 3033 "attestationObject": b64_aobj, 3034 }, 3035 "clientExtensionResults": {}, 3036 "type": "public-key" 3037 }) 3038 .to_string() 3039 .as_str() 3040 ) 3041 .is_ok_and( 3042 |reg| reg.response.client_data_json == c_data_json.as_bytes() 3043 && reg.response.attestation_object_and_c_data_hash[..att_obj.len()] == att_obj 3044 && reg.response.attestation_object_and_c_data_hash[att_obj.len()..] 3045 == *Sha256::digest(c_data_json.as_bytes()) 3046 && reg.response.transports.is_empty() 3047 && matches!(reg.authenticator_attachment, AuthenticatorAttachment::None) 3048 && reg.client_extension_results.cred_props.is_none() 3049 && reg.client_extension_results.prf.is_none() 3050 ) 3051 ); 3052 // `null` `credProps`. 3053 assert!( 3054 serde_json::from_str::<Registration>( 3055 serde_json::json!({ 3056 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3057 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3058 "response": { 3059 "clientDataJSON": b64_cdata_json, 3060 "authenticatorData": b64_adata, 3061 "transports": [], 3062 "publicKey": b64_key, 3063 "publicKeyAlgorithm": -8i8, 3064 "attestationObject": b64_aobj, 3065 }, 3066 "clientExtensionResults": { 3067 "credProps": null 3068 }, 3069 "type": "public-key" 3070 }) 3071 .to_string() 3072 .as_str() 3073 ) 3074 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3075 && reg.client_extension_results.prf.is_none()) 3076 ); 3077 // `null` `prf`. 3078 assert!( 3079 serde_json::from_str::<Registration>( 3080 serde_json::json!({ 3081 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3082 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3083 "response": { 3084 "clientDataJSON": b64_cdata_json, 3085 "authenticatorData": b64_adata, 3086 "transports": [], 3087 "publicKey": b64_key, 3088 "publicKeyAlgorithm": -8i8, 3089 "attestationObject": b64_aobj, 3090 }, 3091 "clientExtensionResults": { 3092 "prf": null 3093 }, 3094 "type": "public-key" 3095 }) 3096 .to_string() 3097 .as_str() 3098 ) 3099 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3100 && reg.client_extension_results.prf.is_none()) 3101 ); 3102 // Unknown `clientExtensionResults`. 3103 let mut err = Error::unknown_field("CredProps", ["credProps", "prf"].as_slice()) 3104 .to_string() 3105 .into_bytes(); 3106 assert_eq!( 3107 serde_json::from_str::<Registration>( 3108 serde_json::json!({ 3109 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3110 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3111 "response": { 3112 "clientDataJSON": b64_cdata_json, 3113 "authenticatorData": b64_adata, 3114 "transports": [], 3115 "publicKey": b64_key, 3116 "publicKeyAlgorithm": -8i8, 3117 "attestationObject": b64_aobj, 3118 }, 3119 "clientExtensionResults": { 3120 "CredProps": { 3121 "rk": true 3122 } 3123 }, 3124 "type": "public-key" 3125 }) 3126 .to_string() 3127 .as_str() 3128 ) 3129 .unwrap_err() 3130 .to_string() 3131 .into_bytes() 3132 .get(..err.len()), 3133 Some(err.as_slice()) 3134 ); 3135 // Duplicate field. 3136 err = Error::duplicate_field("credProps").to_string().into_bytes(); 3137 assert_eq!( 3138 serde_json::from_str::<Registration>( 3139 format!( 3140 "{{ 3141 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3142 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3143 \"response\": {{ 3144 \"clientDataJSON\": \"{b64_cdata_json}\", 3145 \"authenticatorData\": \"{b64_adata}\", 3146 \"transports\": [], 3147 \"publicKey\": \"{b64_key}\", 3148 \"publicKeyAlgorithm\": -8, 3149 \"attestationObject\": \"{b64_aobj}\" 3150 }}, 3151 \"clientExtensionResults\": {{ 3152 \"credProps\": null, 3153 \"credProps\": null 3154 }}, 3155 \"type\": \"public-key\" 3156 }}" 3157 ) 3158 .as_str() 3159 ) 3160 .unwrap_err() 3161 .to_string() 3162 .into_bytes() 3163 .get(..err.len()), 3164 Some(err.as_slice()) 3165 ); 3166 // `null` `rk`. 3167 assert!( 3168 serde_json::from_str::<Registration>( 3169 serde_json::json!({ 3170 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3171 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3172 "response": { 3173 "clientDataJSON": b64_cdata_json, 3174 "authenticatorData": b64_adata, 3175 "transports": [], 3176 "publicKey": b64_key, 3177 "publicKeyAlgorithm": -8i8, 3178 "attestationObject": b64_aobj, 3179 }, 3180 "clientExtensionResults": { 3181 "credProps": { 3182 "rk": null 3183 } 3184 }, 3185 "type": "public-key" 3186 }) 3187 .to_string() 3188 .as_str() 3189 ) 3190 .is_ok_and(|reg| reg 3191 .client_extension_results 3192 .cred_props 3193 .is_some_and(|props| props.rk.is_none()) 3194 && reg.client_extension_results.prf.is_none()) 3195 ); 3196 // Missing `rk`. 3197 assert!( 3198 serde_json::from_str::<Registration>( 3199 serde_json::json!({ 3200 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3201 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3202 "response": { 3203 "clientDataJSON": b64_cdata_json, 3204 "authenticatorData": b64_adata, 3205 "transports": [], 3206 "publicKey": b64_key, 3207 "publicKeyAlgorithm": -8i8, 3208 "attestationObject": b64_aobj, 3209 }, 3210 "clientExtensionResults": { 3211 "credProps": {} 3212 }, 3213 "type": "public-key" 3214 }) 3215 .to_string() 3216 .as_str() 3217 ) 3218 .is_ok_and(|reg| reg 3219 .client_extension_results 3220 .cred_props 3221 .is_some_and(|props| props.rk.is_none()) 3222 && reg.client_extension_results.prf.is_none()) 3223 ); 3224 // `true` rk`. 3225 assert!( 3226 serde_json::from_str::<Registration>( 3227 serde_json::json!({ 3228 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3229 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3230 "response": { 3231 "clientDataJSON": b64_cdata_json, 3232 "authenticatorData": b64_adata, 3233 "transports": [], 3234 "publicKey": b64_key, 3235 "publicKeyAlgorithm": -8i8, 3236 "attestationObject": b64_aobj, 3237 }, 3238 "clientExtensionResults": { 3239 "credProps": { 3240 "rk": true 3241 } 3242 }, 3243 "type": "public-key" 3244 }) 3245 .to_string() 3246 .as_str() 3247 ) 3248 .is_ok_and(|reg| reg 3249 .client_extension_results 3250 .cred_props 3251 .is_some_and(|props| props.rk.unwrap_or_default()) 3252 && reg.client_extension_results.prf.is_none()) 3253 ); 3254 // `false` rk`. 3255 assert!( 3256 serde_json::from_str::<Registration>( 3257 serde_json::json!({ 3258 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3259 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3260 "response": { 3261 "clientDataJSON": b64_cdata_json, 3262 "authenticatorData": b64_adata, 3263 "transports": [], 3264 "publicKey": b64_key, 3265 "publicKeyAlgorithm": -8i8, 3266 "attestationObject": b64_aobj, 3267 }, 3268 "clientExtensionResults": { 3269 "credProps": { 3270 "rk": false 3271 } 3272 }, 3273 "type": "public-key" 3274 }) 3275 .to_string() 3276 .as_str() 3277 ) 3278 .is_ok_and(|reg| reg 3279 .client_extension_results 3280 .cred_props 3281 .is_some_and(|props| props.rk.is_some_and(|rk| !rk)) 3282 && reg.client_extension_results.prf.is_none()) 3283 ); 3284 // Invalid `rk`. 3285 err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean") 3286 .to_string() 3287 .into_bytes(); 3288 assert_eq!( 3289 serde_json::from_str::<Registration>( 3290 serde_json::json!({ 3291 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3292 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3293 "response": { 3294 "clientDataJSON": b64_cdata_json, 3295 "authenticatorData": b64_adata, 3296 "transports": [], 3297 "publicKey": b64_key, 3298 "publicKeyAlgorithm": -8i8, 3299 "attestationObject": b64_aobj, 3300 }, 3301 "clientExtensionResults": { 3302 "credProps": { 3303 "rk": 3u8 3304 } 3305 }, 3306 "type": "public-key" 3307 }) 3308 .to_string() 3309 .as_str() 3310 ) 3311 .unwrap_err() 3312 .to_string() 3313 .into_bytes() 3314 .get(..err.len()), 3315 Some(err.as_slice()) 3316 ); 3317 // Unknown `credProps` field. 3318 err = Error::unknown_field("Rk", ["rk"].as_slice()) 3319 .to_string() 3320 .into_bytes(); 3321 assert_eq!( 3322 serde_json::from_str::<Registration>( 3323 serde_json::json!({ 3324 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3325 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3326 "response": { 3327 "clientDataJSON": b64_cdata_json, 3328 "authenticatorData": b64_adata, 3329 "transports": [], 3330 "publicKey": b64_key, 3331 "publicKeyAlgorithm": -8i8, 3332 "attestationObject": b64_aobj, 3333 }, 3334 "clientExtensionResults": { 3335 "credProps": { 3336 "Rk": true, 3337 } 3338 }, 3339 "type": "public-key" 3340 }) 3341 .to_string() 3342 .as_str() 3343 ) 3344 .unwrap_err() 3345 .to_string() 3346 .into_bytes() 3347 .get(..err.len()), 3348 Some(err.as_slice()) 3349 ); 3350 // Duplicate field in `credProps`. 3351 err = Error::duplicate_field("rk").to_string().into_bytes(); 3352 assert_eq!( 3353 serde_json::from_str::<Registration>( 3354 format!( 3355 "{{ 3356 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3357 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3358 \"response\": {{ 3359 \"clientDataJSON\": \"{b64_cdata_json}\", 3360 \"authenticatorData\": \"{b64_adata}\", 3361 \"transports\": [], 3362 \"publicKey\": \"{b64_key}\", 3363 \"publicKeyAlgorithm\": -8, 3364 \"attestationObject\": \"{b64_aobj}\" 3365 }}, 3366 \"clientExtensionResults\": {{ 3367 \"credProps\": {{ 3368 \"rk\": true, 3369 \"rk\": true 3370 }} 3371 }}, 3372 \"type\": \"public-key\" 3373 }}" 3374 ) 3375 .as_str() 3376 ) 3377 .unwrap_err() 3378 .to_string() 3379 .into_bytes() 3380 .get(..err.len()), 3381 Some(err.as_slice()) 3382 ); 3383 // `null` `enabled`. 3384 err = Error::invalid_type(Unexpected::Other("null"), &"a boolean") 3385 .to_string() 3386 .into_bytes(); 3387 assert_eq!( 3388 serde_json::from_str::<Registration>( 3389 serde_json::json!({ 3390 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3391 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3392 "response": { 3393 "clientDataJSON": b64_cdata_json, 3394 "authenticatorData": b64_adata, 3395 "transports": [], 3396 "publicKey": b64_key, 3397 "publicKeyAlgorithm": -8i8, 3398 "attestationObject": b64_aobj, 3399 }, 3400 "clientExtensionResults": { 3401 "prf": { 3402 "enabled": null 3403 } 3404 }, 3405 "type": "public-key" 3406 }) 3407 .to_string() 3408 .as_str() 3409 ) 3410 .unwrap_err() 3411 .to_string() 3412 .into_bytes() 3413 .get(..err.len()), 3414 Some(err.as_slice()) 3415 ); 3416 // Missing `enabled`. 3417 err = Error::missing_field("enabled").to_string().into_bytes(); 3418 assert_eq!( 3419 serde_json::from_str::<Registration>( 3420 serde_json::json!({ 3421 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3422 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3423 "response": { 3424 "clientDataJSON": b64_cdata_json, 3425 "authenticatorData": b64_adata, 3426 "transports": [], 3427 "publicKey": b64_key, 3428 "publicKeyAlgorithm": -8i8, 3429 "attestationObject": b64_aobj, 3430 }, 3431 "clientExtensionResults": { 3432 "prf": {} 3433 }, 3434 "type": "public-key" 3435 }) 3436 .to_string() 3437 .as_str() 3438 ) 3439 .unwrap_err() 3440 .to_string() 3441 .into_bytes() 3442 .get(..err.len()), 3443 Some(err.as_slice()) 3444 ); 3445 // `true` `enabled`. 3446 assert!( 3447 serde_json::from_str::<Registration>( 3448 serde_json::json!({ 3449 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3450 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3451 "response": { 3452 "clientDataJSON": b64_cdata_json, 3453 "authenticatorData": b64_adata, 3454 "transports": [], 3455 "publicKey": b64_key, 3456 "publicKeyAlgorithm": -8i8, 3457 "attestationObject": b64_aobj, 3458 }, 3459 "clientExtensionResults": { 3460 "prf": { 3461 "enabled": true 3462 } 3463 }, 3464 "type": "public-key" 3465 }) 3466 .to_string() 3467 .as_str() 3468 ) 3469 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3470 && reg 3471 .client_extension_results 3472 .prf 3473 .is_some_and(|prf| prf.enabled)) 3474 ); 3475 // `false` `enabled`. 3476 assert!( 3477 serde_json::from_str::<Registration>( 3478 serde_json::json!({ 3479 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3480 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3481 "response": { 3482 "clientDataJSON": b64_cdata_json, 3483 "authenticatorData": b64_adata, 3484 "transports": [], 3485 "publicKey": b64_key, 3486 "publicKeyAlgorithm": -8i8, 3487 "attestationObject": b64_aobj, 3488 }, 3489 "clientExtensionResults": { 3490 "prf": { 3491 "enabled": false, 3492 } 3493 }, 3494 "type": "public-key" 3495 }) 3496 .to_string() 3497 .as_str() 3498 ) 3499 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3500 && reg 3501 .client_extension_results 3502 .prf 3503 .is_some_and(|prf| !prf.enabled)) 3504 ); 3505 // Invalid `enabled`. 3506 err = Error::invalid_type(Unexpected::Unsigned(3), &"a boolean") 3507 .to_string() 3508 .into_bytes(); 3509 assert_eq!( 3510 serde_json::from_str::<Registration>( 3511 serde_json::json!({ 3512 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3513 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3514 "response": { 3515 "clientDataJSON": b64_cdata_json, 3516 "authenticatorData": b64_adata, 3517 "transports": [], 3518 "publicKey": b64_key, 3519 "publicKeyAlgorithm": -8i8, 3520 "attestationObject": b64_aobj, 3521 }, 3522 "clientExtensionResults": { 3523 "prf": { 3524 "enabled": 3u8 3525 } 3526 }, 3527 "type": "public-key" 3528 }) 3529 .to_string() 3530 .as_str() 3531 ) 3532 .unwrap_err() 3533 .to_string() 3534 .into_bytes() 3535 .get(..err.len()), 3536 Some(err.as_slice()) 3537 ); 3538 // `null` `results` with `enabled` `true`. 3539 assert!( 3540 serde_json::from_str::<Registration>( 3541 serde_json::json!({ 3542 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3543 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3544 "response": { 3545 "clientDataJSON": b64_cdata_json, 3546 "authenticatorData": b64_adata, 3547 "transports": [], 3548 "publicKey": b64_key, 3549 "publicKeyAlgorithm": -8i8, 3550 "attestationObject": b64_aobj, 3551 }, 3552 "clientExtensionResults": { 3553 "prf": { 3554 "enabled": true, 3555 "results": null, 3556 } 3557 }, 3558 "type": "public-key" 3559 }) 3560 .to_string() 3561 .as_str() 3562 ) 3563 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3564 && reg 3565 .client_extension_results 3566 .prf 3567 .is_some_and(|prf| prf.enabled)) 3568 ); 3569 // `null` `results` with `enabled` `false`. 3570 err = Error::custom( 3571 "prf must not have 'results', including a null 'results', if 'enabled' is false", 3572 ) 3573 .to_string() 3574 .into_bytes(); 3575 assert_eq!( 3576 serde_json::from_str::<Registration>( 3577 serde_json::json!({ 3578 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3579 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3580 "response": { 3581 "clientDataJSON": b64_cdata_json, 3582 "authenticatorData": b64_adata, 3583 "transports": [], 3584 "publicKey": b64_key, 3585 "publicKeyAlgorithm": -8i8, 3586 "attestationObject": b64_aobj, 3587 }, 3588 "clientExtensionResults": { 3589 "prf": { 3590 "enabled": false, 3591 "results": null 3592 } 3593 }, 3594 "type": "public-key" 3595 }) 3596 .to_string() 3597 .as_str() 3598 ) 3599 .unwrap_err() 3600 .to_string() 3601 .into_bytes() 3602 .get(..err.len()), 3603 Some(err.as_slice()) 3604 ); 3605 // Duplicate field in `prf`. 3606 err = Error::duplicate_field("enabled").to_string().into_bytes(); 3607 assert_eq!( 3608 serde_json::from_str::<Registration>( 3609 format!( 3610 "{{ 3611 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3612 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3613 \"response\": {{ 3614 \"clientDataJSON\": \"{b64_cdata_json}\", 3615 \"authenticatorData\": \"{b64_adata}\", 3616 \"transports\": [], 3617 \"publicKey\": \"{b64_key}\", 3618 \"publicKeyAlgorithm\": -8, 3619 \"attestationObject\": \"{b64_aobj}\" 3620 }}, 3621 \"clientExtensionResults\": {{ 3622 \"prf\": {{ 3623 \"enabled\": true, 3624 \"enabled\": true 3625 }} 3626 }}, 3627 \"type\": \"public-key\" 3628 }}" 3629 ) 3630 .as_str() 3631 ) 3632 .unwrap_err() 3633 .to_string() 3634 .into_bytes() 3635 .get(..err.len()), 3636 Some(err.as_slice()) 3637 ); 3638 // Missing `first`. 3639 err = Error::missing_field("first").to_string().into_bytes(); 3640 assert_eq!( 3641 serde_json::from_str::<Registration>( 3642 serde_json::json!({ 3643 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3644 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3645 "response": { 3646 "clientDataJSON": b64_cdata_json, 3647 "authenticatorData": b64_adata, 3648 "transports": [], 3649 "publicKey": b64_key, 3650 "publicKeyAlgorithm": -8i8, 3651 "attestationObject": b64_aobj, 3652 }, 3653 "clientExtensionResults": { 3654 "prf": { 3655 "enabled": true, 3656 "results": {}, 3657 } 3658 }, 3659 "type": "public-key" 3660 }) 3661 .to_string() 3662 .as_str() 3663 ) 3664 .unwrap_err() 3665 .to_string() 3666 .into_bytes() 3667 .get(..err.len()), 3668 Some(err.as_slice()) 3669 ); 3670 // `null` `first`. 3671 assert!( 3672 serde_json::from_str::<Registration>( 3673 serde_json::json!({ 3674 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3675 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3676 "response": { 3677 "clientDataJSON": b64_cdata_json, 3678 "authenticatorData": b64_adata, 3679 "transports": [], 3680 "publicKey": b64_key, 3681 "publicKeyAlgorithm": -8i8, 3682 "attestationObject": b64_aobj, 3683 }, 3684 "clientExtensionResults": { 3685 "prf": { 3686 "enabled": true, 3687 "results": { 3688 "first": null 3689 }, 3690 } 3691 }, 3692 "type": "public-key" 3693 }) 3694 .to_string() 3695 .as_str() 3696 ) 3697 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3698 && reg 3699 .client_extension_results 3700 .prf 3701 .is_some_and(|prf| prf.enabled)) 3702 ); 3703 // `null` `second`. 3704 assert!( 3705 serde_json::from_str::<Registration>( 3706 serde_json::json!({ 3707 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3708 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3709 "response": { 3710 "clientDataJSON": b64_cdata_json, 3711 "authenticatorData": b64_adata, 3712 "transports": [], 3713 "publicKey": b64_key, 3714 "publicKeyAlgorithm": -8i8, 3715 "attestationObject": b64_aobj, 3716 }, 3717 "clientExtensionResults": { 3718 "prf": { 3719 "enabled": true, 3720 "results": { 3721 "first": null, 3722 "second": null 3723 }, 3724 } 3725 }, 3726 "type": "public-key" 3727 }) 3728 .to_string() 3729 .as_str() 3730 ) 3731 .is_ok_and(|reg| reg.client_extension_results.cred_props.is_none() 3732 && reg 3733 .client_extension_results 3734 .prf 3735 .is_some_and(|prf| prf.enabled)) 3736 ); 3737 // Non-`null` `first`. 3738 err = Error::invalid_type(Unexpected::Option, &"null") 3739 .to_string() 3740 .into_bytes(); 3741 assert_eq!( 3742 serde_json::from_str::<Registration>( 3743 serde_json::json!({ 3744 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3745 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3746 "response": { 3747 "clientDataJSON": b64_cdata_json, 3748 "authenticatorData": b64_adata, 3749 "transports": [], 3750 "publicKey": b64_key, 3751 "publicKeyAlgorithm": -8i8, 3752 "attestationObject": b64_aobj, 3753 }, 3754 "clientExtensionResults": { 3755 "prf": { 3756 "enabled": true, 3757 "results": { 3758 "first": "" 3759 }, 3760 } 3761 }, 3762 "type": "public-key" 3763 }) 3764 .to_string() 3765 .as_str() 3766 ) 3767 .unwrap_err() 3768 .to_string() 3769 .into_bytes() 3770 .get(..err.len()), 3771 Some(err.as_slice()) 3772 ); 3773 // Non-`null` `second`. 3774 err = Error::invalid_type(Unexpected::Option, &"null") 3775 .to_string() 3776 .into_bytes(); 3777 assert_eq!( 3778 serde_json::from_str::<Registration>( 3779 serde_json::json!({ 3780 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3781 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3782 "response": { 3783 "clientDataJSON": b64_cdata_json, 3784 "authenticatorData": b64_adata, 3785 "transports": [], 3786 "publicKey": b64_key, 3787 "publicKeyAlgorithm": -8i8, 3788 "attestationObject": b64_aobj, 3789 }, 3790 "clientExtensionResults": { 3791 "prf": { 3792 "enabled": true, 3793 "results": { 3794 "first": null, 3795 "second": "" 3796 }, 3797 } 3798 }, 3799 "type": "public-key" 3800 }) 3801 .to_string() 3802 .as_str() 3803 ) 3804 .unwrap_err() 3805 .to_string() 3806 .into_bytes() 3807 .get(..err.len()), 3808 Some(err.as_slice()) 3809 ); 3810 // Unknown `prf` field. 3811 err = Error::unknown_field("Results", ["enabled", "results"].as_slice()) 3812 .to_string() 3813 .into_bytes(); 3814 assert_eq!( 3815 serde_json::from_str::<Registration>( 3816 serde_json::json!({ 3817 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3818 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3819 "response": { 3820 "clientDataJSON": b64_cdata_json, 3821 "authenticatorData": b64_adata, 3822 "transports": [], 3823 "publicKey": b64_key, 3824 "publicKeyAlgorithm": -8i8, 3825 "attestationObject": b64_aobj, 3826 }, 3827 "clientExtensionResults": { 3828 "prf": { 3829 "enabled": true, 3830 "Results": null 3831 } 3832 }, 3833 "type": "public-key" 3834 }) 3835 .to_string() 3836 .as_str() 3837 ) 3838 .unwrap_err() 3839 .to_string() 3840 .into_bytes() 3841 .get(..err.len()), 3842 Some(err.as_slice()) 3843 ); 3844 // Unknown `results` field. 3845 err = Error::unknown_field("Second", ["first", "second"].as_slice()) 3846 .to_string() 3847 .into_bytes(); 3848 assert_eq!( 3849 serde_json::from_str::<Registration>( 3850 serde_json::json!({ 3851 "id": "AAAAAAAAAAAAAAAAAAAAAA", 3852 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 3853 "response": { 3854 "clientDataJSON": b64_cdata_json, 3855 "authenticatorData": b64_adata, 3856 "transports": [], 3857 "publicKey": b64_key, 3858 "publicKeyAlgorithm": -8i8, 3859 "attestationObject": b64_aobj, 3860 }, 3861 "clientExtensionResults": { 3862 "prf": { 3863 "enabled": true, 3864 "results": { 3865 "first": null, 3866 "Second": null 3867 } 3868 } 3869 }, 3870 "type": "public-key" 3871 }) 3872 .to_string() 3873 .as_str() 3874 ) 3875 .unwrap_err() 3876 .to_string() 3877 .into_bytes() 3878 .get(..err.len()), 3879 Some(err.as_slice()) 3880 ); 3881 // Duplicate field in `results`. 3882 err = Error::duplicate_field("first").to_string().into_bytes(); 3883 assert_eq!( 3884 serde_json::from_str::<Registration>( 3885 format!( 3886 "{{ 3887 \"id\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3888 \"rawId\": \"AAAAAAAAAAAAAAAAAAAAAA\", 3889 \"response\": {{ 3890 \"clientDataJSON\": \"{b64_cdata_json}\", 3891 \"authenticatorData\": \"{b64_adata}\", 3892 \"transports\": [], 3893 \"publicKey\": \"{b64_key}\", 3894 \"publicKeyAlgorithm\": -8, 3895 \"attestationObject\": \"{b64_aobj}\" 3896 }}, 3897 \"clientExtensionResults\": {{ 3898 \"prf\": {{ 3899 \"enabled\": true, 3900 \"results\": {{ 3901 \"first\": null, 3902 \"first\": null 3903 }} 3904 }} 3905 }}, 3906 \"type\": \"public-key\" 3907 }}" 3908 ) 3909 .as_str() 3910 ) 3911 .unwrap_err() 3912 .to_string() 3913 .into_bytes() 3914 .get(..err.len()), 3915 Some(err.as_slice()) 3916 ); 3917 } 3918 #[expect(clippy::unwrap_used, reason = "OK in tests")] 3919 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 3920 #[expect(clippy::too_many_lines, reason = "a lot to test")] 3921 #[test] 3922 fn es256_registration_deserialize_data_mismatch() { 3923 let c_data_json = serde_json::json!({}).to_string(); 3924 let mut att_obj: [u8; 178] = [ 3925 cbor::MAP_3, 3926 cbor::TEXT_3, 3927 b'f', 3928 b'm', 3929 b't', 3930 cbor::TEXT_4, 3931 b'n', 3932 b'o', 3933 b'n', 3934 b'e', 3935 cbor::TEXT_7, 3936 b'a', 3937 b't', 3938 b't', 3939 b'S', 3940 b't', 3941 b'm', 3942 b't', 3943 cbor::MAP_0, 3944 cbor::TEXT_8, 3945 b'a', 3946 b'u', 3947 b't', 3948 b'h', 3949 b'D', 3950 b'a', 3951 b't', 3952 b'a', 3953 cbor::BYTES_INFO_24, 3954 148, 3955 // `rpIdHash`. 3956 0, 3957 0, 3958 0, 3959 0, 3960 0, 3961 0, 3962 0, 3963 0, 3964 0, 3965 0, 3966 0, 3967 0, 3968 0, 3969 0, 3970 0, 3971 0, 3972 0, 3973 0, 3974 0, 3975 0, 3976 0, 3977 0, 3978 0, 3979 0, 3980 0, 3981 0, 3982 0, 3983 0, 3984 0, 3985 0, 3986 0, 3987 0, 3988 // `flags`. 3989 0b0100_0101, 3990 // `signCount`. 3991 0, 3992 0, 3993 0, 3994 0, 3995 // `aaguid`. 3996 0, 3997 0, 3998 0, 3999 0, 4000 0, 4001 0, 4002 0, 4003 0, 4004 0, 4005 0, 4006 0, 4007 0, 4008 0, 4009 0, 4010 0, 4011 0, 4012 // `credentialIdLength`. 4013 0, 4014 16, 4015 // `credentialId`. 4016 0, 4017 0, 4018 0, 4019 0, 4020 0, 4021 0, 4022 0, 4023 0, 4024 0, 4025 0, 4026 0, 4027 0, 4028 0, 4029 0, 4030 0, 4031 0, 4032 // P-256 COSE key. 4033 cbor::MAP_5, 4034 KTY, 4035 EC2, 4036 ALG, 4037 ES256, 4038 // `crv`. 4039 cbor::NEG_ONE, 4040 // `P-256`. 4041 cbor::ONE, 4042 // `x`. 4043 cbor::NEG_TWO, 4044 cbor::BYTES_INFO_24, 4045 32, 4046 // x-coordinate. This will be overwritten later. 4047 0, 4048 0, 4049 0, 4050 0, 4051 0, 4052 0, 4053 0, 4054 0, 4055 0, 4056 0, 4057 0, 4058 0, 4059 0, 4060 0, 4061 0, 4062 0, 4063 0, 4064 0, 4065 0, 4066 0, 4067 0, 4068 0, 4069 0, 4070 0, 4071 0, 4072 0, 4073 0, 4074 0, 4075 0, 4076 0, 4077 0, 4078 0, 4079 // `y`. 4080 cbor::NEG_THREE, 4081 cbor::BYTES_INFO_24, 4082 32, 4083 // y-coordinate. This will be overwritten later. 4084 0, 4085 0, 4086 0, 4087 0, 4088 0, 4089 0, 4090 0, 4091 0, 4092 0, 4093 0, 4094 0, 4095 0, 4096 0, 4097 0, 4098 0, 4099 0, 4100 0, 4101 0, 4102 0, 4103 0, 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 ]; 4117 let key = P256Key::from_bytes( 4118 &[ 4119 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 4120 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 4121 ] 4122 .into(), 4123 ) 4124 .unwrap() 4125 .public_key(); 4126 let enc_key = key.to_encoded_point(false); 4127 let pub_key = key.to_public_key_der().unwrap(); 4128 let att_obj_len = att_obj.len(); 4129 let x_start = att_obj_len - 67; 4130 let y_meta_start = x_start + 32; 4131 let y_start = y_meta_start + 3; 4132 att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap()); 4133 att_obj[y_start..].copy_from_slice(enc_key.y().unwrap()); 4134 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 4135 let b64_adata = base64url_nopad::encode(&att_obj[att_obj.len() - 148..]); 4136 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 4137 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 4138 // Base case is valid. 4139 assert!( 4140 serde_json::from_str::<Registration>( 4141 serde_json::json!({ 4142 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4143 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4144 "response": { 4145 "clientDataJSON": b64_cdata_json, 4146 "authenticatorData": b64_adata, 4147 "transports": [], 4148 "publicKey": b64_key, 4149 "publicKeyAlgorithm": -7i8, 4150 "attestationObject": b64_aobj, 4151 }, 4152 "clientExtensionResults": {}, 4153 "type": "public-key" 4154 }) 4155 .to_string() 4156 .as_str() 4157 ) 4158 .is_ok_and( 4159 |reg| reg.response.client_data_json == c_data_json.as_bytes() 4160 && reg.response.attestation_object_and_c_data_hash[..att_obj.len()] == att_obj 4161 && reg.response.attestation_object_and_c_data_hash[att_obj.len()..] 4162 == *Sha256::digest(c_data_json.as_bytes()) 4163 && reg.response.transports.is_empty() 4164 && matches!(reg.authenticator_attachment, AuthenticatorAttachment::None) 4165 && reg.client_extension_results.cred_props.is_none() 4166 && reg.client_extension_results.prf.is_none() 4167 ) 4168 ); 4169 // `publicKeyAlgorithm` mismatch. 4170 let mut err = Error::invalid_value( 4171 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 4172 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es256).as_str() 4173 ) 4174 .to_string().into_bytes(); 4175 assert_eq!( 4176 serde_json::from_str::<Registration>( 4177 serde_json::json!({ 4178 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4179 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4180 "response": { 4181 "clientDataJSON": b64_cdata_json, 4182 "authenticatorData": b64_adata, 4183 "transports": [], 4184 "publicKey": b64_key, 4185 "publicKeyAlgorithm": -8i8, 4186 "attestationObject": b64_aobj, 4187 }, 4188 "clientExtensionResults": {}, 4189 "type": "public-key" 4190 }) 4191 .to_string() 4192 .as_str() 4193 ) 4194 .unwrap_err() 4195 .to_string() 4196 .into_bytes() 4197 .get(..err.len()), 4198 Some(err.as_slice()) 4199 ); 4200 // Missing `publicKeyAlgorithm`. 4201 err = Error::missing_field("publicKeyAlgorithm") 4202 .to_string() 4203 .into_bytes(); 4204 assert_eq!( 4205 serde_json::from_str::<Registration>( 4206 serde_json::json!({ 4207 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4208 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4209 "response": { 4210 "clientDataJSON": b64_cdata_json, 4211 "authenticatorData": b64_adata, 4212 "transports": [], 4213 "publicKey": b64_key, 4214 "attestationObject": b64_aobj, 4215 }, 4216 "clientExtensionResults": {}, 4217 "type": "public-key" 4218 }) 4219 .to_string() 4220 .as_str() 4221 ) 4222 .unwrap_err() 4223 .to_string() 4224 .into_bytes() 4225 .get(..err.len()), 4226 Some(err.as_slice()) 4227 ); 4228 // `null` `publicKeyAlgorithm`. 4229 err = Error::invalid_type(Unexpected::Other("null"), &"publicKeyAlgorithm") 4230 .to_string() 4231 .into_bytes(); 4232 assert_eq!( 4233 serde_json::from_str::<Registration>( 4234 serde_json::json!({ 4235 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4236 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4237 "response": { 4238 "clientDataJSON": b64_cdata_json, 4239 "authenticatorData": b64_adata, 4240 "transports": [], 4241 "publicKey": b64_key, 4242 "publicKeyAlgorithm": null, 4243 "attestationObject": b64_aobj, 4244 }, 4245 "clientExtensionResults": {}, 4246 "type": "public-key" 4247 }) 4248 .to_string() 4249 .as_str() 4250 ) 4251 .unwrap_err() 4252 .to_string() 4253 .into_bytes() 4254 .get(..err.len()), 4255 Some(err.as_slice()) 4256 ); 4257 // `publicKey` mismatch. 4258 let bad_pub_key = P256PubKey::from_encoded_point(&P256Pt::from_affine_coordinates( 4259 &[ 4260 66, 71, 188, 41, 125, 2, 226, 44, 148, 62, 63, 190, 172, 64, 33, 214, 6, 37, 148, 4261 23, 240, 235, 203, 84, 112, 219, 232, 197, 54, 182, 17, 235, 4262 ] 4263 .into(), 4264 &[ 4265 22, 172, 123, 13, 170, 242, 217, 248, 193, 209, 206, 163, 92, 4, 162, 168, 113, 63, 4266 2, 117, 16, 223, 239, 196, 109, 179, 10, 130, 43, 213, 205, 92, 4267 ] 4268 .into(), 4269 false, 4270 )) 4271 .unwrap(); 4272 err = Error::invalid_value( 4273 Unexpected::Bytes([0; 32].as_slice()), 4274 &format!( 4275 "DER-encoded public key to match the public key within the attestation object: P256(UncompressedP256PubKey({:?}, {:?}))", 4276 &att_obj[x_start..y_meta_start], 4277 &att_obj[y_start..], 4278 ) 4279 .as_str(), 4280 ) 4281 .to_string().into_bytes(); 4282 assert_eq!(serde_json::from_str::<Registration>( 4283 serde_json::json!({ 4284 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4285 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4286 "response": { 4287 "clientDataJSON": b64_cdata_json, 4288 "authenticatorData": b64_adata, 4289 "transports": [], 4290 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 4291 "publicKeyAlgorithm": -7i8, 4292 "attestationObject": b64_aobj, 4293 }, 4294 "clientExtensionResults": {}, 4295 "type": "public-key" 4296 }) 4297 .to_string() 4298 .as_str() 4299 ) 4300 .unwrap_err().to_string().into_bytes().get(..err.len()), 4301 Some(err.as_slice()) 4302 ); 4303 // Missing `publicKey` when using EdDSA, ES256, or RS256. 4304 err = Error::missing_field("publicKey").to_string().into_bytes(); 4305 assert_eq!( 4306 serde_json::from_str::<Registration>( 4307 serde_json::json!({ 4308 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4309 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4310 "response": { 4311 "clientDataJSON": b64_cdata_json, 4312 "authenticatorData": b64_adata, 4313 "transports": [], 4314 "publicKeyAlgorithm": -7i8, 4315 "attestationObject": b64_aobj, 4316 }, 4317 "clientExtensionResults": {}, 4318 "type": "public-key" 4319 }) 4320 .to_string() 4321 .as_str() 4322 ) 4323 .unwrap_err() 4324 .to_string() 4325 .into_bytes() 4326 .get(..err.len()), 4327 Some(err.as_slice()) 4328 ); 4329 // `null` `publicKey` when using EdDSA, ES256, or RS256. 4330 err = Error::invalid_type(Unexpected::Other("null"), &"publicKey") 4331 .to_string() 4332 .into_bytes(); 4333 assert_eq!( 4334 serde_json::from_str::<Registration>( 4335 serde_json::json!({ 4336 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4337 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4338 "response": { 4339 "clientDataJSON": b64_cdata_json, 4340 "authenticatorData": b64_adata, 4341 "transports": [], 4342 "publicKey": null, 4343 "publicKeyAlgorithm": -7i8, 4344 "attestationObject": b64_aobj, 4345 }, 4346 "clientExtensionResults": {}, 4347 "type": "public-key" 4348 }) 4349 .to_string() 4350 .as_str() 4351 ) 4352 .unwrap_err() 4353 .to_string() 4354 .into_bytes() 4355 .get(..err.len()), 4356 Some(err.as_slice()) 4357 ); 4358 } 4359 #[expect( 4360 clippy::assertions_on_result_states, 4361 clippy::unwrap_used, 4362 reason = "OK in tests" 4363 )] 4364 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 4365 #[expect(clippy::too_many_lines, reason = "a lot to test")] 4366 #[test] 4367 fn es384_registration_deserialize_data_mismatch() { 4368 let c_data_json = serde_json::json!({}).to_string(); 4369 let mut att_obj: [u8; 211] = [ 4370 cbor::MAP_3, 4371 cbor::TEXT_3, 4372 b'f', 4373 b'm', 4374 b't', 4375 cbor::TEXT_4, 4376 b'n', 4377 b'o', 4378 b'n', 4379 b'e', 4380 cbor::TEXT_7, 4381 b'a', 4382 b't', 4383 b't', 4384 b'S', 4385 b't', 4386 b'm', 4387 b't', 4388 cbor::MAP_0, 4389 cbor::TEXT_8, 4390 b'a', 4391 b'u', 4392 b't', 4393 b'h', 4394 b'D', 4395 b'a', 4396 b't', 4397 b'a', 4398 cbor::BYTES_INFO_24, 4399 181, 4400 // `rpIdHash`. 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 // `flags`. 4434 0b0100_0101, 4435 // `signCount`. 4436 0, 4437 0, 4438 0, 4439 0, 4440 // `aaguid`. 4441 0, 4442 0, 4443 0, 4444 0, 4445 0, 4446 0, 4447 0, 4448 0, 4449 0, 4450 0, 4451 0, 4452 0, 4453 0, 4454 0, 4455 0, 4456 0, 4457 // `credentialIdLength`. 4458 0, 4459 16, 4460 // `credentialId`. 4461 0, 4462 0, 4463 0, 4464 0, 4465 0, 4466 0, 4467 0, 4468 0, 4469 0, 4470 0, 4471 0, 4472 0, 4473 0, 4474 0, 4475 0, 4476 0, 4477 // P-384 COSE key. 4478 cbor::MAP_5, 4479 KTY, 4480 EC2, 4481 ALG, 4482 cbor::NEG_INFO_24, 4483 ES384, 4484 // `crv`. 4485 cbor::NEG_ONE, 4486 // `P-384`. 4487 cbor::TWO, 4488 // `x`. 4489 cbor::NEG_TWO, 4490 cbor::BYTES_INFO_24, 4491 48, 4492 // x-coordinate. This will be overwritten later. 4493 0, 4494 0, 4495 0, 4496 0, 4497 0, 4498 0, 4499 0, 4500 0, 4501 0, 4502 0, 4503 0, 4504 0, 4505 0, 4506 0, 4507 0, 4508 0, 4509 0, 4510 0, 4511 0, 4512 0, 4513 0, 4514 0, 4515 0, 4516 0, 4517 0, 4518 0, 4519 0, 4520 0, 4521 0, 4522 0, 4523 0, 4524 0, 4525 0, 4526 0, 4527 0, 4528 0, 4529 0, 4530 0, 4531 0, 4532 0, 4533 0, 4534 0, 4535 0, 4536 0, 4537 0, 4538 0, 4539 0, 4540 0, 4541 // `y`. 4542 cbor::NEG_THREE, 4543 cbor::BYTES_INFO_24, 4544 48, 4545 // y-coordinate. This will be overwritten later. 4546 0, 4547 0, 4548 0, 4549 0, 4550 0, 4551 0, 4552 0, 4553 0, 4554 0, 4555 0, 4556 0, 4557 0, 4558 0, 4559 0, 4560 0, 4561 0, 4562 0, 4563 0, 4564 0, 4565 0, 4566 0, 4567 0, 4568 0, 4569 0, 4570 0, 4571 0, 4572 0, 4573 0, 4574 0, 4575 0, 4576 0, 4577 0, 4578 0, 4579 0, 4580 0, 4581 0, 4582 0, 4583 0, 4584 0, 4585 0, 4586 0, 4587 0, 4588 0, 4589 0, 4590 0, 4591 0, 4592 0, 4593 0, 4594 ]; 4595 let key = P384Key::from_bytes( 4596 &[ 4597 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 4598 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 4599 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 4600 ] 4601 .into(), 4602 ) 4603 .unwrap() 4604 .public_key(); 4605 let enc_key = key.to_encoded_point(false); 4606 let pub_key = key.to_public_key_der().unwrap(); 4607 let att_obj_len = att_obj.len(); 4608 let x_start = att_obj_len - 99; 4609 let y_meta_start = x_start + 48; 4610 let y_start = y_meta_start + 3; 4611 att_obj[x_start..y_meta_start].copy_from_slice(enc_key.x().unwrap()); 4612 att_obj[y_start..].copy_from_slice(enc_key.y().unwrap()); 4613 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 4614 let b64_adata = base64url_nopad::encode(&att_obj[att_obj_len - 181..]); 4615 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 4616 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 4617 // Base case is valid. 4618 assert!( 4619 serde_json::from_str::<Registration>( 4620 serde_json::json!({ 4621 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4622 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4623 "response": { 4624 "clientDataJSON": b64_cdata_json, 4625 "authenticatorData": b64_adata, 4626 "transports": [], 4627 "publicKey": b64_key, 4628 "publicKeyAlgorithm": -35i8, 4629 "attestationObject": b64_aobj, 4630 }, 4631 "clientExtensionResults": {}, 4632 "type": "public-key" 4633 }) 4634 .to_string() 4635 .as_str() 4636 ) 4637 .is_ok_and( 4638 |reg| reg.response.client_data_json == c_data_json.as_bytes() 4639 && reg.response.attestation_object_and_c_data_hash[..att_obj.len()] == att_obj 4640 && reg.response.attestation_object_and_c_data_hash[att_obj.len()..] 4641 == *Sha256::digest(c_data_json.as_bytes()) 4642 && reg.response.transports.is_empty() 4643 && matches!(reg.authenticator_attachment, AuthenticatorAttachment::None) 4644 && reg.client_extension_results.cred_props.is_none() 4645 && reg.client_extension_results.prf.is_none() 4646 ) 4647 ); 4648 // `publicKeyAlgorithm` mismatch. 4649 let mut err = Error::invalid_value( 4650 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 4651 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 4652 ) 4653 .to_string().into_bytes(); 4654 assert_eq!( 4655 serde_json::from_str::<Registration>( 4656 serde_json::json!({ 4657 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4658 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4659 "response": { 4660 "clientDataJSON": b64_cdata_json, 4661 "authenticatorData": b64_adata, 4662 "transports": [], 4663 "publicKey": b64_key, 4664 "publicKeyAlgorithm": -7i8, 4665 "attestationObject": b64_aobj, 4666 }, 4667 "clientExtensionResults": {}, 4668 "type": "public-key" 4669 }) 4670 .to_string() 4671 .as_str() 4672 ) 4673 .unwrap_err() 4674 .to_string() 4675 .into_bytes() 4676 .get(..err.len()), 4677 Some(err.as_slice()) 4678 ); 4679 // Missing `publicKeyAlgorithm`. 4680 err = Error::missing_field("publicKeyAlgorithm") 4681 .to_string() 4682 .into_bytes(); 4683 assert_eq!( 4684 serde_json::from_str::<Registration>( 4685 serde_json::json!({ 4686 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4687 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4688 "response": { 4689 "clientDataJSON": b64_cdata_json, 4690 "authenticatorData": b64_adata, 4691 "transports": [], 4692 "publicKey": b64_key, 4693 "attestationObject": b64_aobj, 4694 }, 4695 "clientExtensionResults": {}, 4696 "type": "public-key" 4697 }) 4698 .to_string() 4699 .as_str() 4700 ) 4701 .unwrap_err() 4702 .to_string() 4703 .into_bytes() 4704 .get(..err.len()), 4705 Some(err.as_slice()) 4706 ); 4707 // `null` `publicKeyAlgorithm`. 4708 err = Error::invalid_type(Unexpected::Other("null"), &"publicKeyAlgorithm") 4709 .to_string() 4710 .into_bytes(); 4711 assert_eq!( 4712 serde_json::from_str::<Registration>( 4713 serde_json::json!({ 4714 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4715 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4716 "response": { 4717 "clientDataJSON": b64_cdata_json, 4718 "authenticatorData": b64_adata, 4719 "transports": [], 4720 "publicKey": b64_key, 4721 "publicKeyAlgorithm": null, 4722 "attestationObject": b64_aobj, 4723 }, 4724 "clientExtensionResults": {}, 4725 "type": "public-key" 4726 }) 4727 .to_string() 4728 .as_str() 4729 ) 4730 .unwrap_err() 4731 .to_string() 4732 .into_bytes() 4733 .get(..err.len()), 4734 Some(err.as_slice()) 4735 ); 4736 // `publicKey` mismatch. 4737 let bad_pub_key = P384PubKey::from_encoded_point(&P384Pt::from_affine_coordinates( 4738 &[ 4739 192, 10, 27, 46, 66, 67, 80, 98, 33, 230, 156, 95, 1, 135, 150, 110, 64, 243, 22, 4740 118, 5, 255, 107, 44, 234, 111, 217, 105, 125, 114, 39, 7, 126, 2, 191, 111, 48, 4741 93, 234, 175, 18, 172, 59, 28, 97, 106, 178, 152, 4742 ] 4743 .into(), 4744 &[ 4745 57, 36, 196, 12, 109, 129, 253, 115, 88, 154, 6, 43, 195, 85, 169, 5, 230, 51, 28, 4746 205, 142, 28, 150, 35, 24, 222, 170, 253, 14, 248, 84, 151, 109, 191, 152, 111, 4747 222, 70, 134, 247, 109, 171, 211, 33, 214, 217, 200, 111, 4748 ] 4749 .into(), 4750 false, 4751 )) 4752 .unwrap(); 4753 err = Error::invalid_value( 4754 Unexpected::Bytes([0; 32].as_slice()), 4755 &format!( 4756 "DER-encoded public key to match the public key within the attestation object: P384(UncompressedP384PubKey({:?}, {:?}))", 4757 &att_obj[x_start..y_meta_start], 4758 &att_obj[y_start..], 4759 ) 4760 .as_str(), 4761 ) 4762 .to_string().into_bytes(); 4763 assert_eq!(serde_json::from_str::<Registration>( 4764 serde_json::json!({ 4765 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4766 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4767 "response": { 4768 "clientDataJSON": b64_cdata_json, 4769 "authenticatorData": b64_adata, 4770 "transports": [], 4771 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 4772 "publicKeyAlgorithm": -35i8, 4773 "attestationObject": b64_aobj, 4774 }, 4775 "clientExtensionResults": {}, 4776 "type": "public-key" 4777 }) 4778 .to_string() 4779 .as_str() 4780 ) 4781 .unwrap_err().to_string().into_bytes().get(..err.len()), 4782 Some(err.as_slice()) 4783 ); 4784 // Missing `publicKey` is allowed when not using EdDSA, ES256, or RS256. 4785 assert!( 4786 serde_json::from_str::<Registration>( 4787 serde_json::json!({ 4788 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4789 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4790 "response": { 4791 "clientDataJSON": b64_cdata_json, 4792 "authenticatorData": b64_adata, 4793 "transports": [], 4794 "publicKeyAlgorithm": -35i8, 4795 "attestationObject": b64_aobj, 4796 }, 4797 "clientExtensionResults": {}, 4798 "type": "public-key" 4799 }) 4800 .to_string() 4801 .as_str() 4802 ) 4803 .is_ok() 4804 ); 4805 // `publicKeyAlgorithm` mismatch when `publicKey` does not exist. 4806 err = Error::invalid_value( 4807 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 4808 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 4809 ) 4810 .to_string().into_bytes(); 4811 assert_eq!( 4812 serde_json::from_str::<Registration>( 4813 serde_json::json!({ 4814 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4815 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4816 "response": { 4817 "clientDataJSON": b64_cdata_json, 4818 "authenticatorData": b64_adata, 4819 "transports": [], 4820 "publicKeyAlgorithm": -7i8, 4821 "attestationObject": b64_aobj, 4822 }, 4823 "clientExtensionResults": {}, 4824 "type": "public-key" 4825 }) 4826 .to_string() 4827 .as_str() 4828 ) 4829 .unwrap_err() 4830 .to_string() 4831 .into_bytes() 4832 .get(..err.len()), 4833 Some(err.as_slice()) 4834 ); 4835 // `null` `publicKey` is allowed when not using EdDSA, ES256, or RS256. 4836 assert!( 4837 serde_json::from_str::<Registration>( 4838 serde_json::json!({ 4839 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4840 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4841 "response": { 4842 "clientDataJSON": b64_cdata_json, 4843 "authenticatorData": b64_adata, 4844 "transports": [], 4845 "publicKey": null, 4846 "publicKeyAlgorithm": -35i8, 4847 "attestationObject": b64_aobj, 4848 }, 4849 "clientExtensionResults": {}, 4850 "type": "public-key" 4851 }) 4852 .to_string() 4853 .as_str() 4854 ) 4855 .is_ok() 4856 ); 4857 // `publicKeyAlgorithm` mismatch when `publicKey` is null. 4858 err = Error::invalid_value( 4859 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Es256).as_str()), 4860 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Es384).as_str() 4861 ) 4862 .to_string().into_bytes(); 4863 assert_eq!( 4864 serde_json::from_str::<Registration>( 4865 serde_json::json!({ 4866 "id": "AAAAAAAAAAAAAAAAAAAAAA", 4867 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 4868 "response": { 4869 "clientDataJSON": b64_cdata_json, 4870 "authenticatorData": b64_adata, 4871 "transports": [], 4872 "publicKey": null, 4873 "publicKeyAlgorithm": -7i8, 4874 "attestationObject": b64_aobj, 4875 }, 4876 "clientExtensionResults": {}, 4877 "type": "public-key" 4878 }) 4879 .to_string() 4880 .as_str() 4881 ) 4882 .unwrap_err() 4883 .to_string() 4884 .into_bytes() 4885 .get(..err.len()), 4886 Some(err.as_slice()) 4887 ); 4888 } 4889 #[expect(clippy::unwrap_used, reason = "OK in tests")] 4890 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 4891 #[expect(clippy::too_many_lines, reason = "a lot to test")] 4892 #[test] 4893 fn rs256_registration_deserialize_data_mismatch() { 4894 let c_data_json = serde_json::json!({}).to_string(); 4895 let mut att_obj: [u8; 374] = [ 4896 cbor::MAP_3, 4897 cbor::TEXT_3, 4898 b'f', 4899 b'm', 4900 b't', 4901 cbor::TEXT_4, 4902 b'n', 4903 b'o', 4904 b'n', 4905 b'e', 4906 cbor::TEXT_7, 4907 b'a', 4908 b't', 4909 b't', 4910 b'S', 4911 b't', 4912 b'm', 4913 b't', 4914 cbor::MAP_0, 4915 cbor::TEXT_8, 4916 b'a', 4917 b'u', 4918 b't', 4919 b'h', 4920 b'D', 4921 b'a', 4922 b't', 4923 b'a', 4924 cbor::BYTES_INFO_25, 4925 1, 4926 87, 4927 // `rpIdHash`. 4928 0, 4929 0, 4930 0, 4931 0, 4932 0, 4933 0, 4934 0, 4935 0, 4936 0, 4937 0, 4938 0, 4939 0, 4940 0, 4941 0, 4942 0, 4943 0, 4944 0, 4945 0, 4946 0, 4947 0, 4948 0, 4949 0, 4950 0, 4951 0, 4952 0, 4953 0, 4954 0, 4955 0, 4956 0, 4957 0, 4958 0, 4959 0, 4960 // `flags`. 4961 0b0100_0101, 4962 // `signCount`. 4963 0, 4964 0, 4965 0, 4966 0, 4967 // `aaguid`. 4968 0, 4969 0, 4970 0, 4971 0, 4972 0, 4973 0, 4974 0, 4975 0, 4976 0, 4977 0, 4978 0, 4979 0, 4980 0, 4981 0, 4982 0, 4983 0, 4984 // `credentialIdLength`. 4985 0, 4986 16, 4987 // `credentialId`. 4988 0, 4989 0, 4990 0, 4991 0, 4992 0, 4993 0, 4994 0, 4995 0, 4996 0, 4997 0, 4998 0, 4999 0, 5000 0, 5001 0, 5002 0, 5003 0, 5004 // RSA COSE key. 5005 cbor::MAP_4, 5006 KTY, 5007 RSA, 5008 ALG, 5009 cbor::NEG_INFO_25, 5010 // RS256. 5011 1, 5012 0, 5013 // `n`. 5014 cbor::NEG_ONE, 5015 cbor::BYTES_INFO_25, 5016 1, 5017 0, 5018 // n. This will be overwritten later. 5019 0, 5020 0, 5021 0, 5022 0, 5023 0, 5024 0, 5025 0, 5026 0, 5027 0, 5028 0, 5029 0, 5030 0, 5031 0, 5032 0, 5033 0, 5034 0, 5035 0, 5036 0, 5037 0, 5038 0, 5039 0, 5040 0, 5041 0, 5042 0, 5043 0, 5044 0, 5045 0, 5046 0, 5047 0, 5048 0, 5049 0, 5050 0, 5051 0, 5052 0, 5053 0, 5054 0, 5055 0, 5056 0, 5057 0, 5058 0, 5059 0, 5060 0, 5061 0, 5062 0, 5063 0, 5064 0, 5065 0, 5066 0, 5067 0, 5068 0, 5069 0, 5070 0, 5071 0, 5072 0, 5073 0, 5074 0, 5075 0, 5076 0, 5077 0, 5078 0, 5079 0, 5080 0, 5081 0, 5082 0, 5083 0, 5084 0, 5085 0, 5086 0, 5087 0, 5088 0, 5089 0, 5090 0, 5091 0, 5092 0, 5093 0, 5094 0, 5095 0, 5096 0, 5097 0, 5098 0, 5099 0, 5100 0, 5101 0, 5102 0, 5103 0, 5104 0, 5105 0, 5106 0, 5107 0, 5108 0, 5109 0, 5110 0, 5111 0, 5112 0, 5113 0, 5114 0, 5115 0, 5116 0, 5117 0, 5118 0, 5119 0, 5120 0, 5121 0, 5122 0, 5123 0, 5124 0, 5125 0, 5126 0, 5127 0, 5128 0, 5129 0, 5130 0, 5131 0, 5132 0, 5133 0, 5134 0, 5135 0, 5136 0, 5137 0, 5138 0, 5139 0, 5140 0, 5141 0, 5142 0, 5143 0, 5144 0, 5145 0, 5146 0, 5147 0, 5148 0, 5149 0, 5150 0, 5151 0, 5152 0, 5153 0, 5154 0, 5155 0, 5156 0, 5157 0, 5158 0, 5159 0, 5160 0, 5161 0, 5162 0, 5163 0, 5164 0, 5165 0, 5166 0, 5167 0, 5168 0, 5169 0, 5170 0, 5171 0, 5172 0, 5173 0, 5174 0, 5175 0, 5176 0, 5177 0, 5178 0, 5179 0, 5180 0, 5181 0, 5182 0, 5183 0, 5184 0, 5185 0, 5186 0, 5187 0, 5188 0, 5189 0, 5190 0, 5191 0, 5192 0, 5193 0, 5194 0, 5195 0, 5196 0, 5197 0, 5198 0, 5199 0, 5200 0, 5201 0, 5202 0, 5203 0, 5204 0, 5205 0, 5206 0, 5207 0, 5208 0, 5209 0, 5210 0, 5211 0, 5212 0, 5213 0, 5214 0, 5215 0, 5216 0, 5217 0, 5218 0, 5219 0, 5220 0, 5221 0, 5222 0, 5223 0, 5224 0, 5225 0, 5226 0, 5227 0, 5228 0, 5229 0, 5230 0, 5231 0, 5232 0, 5233 0, 5234 0, 5235 0, 5236 0, 5237 0, 5238 0, 5239 0, 5240 0, 5241 0, 5242 0, 5243 0, 5244 0, 5245 0, 5246 0, 5247 0, 5248 0, 5249 0, 5250 0, 5251 0, 5252 0, 5253 0, 5254 0, 5255 0, 5256 0, 5257 0, 5258 0, 5259 0, 5260 0, 5261 0, 5262 0, 5263 0, 5264 0, 5265 0, 5266 0, 5267 0, 5268 0, 5269 0, 5270 0, 5271 0, 5272 0, 5273 0, 5274 0, 5275 // `e`. 5276 cbor::NEG_TWO, 5277 cbor::BYTES | 3, 5278 // e. 5279 1, 5280 0, 5281 1, 5282 ]; 5283 let n = [ 5284 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 5285 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 5286 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 5287 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 5288 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 5289 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 5290 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 5291 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 5292 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 5293 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 5294 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 5295 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 5296 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 5297 153, 79, 0, 133, 78, 7, 218, 165, 241, 5298 ]; 5299 let e = 0x0001_0001u32; 5300 let d = [ 5301 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 5302 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 5303 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 5304 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 5305 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 5306 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 5307 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 5308 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 5309 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 5310 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 5311 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 5312 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 5313 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 5314 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 5315 ]; 5316 let p = BigUint::from_bytes_le( 5317 [ 5318 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60, 5319 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229, 5320 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161, 5321 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16, 5322 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79, 5323 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191, 5324 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188, 5325 24, 247, 5326 ] 5327 .as_slice(), 5328 ); 5329 let p_2 = BigUint::from_bytes_le( 5330 [ 5331 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78, 5332 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177, 5333 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29, 5334 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11, 5335 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252, 5336 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214, 5337 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90, 5338 250, 5339 ] 5340 .as_slice(), 5341 ); 5342 let key = RsaPrivateKey::from_components( 5343 BigUint::from_bytes_le(n.as_slice()), 5344 e.into(), 5345 BigUint::from_bytes_le(d.as_slice()), 5346 vec![p, p_2], 5347 ) 5348 .unwrap() 5349 .to_public_key(); 5350 let pub_key = key.to_public_key_der().unwrap(); 5351 let att_obj_len = att_obj.len(); 5352 let n_start_idx = att_obj_len - 261; 5353 let e_meta_start_idx = n_start_idx + 256; 5354 // Correct and won't `panic`. 5355 att_obj[n_start_idx..e_meta_start_idx].copy_from_slice(key.n().to_bytes_be().as_slice()); 5356 let b64_cdata_json = base64url_nopad::encode(c_data_json.as_bytes()); 5357 // Won't `panic`. 5358 let b64_adata = base64url_nopad::encode(&att_obj[31..]); 5359 let b64_key = base64url_nopad::encode(pub_key.as_bytes()); 5360 let b64_aobj = base64url_nopad::encode(att_obj.as_slice()); 5361 // Base case is valid. 5362 assert!( 5363 serde_json::from_str::<Registration>( 5364 serde_json::json!({ 5365 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5366 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5367 "response": { 5368 "clientDataJSON": b64_cdata_json, 5369 "authenticatorData": b64_adata, 5370 "transports": [], 5371 "publicKey": b64_key, 5372 "publicKeyAlgorithm": -257i16, 5373 "attestationObject": b64_aobj, 5374 }, 5375 "clientExtensionResults": {}, 5376 "type": "public-key" 5377 }) 5378 .to_string() 5379 .as_str() 5380 ) 5381 .is_ok_and( 5382 |reg| reg.response.client_data_json == c_data_json.as_bytes() 5383 && reg.response.attestation_object_and_c_data_hash[..att_obj_len] == att_obj 5384 && reg.response.attestation_object_and_c_data_hash[att_obj_len..] 5385 == *Sha256::digest(c_data_json.as_bytes()) 5386 && reg.response.transports.is_empty() 5387 && matches!(reg.authenticator_attachment, AuthenticatorAttachment::None) 5388 && reg.client_extension_results.cred_props.is_none() 5389 && reg.client_extension_results.prf.is_none() 5390 ) 5391 ); 5392 // `publicKeyAlgorithm` mismatch. 5393 let mut err = Error::invalid_value( 5394 Unexpected::Other(format!("{:?}", CoseAlgorithmIdentifier::Eddsa).as_str()), 5395 &format!("public key algorithm to match the algorithm associated with the public key within the attestation object: {:?}", CoseAlgorithmIdentifier::Rs256).as_str() 5396 ) 5397 .to_string().into_bytes(); 5398 assert_eq!( 5399 serde_json::from_str::<Registration>( 5400 serde_json::json!({ 5401 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5402 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5403 "response": { 5404 "clientDataJSON": b64_cdata_json, 5405 "authenticatorData": b64_adata, 5406 "transports": [], 5407 "publicKey": b64_key, 5408 "publicKeyAlgorithm": -8i8, 5409 "attestationObject": b64_aobj, 5410 }, 5411 "clientExtensionResults": {}, 5412 "type": "public-key" 5413 }) 5414 .to_string() 5415 .as_str() 5416 ) 5417 .unwrap_err() 5418 .to_string() 5419 .into_bytes() 5420 .get(..err.len()), 5421 Some(err.as_slice()) 5422 ); 5423 // Missing `publicKeyAlgorithm`. 5424 err = Error::missing_field("publicKeyAlgorithm") 5425 .to_string() 5426 .into_bytes(); 5427 assert_eq!( 5428 serde_json::from_str::<Registration>( 5429 serde_json::json!({ 5430 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5431 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5432 "response": { 5433 "clientDataJSON": b64_cdata_json, 5434 "authenticatorData": b64_adata, 5435 "transports": [], 5436 "publicKey": b64_key, 5437 "attestationObject": b64_aobj, 5438 }, 5439 "clientExtensionResults": {}, 5440 "type": "public-key" 5441 }) 5442 .to_string() 5443 .as_str() 5444 ) 5445 .unwrap_err() 5446 .to_string() 5447 .into_bytes() 5448 .get(..err.len()), 5449 Some(err.as_slice()) 5450 ); 5451 // `null` `publicKeyAlgorithm`. 5452 err = Error::invalid_type(Unexpected::Other("null"), &"publicKeyAlgorithm") 5453 .to_string() 5454 .into_bytes(); 5455 assert_eq!( 5456 serde_json::from_str::<Registration>( 5457 serde_json::json!({ 5458 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5459 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5460 "response": { 5461 "clientDataJSON": b64_cdata_json, 5462 "authenticatorData": b64_adata, 5463 "transports": [], 5464 "publicKey": b64_key, 5465 "publicKeyAlgorithm": null, 5466 "attestationObject": b64_aobj, 5467 }, 5468 "clientExtensionResults": {}, 5469 "type": "public-key" 5470 }) 5471 .to_string() 5472 .as_str() 5473 ) 5474 .unwrap_err() 5475 .to_string() 5476 .into_bytes() 5477 .get(..err.len()), 5478 Some(err.as_slice()) 5479 ); 5480 // `publicKey` mismatch. 5481 let bad_pub_key = RsaPrivateKey::from_components( 5482 BigUint::from_bytes_le( 5483 [ 5484 175, 161, 161, 75, 52, 244, 72, 168, 29, 119, 33, 120, 3, 222, 231, 152, 222, 5485 119, 112, 83, 221, 237, 74, 174, 79, 216, 147, 251, 245, 94, 234, 114, 254, 21, 5486 17, 254, 8, 115, 75, 127, 150, 87, 59, 109, 230, 116, 85, 90, 11, 160, 63, 217, 5487 9, 38, 187, 250, 226, 183, 38, 164, 182, 218, 22, 19, 58, 189, 83, 219, 11, 5488 144, 15, 99, 151, 166, 46, 57, 17, 111, 189, 131, 142, 113, 85, 122, 188, 238, 5489 52, 21, 116, 125, 102, 195, 182, 165, 29, 156, 213, 182, 125, 156, 88, 56, 221, 5490 2, 98, 43, 210, 115, 32, 4, 105, 88, 181, 158, 207, 236, 162, 250, 253, 240, 5491 72, 8, 253, 50, 220, 247, 76, 170, 143, 68, 225, 231, 113, 64, 244, 17, 138, 5492 162, 233, 33, 2, 67, 11, 223, 188, 232, 152, 193, 20, 32, 243, 52, 64, 43, 2, 5493 243, 8, 77, 150, 232, 109, 148, 95, 127, 55, 71, 162, 34, 54, 83, 135, 52, 172, 5494 191, 32, 42, 106, 43, 211, 206, 100, 104, 110, 232, 5, 43, 120, 180, 166, 40, 5495 144, 233, 239, 103, 134, 103, 255, 224, 138, 184, 208, 137, 127, 36, 189, 143, 5496 248, 201, 2, 218, 51, 232, 96, 30, 83, 124, 109, 241, 23, 179, 247, 151, 238, 5497 212, 204, 44, 43, 223, 148, 241, 172, 10, 235, 155, 94, 68, 116, 24, 116, 191, 5498 86, 53, 127, 35, 133, 198, 204, 59, 76, 110, 16, 1, 15, 148, 135, 157, 5499 ] 5500 .as_slice(), 5501 ), 5502 0x0001_0001u32.into(), 5503 BigUint::from_bytes_le( 5504 [ 5505 129, 93, 123, 251, 104, 29, 84, 203, 116, 100, 75, 237, 111, 160, 12, 100, 172, 5506 76, 57, 178, 144, 235, 81, 61, 115, 243, 28, 40, 183, 22, 56, 150, 68, 38, 220, 5507 62, 233, 110, 48, 174, 35, 197, 244, 109, 148, 109, 36, 69, 69, 82, 225, 113, 5508 175, 6, 239, 27, 193, 101, 50, 239, 122, 102, 7, 46, 98, 79, 195, 116, 155, 5509 158, 138, 147, 51, 93, 24, 237, 246, 82, 14, 109, 144, 250, 239, 93, 63, 214, 5510 96, 130, 226, 134, 198, 145, 161, 11, 231, 97, 214, 180, 255, 95, 158, 88, 108, 5511 254, 243, 177, 133, 184, 92, 95, 148, 88, 55, 124, 245, 244, 84, 86, 4, 121, 5512 44, 231, 97, 176, 190, 29, 155, 40, 57, 69, 165, 80, 168, 9, 56, 43, 233, 6, 5513 14, 157, 112, 223, 64, 88, 141, 7, 65, 23, 64, 208, 6, 83, 61, 8, 182, 248, 5514 126, 84, 179, 163, 80, 238, 90, 133, 4, 14, 71, 177, 175, 27, 29, 151, 211, 5515 108, 162, 195, 7, 157, 167, 86, 169, 3, 87, 235, 89, 158, 237, 216, 31, 243, 5516 197, 62, 5, 84, 131, 230, 186, 248, 49, 12, 93, 244, 61, 135, 180, 17, 162, 5517 241, 13, 115, 241, 138, 219, 98, 155, 166, 191, 63, 12, 37, 1, 165, 178, 84, 5518 200, 72, 80, 41, 77, 136, 217, 141, 246, 209, 31, 243, 159, 71, 43, 246, 159, 5519 182, 171, 116, 12, 3, 142, 235, 218, 164, 70, 90, 147, 238, 42, 75, 5520 ] 5521 .as_slice(), 5522 ), 5523 vec![ 5524 BigUint::from_bytes_le( 5525 [ 5526 215, 199, 110, 28, 64, 16, 16, 109, 106, 152, 150, 124, 52, 166, 121, 92, 5527 242, 13, 0, 69, 7, 152, 72, 172, 118, 63, 156, 180, 140, 39, 53, 29, 197, 5528 224, 177, 48, 41, 221, 102, 65, 17, 185, 55, 62, 219, 152, 227, 7, 78, 219, 5529 14, 139, 71, 204, 144, 152, 14, 39, 247, 244, 165, 224, 234, 60, 213, 74, 5530 237, 30, 102, 177, 242, 138, 168, 31, 122, 47, 206, 155, 225, 113, 103, 5531 175, 152, 244, 27, 233, 112, 223, 248, 38, 215, 178, 20, 244, 8, 121, 26, 5532 11, 70, 122, 16, 85, 167, 87, 64, 216, 228, 227, 173, 57, 250, 8, 221, 38, 5533 12, 203, 212, 1, 112, 43, 72, 91, 225, 97, 228, 57, 154, 193, 5534 ] 5535 .as_slice(), 5536 ), 5537 BigUint::from_bytes_le( 5538 [ 5539 233, 89, 204, 152, 31, 242, 8, 110, 38, 190, 111, 159, 105, 105, 45, 85, 5540 15, 244, 30, 250, 174, 226, 219, 111, 107, 191, 196, 135, 17, 123, 186, 5541 167, 85, 13, 120, 197, 159, 129, 78, 237, 152, 31, 230, 26, 229, 253, 197, 5542 211, 105, 204, 126, 142, 250, 55, 26, 172, 65, 160, 45, 6, 99, 86, 66, 238, 5543 107, 6, 98, 171, 93, 224, 201, 160, 31, 204, 82, 120, 228, 158, 238, 6, 5544 190, 12, 150, 153, 239, 95, 57, 71, 100, 239, 235, 155, 73, 200, 5, 225, 5545 127, 185, 46, 48, 243, 84, 33, 142, 17, 19, 20, 23, 215, 16, 114, 58, 211, 5546 14, 73, 148, 168, 252, 159, 252, 125, 57, 101, 211, 188, 12, 77, 208, 5547 ] 5548 .as_slice(), 5549 ), 5550 ], 5551 ) 5552 .unwrap() 5553 .to_public_key(); 5554 err = Error::invalid_value( 5555 Unexpected::Bytes([0; 32].as_slice()), 5556 &format!( 5557 "DER-encoded public key to match the public key within the attestation object: Rsa(RsaPubKey({:?}, 65537))", 5558 // Correct and won't `panic`. 5559 &att_obj[n_start_idx..e_meta_start_idx], 5560 ) 5561 .as_str(), 5562 ) 5563 .to_string().into_bytes(); 5564 assert_eq!(serde_json::from_str::<Registration>( 5565 serde_json::json!({ 5566 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5567 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5568 "response": { 5569 "clientDataJSON": b64_cdata_json, 5570 "authenticatorData": b64_adata, 5571 "transports": [], 5572 "publicKey": base64url_nopad::encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()), 5573 "publicKeyAlgorithm": -257i16, 5574 "attestationObject": b64_aobj, 5575 }, 5576 "clientExtensionResults": {}, 5577 "type": "public-key" 5578 }) 5579 .to_string() 5580 .as_str() 5581 ) 5582 .unwrap_err().to_string().into_bytes().get(..err.len()), 5583 Some(err.as_slice()) 5584 ); 5585 // Missing `publicKey` when using EdDSA, ES256, or RS256. 5586 err = Error::missing_field("publicKey").to_string().into_bytes(); 5587 assert_eq!( 5588 serde_json::from_str::<Registration>( 5589 serde_json::json!({ 5590 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5591 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5592 "response": { 5593 "clientDataJSON": b64_cdata_json, 5594 "authenticatorData": b64_adata, 5595 "transports": [], 5596 "publicKeyAlgorithm": -257i16, 5597 "attestationObject": b64_aobj, 5598 }, 5599 "clientExtensionResults": {}, 5600 "type": "public-key" 5601 }) 5602 .to_string() 5603 .as_str() 5604 ) 5605 .unwrap_err() 5606 .to_string() 5607 .into_bytes() 5608 .get(..err.len()), 5609 Some(err.as_slice()) 5610 ); 5611 // `null` `publicKey` when using EdDSA, ES256, or RS256. 5612 err = Error::invalid_type(Unexpected::Other("null"), &"publicKey") 5613 .to_string() 5614 .into_bytes(); 5615 assert_eq!( 5616 serde_json::from_str::<Registration>( 5617 serde_json::json!({ 5618 "id": "AAAAAAAAAAAAAAAAAAAAAA", 5619 "rawId": "AAAAAAAAAAAAAAAAAAAAAA", 5620 "response": { 5621 "clientDataJSON": b64_cdata_json, 5622 "authenticatorData": b64_adata, 5623 "transports": [], 5624 "publicKey": null, 5625 "publicKeyAlgorithm": -257i16, 5626 "attestationObject": b64_aobj, 5627 }, 5628 "clientExtensionResults": {}, 5629 "type": "public-key" 5630 }) 5631 .to_string() 5632 .as_str() 5633 ) 5634 .unwrap_err() 5635 .to_string() 5636 .into_bytes() 5637 .get(..err.len()), 5638 Some(err.as_slice()) 5639 ); 5640 } 5641 }