request.rs (276253B)
1 #[cfg(doc)] 2 use super::{ 3 hash::hash_set::MaxLenHashSet, 4 request::{ 5 auth::{ 6 AllowedCredential, AllowedCredentials, CredentialSpecificExtension, 7 DiscoverableAuthenticationServerState, DiscoverableCredentialRequestOptions, 8 NonDiscoverableAuthenticationServerState, NonDiscoverableCredentialRequestOptions, 9 PublicKeyCredentialRequestOptions, 10 }, 11 register::{CredentialCreationOptions, RegistrationServerState}, 12 }, 13 response::register::ClientExtensionsOutputs, 14 }; 15 use crate::{ 16 request::{ 17 error::{ 18 AsciiDomainErr, DomainOriginParseErr, PortParseErr, RpIdErr, SchemeParseErr, UrlErr, 19 }, 20 register::RegistrationVerificationOptions, 21 }, 22 response::{ 23 AuthData as _, AuthDataContainer, AuthResponse, AuthTransports, Backup, CeremonyErr, 24 CredentialId, Origin, Response, SentChallenge, 25 }, 26 }; 27 use core::{ 28 borrow::Borrow, 29 fmt::{self, Display, Formatter}, 30 num::NonZeroU32, 31 str::FromStr, 32 }; 33 use rsa::sha2::{Digest as _, Sha256}; 34 #[cfg(any(doc, not(feature = "serializable_server_state")))] 35 use std::time::Instant; 36 #[cfg(feature = "serializable_server_state")] 37 use std::time::SystemTime; 38 use url::Url as Uri; 39 /// Contains functionality for beginning the 40 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony). 41 /// 42 /// # Examples 43 /// 44 /// ``` 45 /// # use core::convert; 46 /// # use webauthn_rp::{ 47 /// # hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, 48 /// # request::{ 49 /// # auth::{AllowedCredentials, DiscoverableCredentialRequestOptions, NonDiscoverableCredentialRequestOptions}, 50 /// # register::UserHandle64, 51 /// # Credentials, PublicKeyCredentialDescriptor, RpId, 52 /// # }, 53 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 54 /// # AggErr, 55 /// # }; 56 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap(); 57 /// let mut ceremonies = MaxLenHashSet::new(128); 58 /// let (server, client) = DiscoverableCredentialRequestOptions::passkey(RP_ID).start_ceremony()?; 59 /// assert_eq!(ceremonies.insert_remove_all_expired(server), InsertRemoveExpired::Success); 60 /// # #[cfg(feature = "custom")] 61 /// let mut ceremonies_2 = MaxLenHashSet::new(128); 62 /// # #[cfg(feature = "serde")] 63 /// assert!(serde_json::to_string(&client).is_ok()); 64 /// let user_handle = get_user_handle(); 65 /// # #[cfg(feature = "custom")] 66 /// let creds = get_registered_credentials(&user_handle)?; 67 /// # #[cfg(feature = "custom")] 68 /// let (server_2, client_2) = 69 /// NonDiscoverableCredentialRequestOptions::second_factor(RP_ID, creds).start_ceremony()?; 70 /// # #[cfg(feature = "custom")] 71 /// assert_eq!(ceremonies_2.insert_remove_all_expired(server_2), InsertRemoveExpired::Success); 72 /// # #[cfg(all(feature = "custom", feature = "serde"))] 73 /// assert!(serde_json::to_string(&client_2).is_ok()); 74 /// /// Extract `UserHandle` from session cookie. 75 /// fn get_user_handle() -> UserHandle64 { 76 /// // ⋮ 77 /// # UserHandle64::new() 78 /// } 79 /// # #[cfg(feature = "custom")] 80 /// /// Fetch the `AllowedCredentials` associated with `user`. 81 /// fn get_registered_credentials(user: &UserHandle64) -> Result<AllowedCredentials, AggErr> { 82 /// // ⋮ 83 /// # let mut creds = AllowedCredentials::new(); 84 /// # creds.push( 85 /// # PublicKeyCredentialDescriptor { 86 /// # id: CredentialId::try_from(vec![0; CRED_ID_MIN_LEN].into_boxed_slice())?, 87 /// # transports: AuthTransports::NONE, 88 /// # } 89 /// # .into(), 90 /// # ); 91 /// # Ok(creds) 92 /// } 93 /// # Ok::<_, AggErr>(()) 94 /// ``` 95 pub mod auth; 96 /// Contains error types. 97 pub mod error; 98 /// Contains functionality for beginning the 99 /// [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony). 100 /// 101 /// # Examples 102 /// 103 /// ``` 104 /// # use core::convert; 105 /// # use webauthn_rp::{ 106 /// # hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, 107 /// # request::{ 108 /// # register::{ 109 /// # CredentialCreationOptions, DisplayName, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64, 110 /// # }, 111 /// # PublicKeyCredentialDescriptor, RpId 112 /// # }, 113 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 114 /// # AggErr, 115 /// # }; 116 /// const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap(); 117 /// # #[cfg(feature = "custom")] 118 /// let mut ceremonies = MaxLenHashSet::new(128); 119 /// # #[cfg(feature = "custom")] 120 /// let user_handle = get_user_handle(); 121 /// # #[cfg(feature = "custom")] 122 /// let user = get_user_entity(&user_handle)?; 123 /// # #[cfg(feature = "custom")] 124 /// let creds = get_registered_credentials(&user_handle)?; 125 /// # #[cfg(feature = "custom")] 126 /// let (server, client) = CredentialCreationOptions::passkey(RP_ID, user.clone(), creds) 127 /// .start_ceremony()?; 128 /// # #[cfg(feature = "custom")] 129 /// assert_eq!(ceremonies.insert_remove_all_expired(server), InsertRemoveExpired::Success); 130 /// # #[cfg(all(feature = "serde", feature = "custom"))] 131 /// assert!(serde_json::to_string(&client).is_ok()); 132 /// # #[cfg(feature = "custom")] 133 /// let creds_2 = get_registered_credentials(&user_handle)?; 134 /// # #[cfg(feature = "custom")] 135 /// let (server_2, client_2) = 136 /// CredentialCreationOptions::second_factor(RP_ID, user, creds_2).start_ceremony()?; 137 /// # #[cfg(feature = "custom")] 138 /// assert_eq!(ceremonies.insert_remove_all_expired(server_2), InsertRemoveExpired::Success); 139 /// # #[cfg(all(feature = "serde", feature = "custom"))] 140 /// assert!(serde_json::to_string(&client_2).is_ok()); 141 /// /// Extract `UserHandle` from session cookie or storage if this is not the first credential registered. 142 /// # #[cfg(feature = "custom")] 143 /// fn get_user_handle() -> UserHandle64 { 144 /// // ⋮ 145 /// # [0; USER_HANDLE_MAX_LEN].into() 146 /// } 147 /// /// Fetch `PublicKeyCredentialUserEntity` info associated with `user`. 148 /// /// 149 /// /// If this is the first time a credential is being registered, then `PublicKeyCredentialUserEntity` 150 /// /// will need to be constructed with `name` and `display_name` passed from the client and `UserHandle::new` 151 /// /// used for `id`. Once created, this info can be stored such that the entity information 152 /// /// does not need to be requested for subsequent registrations. 153 /// # #[cfg(feature = "custom")] 154 /// fn get_user_entity(user: &UserHandle64) -> Result<PublicKeyCredentialUserEntity<'_, '_, '_, USER_HANDLE_MAX_LEN>, AggErr> { 155 /// // ⋮ 156 /// # Ok(PublicKeyCredentialUserEntity { 157 /// # name: "foo".try_into()?, 158 /// # id: user, 159 /// # display_name: DisplayName::Blank, 160 /// # }) 161 /// } 162 /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`. 163 /// /// 164 /// /// This doesn't need to be called when this is the first credential registered for `user`; instead 165 /// /// an empty `Vec` should be passed. 166 /// fn get_registered_credentials( 167 /// user: &UserHandle64, 168 /// ) -> Result<Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, AggErr> { 169 /// // ⋮ 170 /// # Ok(Vec::new()) 171 /// } 172 /// # Ok::<_, AggErr>(()) 173 /// ``` 174 pub mod register; 175 /// Contains functionality to serialize data to a client. 176 #[cfg(feature = "serde")] 177 mod ser; 178 /// Contains functionality to (de)serialize data needed for [`RegistrationServerState`], 179 /// [`DiscoverableAuthenticationServerState`], and [`NonDiscoverableAuthenticationServerState`] to a data store. 180 #[cfg(feature = "serializable_server_state")] 181 pub(super) mod ser_server_state; 182 // `Challenge` must _never_ be constructable directly or indirectly; thus its tuple field must always be private, 183 // and it must never implement `trait`s (e.g., `Clone`) that would allow indirect creation. It must only ever 184 // be constructed via `Self::new` or `Self::default`. In contrast downstream code must be able to construct 185 // `SentChallenge` since it is used during ceremony validation; thus we must keep `Challenge` and `SentChallenge` 186 // as separate types. 187 /// [Cryptographic challenge](https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges). 188 #[expect( 189 missing_copy_implementations, 190 reason = "want to enforce randomly-generated challenges" 191 )] 192 #[derive(Debug)] 193 pub struct Challenge(u128); 194 impl Challenge { 195 /// The number of bytes a `Challenge` takes to encode in base64url. 196 pub(super) const BASE64_LEN: usize = base64url_nopad::encode_len(16); 197 /// Generates a random `Challenge`. 198 /// 199 /// # Examples 200 /// 201 /// ``` 202 /// # use webauthn_rp::request::Challenge; 203 /// // The probability of a `Challenge` being 0 (assuming a good entropy 204 /// // source) is 2^-128 ≈ 2.9 x 10^-39. 205 /// assert_ne!(Challenge::new().into_data(), 0); 206 /// ``` 207 #[inline] 208 #[must_use] 209 pub fn new() -> Self { 210 Self(rand::random()) 211 } 212 /// Returns the contained `u128` consuming `self`. 213 #[inline] 214 #[must_use] 215 pub const fn into_data(self) -> u128 { 216 self.0 217 } 218 /// Returns the contained `u128`. 219 #[inline] 220 #[must_use] 221 pub const fn as_data(&self) -> u128 { 222 self.0 223 } 224 /// Returns the contained `u128` as a little-endian `array` consuming `self`. 225 #[inline] 226 #[must_use] 227 pub const fn into_array(self) -> [u8; 16] { 228 self.as_array() 229 } 230 /// Returns the contained `u128` as a little-endian `array`. 231 #[expect( 232 clippy::little_endian_bytes, 233 reason = "Challenge and SentChallenge need to be compatible, and we need to ensure the data is sent and received in the same order" 234 )] 235 #[inline] 236 #[must_use] 237 pub const fn as_array(&self) -> [u8; 16] { 238 self.0.to_le_bytes() 239 } 240 } 241 impl Default for Challenge { 242 /// Same as [`Self::new`]. 243 #[inline] 244 fn default() -> Self { 245 Self::new() 246 } 247 } 248 impl From<Challenge> for u128 { 249 #[inline] 250 fn from(value: Challenge) -> Self { 251 value.0 252 } 253 } 254 impl From<&Challenge> for u128 { 255 #[inline] 256 fn from(value: &Challenge) -> Self { 257 value.0 258 } 259 } 260 impl From<Challenge> for [u8; 16] { 261 #[inline] 262 fn from(value: Challenge) -> Self { 263 value.into_array() 264 } 265 } 266 impl From<&Challenge> for [u8; 16] { 267 #[inline] 268 fn from(value: &Challenge) -> Self { 269 value.as_array() 270 } 271 } 272 /// A [domain](https://url.spec.whatwg.org/#concept-domain) in representation format consisting of only and any 273 /// ASCII. 274 /// 275 /// The only ASCII character disallowed in a label is `'.'` since it is used exclusively as a separator. Every 276 /// label must have length inclusively between 1 and 63, and the total length of the domain must be at most 253 277 /// when a trailing `'.'` does not exist; otherwise the max length is 254. The root domain (i.e., `'.'`) is not 278 /// allowed. 279 /// 280 /// Note if the domain is a `&'static str`, then use [`AsciiDomainStatic`] instead. 281 #[derive(Clone, Debug, Eq, PartialEq)] 282 pub struct AsciiDomain(String); 283 impl AsciiDomain { 284 /// Removes a trailing `'.'` if it exists. 285 /// 286 /// # Examples 287 /// 288 /// ``` 289 /// # use webauthn_rp::request::{AsciiDomain, error::AsciiDomainErr}; 290 /// let mut dom = AsciiDomain::try_from("example.com.".to_owned())?; 291 /// assert_eq!(dom.as_ref(), "example.com."); 292 /// dom.remove_trailing_dot(); 293 /// assert_eq!(dom.as_ref(), "example.com"); 294 /// dom.remove_trailing_dot(); 295 /// assert_eq!(dom.as_ref(), "example.com"); 296 /// # Ok::<_, AsciiDomainErr>(()) 297 /// ``` 298 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 299 #[inline] 300 pub fn remove_trailing_dot(&mut self) { 301 if *self 302 .0 303 .as_bytes() 304 .last() 305 .unwrap_or_else(|| unreachable!("there is a bug in AsciiDomain::from_slice")) 306 == b'.' 307 { 308 _ = self.0.pop(); 309 } 310 } 311 } 312 impl AsRef<str> for AsciiDomain { 313 #[inline] 314 fn as_ref(&self) -> &str { 315 self.0.as_str() 316 } 317 } 318 impl Borrow<str> for AsciiDomain { 319 #[inline] 320 fn borrow(&self) -> &str { 321 self.0.as_str() 322 } 323 } 324 impl From<AsciiDomain> for String { 325 #[inline] 326 fn from(value: AsciiDomain) -> Self { 327 value.0 328 } 329 } 330 impl PartialEq<&Self> for AsciiDomain { 331 #[inline] 332 fn eq(&self, other: &&Self) -> bool { 333 *self == **other 334 } 335 } 336 impl PartialEq<AsciiDomain> for &AsciiDomain { 337 #[inline] 338 fn eq(&self, other: &AsciiDomain) -> bool { 339 **self == *other 340 } 341 } 342 impl TryFrom<Vec<u8>> for AsciiDomain { 343 type Error = AsciiDomainErr; 344 /// Verifies `value` is an ASCII domain in representation format converting any uppercase ASCII into 345 /// lowercase. 346 /// 347 /// Note it is _strongly_ encouraged for `value` to only contain letters, numbers, hyphens, and underscores; 348 /// otherwise certain applications may consider it not a domain. If the original domain contains non-ASCII, then 349 /// one must encode it in Punycode _before_ calling this function. Domains that have a trailing `'.'` will be 350 /// considered differently than domains without it; thus one will likely want to trim it if it does exist 351 /// (e.g., [`AsciiDomain::remove_trailing_dot`]). Because this allows any ASCII, one may want to ensure `value` 352 /// is not an IP address. 353 /// 354 /// # Errors 355 /// 356 /// Errors iff `value` is not a valid ASCII domain. 357 /// 358 /// # Examples 359 /// 360 /// ``` 361 /// # use webauthn_rp::request::{error::AsciiDomainErr, AsciiDomain}; 362 /// // Root `'.'` is not removed if it exists. 363 /// assert_ne!("example.com", AsciiDomain::try_from(b"example.com.".to_vec())?.as_ref()); 364 /// // Root domain (i.e., `'.'`) is not allowed. 365 /// assert!(AsciiDomain::try_from(vec![b'.']).is_err()); 366 /// // Uppercase is transformed into lowercase. 367 /// assert_eq!("example.com", AsciiDomain::try_from(b"ExAmPle.CoM".to_vec())?.as_ref()); 368 /// // The only ASCII character not allowed in a domain label is `'.'` as it is used exclusively to delimit 369 /// // labels. 370 /// assert_eq!("\x00", AsciiDomain::try_from(b"\x00".to_vec())?.as_ref()); 371 /// // Empty labels are not allowed. 372 /// assert!(AsciiDomain::try_from(b"example..com".to_vec()).is_err()); 373 /// // Labels cannot have length greater than 63. 374 /// let mut long_label = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned(); 375 /// assert_eq!(long_label.len(), 64); 376 /// assert!(AsciiDomain::try_from(long_label.clone().into_bytes()).is_err()); 377 /// long_label.pop(); 378 /// assert_eq!(long_label, AsciiDomain::try_from(long_label.clone().into_bytes())?.as_ref()); 379 /// // The maximum length of a domain is 254 if a trailing `'.'` exists; otherwise the max length is 253. 380 /// let mut long_domain = format!("{long_label}.{long_label}.{long_label}.{long_label}"); 381 /// long_domain.pop(); 382 /// long_domain.push('.'); 383 /// assert_eq!(long_domain.len(), 255); 384 /// assert!(AsciiDomain::try_from(long_domain.clone().into_bytes()).is_err()); 385 /// long_domain.pop(); 386 /// long_domain.pop(); 387 /// long_domain.push('.'); 388 /// assert_eq!(long_domain.len(), 254); 389 /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone().into_bytes())?.as_ref()); 390 /// long_domain.pop(); 391 /// long_domain.push('a'); 392 /// assert_eq!(long_domain.len(), 254); 393 /// assert!(AsciiDomain::try_from(long_domain.clone().into_bytes()).is_err()); 394 /// long_domain.pop(); 395 /// assert_eq!(long_domain.len(), 253); 396 /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone().into_bytes())?.as_ref()); 397 /// // Only ASCII is allowed; thus if a domain needs to be Punycode-encoded, then it must be _before_ calling 398 /// // this function. 399 /// assert!(AsciiDomain::try_from("λ.com".to_owned().into_bytes()).is_err()); 400 /// assert_eq!("xn--wxa.com", AsciiDomain::try_from(b"xn--wxa.com".to_vec())?.as_ref()); 401 /// # Ok::<_, AsciiDomainErr>(()) 402 /// ``` 403 #[expect(unsafe_code, reason = "comment justifies correctness")] 404 #[expect( 405 clippy::arithmetic_side_effects, 406 reason = "comments justify correctness" 407 )] 408 #[inline] 409 fn try_from(mut value: Vec<u8>) -> Result<Self, Self::Error> { 410 /// Value to add to an uppercase ASCII `u8` to get the lowercase version. 411 const DIFF: u8 = b'a' - b'A'; 412 let bytes = value.as_slice(); 413 bytes 414 .as_ref() 415 .last() 416 .ok_or(AsciiDomainErr::Empty) 417 .and_then(|b| { 418 let len = bytes.len(); 419 if *b == b'.' { 420 if len == 1 { 421 Err(AsciiDomainErr::RootDomain) 422 } else if len > 254 { 423 Err(AsciiDomainErr::Len) 424 } else { 425 Ok(()) 426 } 427 } else if len > 253 { 428 Err(AsciiDomainErr::Len) 429 } else { 430 Ok(()) 431 } 432 }) 433 .and_then(|()| { 434 value 435 .iter_mut() 436 .try_fold(0u8, |mut label_len, byt| { 437 let b = *byt; 438 if b == b'.' { 439 if label_len == 0 { 440 Err(AsciiDomainErr::EmptyLabel) 441 } else { 442 Ok(0) 443 } 444 } else if label_len == 63 { 445 Err(AsciiDomainErr::LabelLen) 446 } else { 447 // We know `label_len` is less than 63, thus this won't overflow. 448 label_len += 1; 449 match *byt { 450 // Non-uppercase ASCII is allowed and doesn't need to be converted. 451 ..b'A' | b'['..=0x7F => Ok(label_len), 452 // Uppercase ASCII is allowed but needs to be transformed into lowercase. 453 b'A'..=b'Z' => { 454 // Lowercase ASCII is a contiguous block starting from `b'a'` as is uppercase 455 // ASCII which starts from `b'A'` with uppercase ASCII coming before; thus we 456 // simply need to shift by a fixed amount. 457 *byt += DIFF; 458 Ok(label_len) 459 } 460 // Non-ASCII is disallowed. 461 0x80.. => Err(AsciiDomainErr::NotAscii), 462 } 463 } 464 }) 465 .map(|_| { 466 // SAFETY: 467 // We just verified `value` only contains ASCII; thus this is safe. 468 let utf8 = unsafe { String::from_utf8_unchecked(value) }; 469 Self(utf8) 470 }) 471 }) 472 } 473 } 474 impl TryFrom<String> for AsciiDomain { 475 type Error = AsciiDomainErr; 476 /// Same as [`Self::try_from`] except `value` is a `String`. 477 #[inline] 478 fn try_from(value: String) -> Result<Self, Self::Error> { 479 Self::try_from(value.into_bytes()) 480 } 481 } 482 /// Similar to [`AsciiDomain`] except the contained data is a `&'static str`. 483 /// 484 /// Since [`Self::new`] and [`Option::unwrap`] are `const fn`s, one can define a global `const` or `static` 485 /// variable that represents the RP ID. 486 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 487 pub struct AsciiDomainStatic(&'static str); 488 impl AsciiDomainStatic { 489 /// Returns the contained `str`. 490 #[inline] 491 #[must_use] 492 pub const fn as_str(self) -> &'static str { 493 self.0 494 } 495 /// Verifies `domain` is a valid lowercase ASCII domain returning `None` when not valid or when 496 /// uppercase ASCII exists. 497 /// 498 /// Read [`AsciiDomain`] for more information about what constitutes a valid domain. 499 /// 500 /// # Examples 501 /// 502 /// ``` 503 /// # use webauthn_rp::request::{AsciiDomainStatic, RpId}; 504 /// /// RP ID of our application. 505 /// const RP_IP: &RpId = &RpId::StaticDomain(AsciiDomainStatic::new("example.com").unwrap()); 506 /// ``` 507 #[expect( 508 clippy::arithmetic_side_effects, 509 reason = "comment justifies correctness" 510 )] 511 #[expect( 512 clippy::else_if_without_else, 513 reason = "part of if branch and else branch are the same" 514 )] 515 #[inline] 516 #[must_use] 517 pub const fn new(domain: &'static str) -> Option<Self> { 518 let mut utf8 = domain.as_bytes(); 519 if let Some(lst) = utf8.last() { 520 let len = utf8.len(); 521 if *lst == b'.' { 522 if len == 1 || len > 254 { 523 return None; 524 } 525 } else if len > 253 { 526 return None; 527 } 528 let mut label_len = 0; 529 while let [first, ref rest @ ..] = *utf8 { 530 if first == b'.' { 531 if label_len == 0 { 532 return None; 533 } 534 label_len = 0; 535 } else if label_len == 63 { 536 return None; 537 } else { 538 match first { 539 // Any non-uppercase ASCII is allowed. 540 // We know `label_len` is less than 63, so this won't overflow. 541 ..b'A' | b'['..=0x7F => label_len += 1, 542 // Uppercase ASCII and non-ASCII are disallowed. 543 b'A'..=b'Z' | 0x80.. => return None, 544 } 545 } 546 utf8 = rest; 547 } 548 Some(Self(domain)) 549 } else { 550 None 551 } 552 } 553 } 554 impl AsRef<str> for AsciiDomainStatic { 555 #[inline] 556 fn as_ref(&self) -> &str { 557 self.as_str() 558 } 559 } 560 impl Borrow<str> for AsciiDomainStatic { 561 #[inline] 562 fn borrow(&self) -> &str { 563 self.as_str() 564 } 565 } 566 impl From<AsciiDomainStatic> for &'static str { 567 #[inline] 568 fn from(value: AsciiDomainStatic) -> Self { 569 value.0 570 } 571 } 572 impl From<AsciiDomainStatic> for String { 573 #[inline] 574 fn from(value: AsciiDomainStatic) -> Self { 575 value.0.to_owned() 576 } 577 } 578 impl From<AsciiDomainStatic> for AsciiDomain { 579 #[inline] 580 fn from(value: AsciiDomainStatic) -> Self { 581 Self(value.0.to_owned()) 582 } 583 } 584 impl PartialEq<&Self> for AsciiDomainStatic { 585 #[inline] 586 fn eq(&self, other: &&Self) -> bool { 587 *self == **other 588 } 589 } 590 impl PartialEq<AsciiDomainStatic> for &AsciiDomainStatic { 591 #[inline] 592 fn eq(&self, other: &AsciiDomainStatic) -> bool { 593 **self == *other 594 } 595 } 596 /// The output of the [URL serializer](https://url.spec.whatwg.org/#concept-url-serializer). 597 /// 598 /// The returned URL must consist of a [scheme](https://url.spec.whatwg.org/#concept-url-scheme) and 599 /// optional [path](https://url.spec.whatwg.org/#url-path) but nothing else. 600 #[derive(Clone, Debug, Eq, PartialEq)] 601 pub struct Url(String); 602 impl AsRef<str> for Url { 603 #[inline] 604 fn as_ref(&self) -> &str { 605 self.0.as_str() 606 } 607 } 608 impl Borrow<str> for Url { 609 #[inline] 610 fn borrow(&self) -> &str { 611 self.0.as_str() 612 } 613 } 614 impl From<Url> for String { 615 #[inline] 616 fn from(value: Url) -> Self { 617 value.0 618 } 619 } 620 impl PartialEq<&Self> for Url { 621 #[inline] 622 fn eq(&self, other: &&Self) -> bool { 623 *self == **other 624 } 625 } 626 impl PartialEq<Url> for &Url { 627 #[inline] 628 fn eq(&self, other: &Url) -> bool { 629 **self == *other 630 } 631 } 632 impl FromStr for Url { 633 type Err = UrlErr; 634 #[inline] 635 fn from_str(s: &str) -> Result<Self, Self::Err> { 636 Uri::from_str(s).map_err(|_e| UrlErr).and_then(|url| { 637 if url.scheme().is_empty() 638 || url.has_host() 639 || url.query().is_some() 640 || url.fragment().is_some() 641 { 642 Err(UrlErr) 643 } else { 644 Ok(Self(url.into())) 645 } 646 }) 647 } 648 } 649 /// [RP ID](https://w3c.github.io/webauthn/#rp-id). 650 #[derive(Clone, Debug, Eq, PartialEq)] 651 pub enum RpId { 652 /// An ASCII domain. 653 /// 654 /// Note web platforms MUST use this variant; and if possible, non-web platforms should too. Also despite 655 /// the spec currently requiring RP IDs to be 656 /// [valid domain strings](https://url.spec.whatwg.org/#valid-domain-string), this is unnecessarily strict 657 /// and will likely be relaxed in a [future version](https://github.com/w3c/webauthn/issues/2206); thus 658 /// any ASCII domain is allowed. 659 Domain(AsciiDomain), 660 /// Similar to [`Self::Domain`] except the ASCII domain is static. 661 /// 662 /// Since [`AsciiDomainStatic::new`] is a `const fn`, one can define a `const` or `static` global variable 663 /// the contains the RP ID. 664 StaticDomain(AsciiDomainStatic), 665 /// A URL with only scheme and path. 666 Url(Url), 667 } 668 impl RpId { 669 /// Returns `Some` containing an [`AsciiDomainStatic`] iff [`AsciiDomainStatic::new`] does. 670 #[inline] 671 #[must_use] 672 pub const fn from_static_domain(domain: &'static str) -> Option<Self> { 673 if let Some(dom) = AsciiDomainStatic::new(domain) { 674 Some(Self::StaticDomain(dom)) 675 } else { 676 None 677 } 678 } 679 /// Validates `hash` is the same as the SHA-256 hash of `self`. 680 fn validate_rp_id_hash<E>(&self, hash: &[u8]) -> Result<(), CeremonyErr<E>> { 681 if *hash == *Sha256::digest(self.as_ref()) { 682 Ok(()) 683 } else { 684 Err(CeremonyErr::RpIdHashMismatch) 685 } 686 } 687 } 688 impl AsRef<str> for RpId { 689 #[inline] 690 fn as_ref(&self) -> &str { 691 match *self { 692 Self::Domain(ref dom) => dom.as_ref(), 693 Self::StaticDomain(dom) => dom.as_str(), 694 Self::Url(ref url) => url.as_ref(), 695 } 696 } 697 } 698 impl Borrow<str> for RpId { 699 #[inline] 700 fn borrow(&self) -> &str { 701 match *self { 702 Self::Domain(ref dom) => dom.borrow(), 703 Self::StaticDomain(dom) => dom.as_str(), 704 Self::Url(ref url) => url.borrow(), 705 } 706 } 707 } 708 impl From<RpId> for String { 709 #[inline] 710 fn from(value: RpId) -> Self { 711 match value { 712 RpId::Domain(dom) => dom.into(), 713 RpId::StaticDomain(dom) => dom.into(), 714 RpId::Url(url) => url.into(), 715 } 716 } 717 } 718 impl PartialEq<&Self> for RpId { 719 #[inline] 720 fn eq(&self, other: &&Self) -> bool { 721 *self == **other 722 } 723 } 724 impl PartialEq<RpId> for &RpId { 725 #[inline] 726 fn eq(&self, other: &RpId) -> bool { 727 **self == *other 728 } 729 } 730 impl From<AsciiDomain> for RpId { 731 #[inline] 732 fn from(value: AsciiDomain) -> Self { 733 Self::Domain(value) 734 } 735 } 736 impl From<AsciiDomainStatic> for RpId { 737 #[inline] 738 fn from(value: AsciiDomainStatic) -> Self { 739 Self::StaticDomain(value) 740 } 741 } 742 impl From<Url> for RpId { 743 #[inline] 744 fn from(value: Url) -> Self { 745 Self::Url(value) 746 } 747 } 748 impl TryFrom<String> for RpId { 749 type Error = RpIdErr; 750 /// Returns `Ok` iff `value` is a valid [`Url`] or [`AsciiDomain`]. 751 /// 752 /// Note when `value` is a valid `Url` and `AsciiDomain`, it will be treated as a `Url`. 753 #[inline] 754 fn try_from(value: String) -> Result<Self, Self::Error> { 755 Url::from_str(value.as_str()) 756 .map(Self::Url) 757 .or_else(|_err| { 758 AsciiDomain::try_from(value) 759 .map(Self::Domain) 760 .map_err(|_e| RpIdErr) 761 }) 762 } 763 } 764 /// A URI scheme. This can be used to make 765 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient. 766 #[derive(Clone, Copy, Debug, Default)] 767 pub enum Scheme<'a> { 768 /// A scheme must not exist when validating the origin. 769 None, 770 /// Any scheme, or no scheme at all, is allowed to exist when validating the origin. 771 Any, 772 /// The HTTPS scheme must exist when validating the origin. 773 #[default] 774 Https, 775 /// The SSH scheme must exist when validating the origin. 776 Ssh, 777 /// The contained `str` scheme must exist when validating the origin. 778 Other(&'a str), 779 /// [`Self::None`] or [`Self::Https`]. 780 NoneHttps, 781 /// [`Self::None`] or [`Self::Ssh`]. 782 NoneSsh, 783 /// [`Self::None`] or [`Self::Other`]. 784 NoneOther(&'a str), 785 } 786 impl Scheme<'_> { 787 /// `self` is any `Scheme`; however `other` is assumed to only be a `Scheme` from a `DomainOrigin` returned 788 /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Scheme::None`, `Scheme::Https`, 789 /// `Scheme::Ssh`, or `Scheme::Other`; furthermore when `Scheme::Other`, it won't contain a `str` that is 790 /// empty or equal to "https" or "ssh". 791 #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")] 792 fn is_equal_to_origin_scheme(self, other: Self) -> bool { 793 match self { 794 Self::None => matches!(other, Self::None), 795 Self::Any => true, 796 Self::Https => matches!(other, Self::Https), 797 Self::Ssh => matches!(other, Self::Ssh), 798 Self::Other(scheme) => match other { 799 Self::None => false, 800 // We want to crash and burn since there is a bug in code. 801 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 802 unreachable!("there is a bug in DomainOrigin::try_from") 803 } 804 Self::Https => scheme == "https", 805 Self::Ssh => scheme == "ssh", 806 Self::Other(scheme_other) => scheme == scheme_other, 807 }, 808 Self::NoneHttps => match other { 809 Self::None | Self::Https => true, 810 Self::Ssh | Self::Other(_) => false, 811 // We want to crash and burn since there is a bug in code. 812 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 813 unreachable!("there is a bug in DomainOrigin::try_from") 814 } 815 }, 816 Self::NoneSsh => match other { 817 Self::None | Self::Ssh => true, 818 // We want to crash and burn since there is a bug in code. 819 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 820 unreachable!("there is a bug in DomainOrigin::try_from") 821 } 822 Self::Https | Self::Other(_) => false, 823 }, 824 Self::NoneOther(scheme) => match other { 825 Self::None => true, 826 // We want to crash and burn since there is a bug in code. 827 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 828 unreachable!("there is a bug in DomainOrigin::try_from") 829 } 830 Self::Https => scheme == "https", 831 Self::Ssh => scheme == "ssh", 832 Self::Other(scheme_other) => scheme == scheme_other, 833 }, 834 } 835 } 836 } 837 impl<'a: 'b, 'b> TryFrom<&'a str> for Scheme<'b> { 838 type Error = SchemeParseErr; 839 /// `"https"` and `"ssh"` get mapped to [`Self::Https`] and [`Self::Ssh`] respectively. All other 840 /// values get mapped to [`Self::Other`]. 841 /// 842 /// # Errors 843 /// 844 /// Errors iff `s` is empty. 845 /// 846 /// # Examples 847 /// 848 /// ``` 849 /// # use webauthn_rp::request::Scheme; 850 /// assert!(matches!(Scheme::try_from("https")?, Scheme::Https)); 851 /// assert!(matches!(Scheme::try_from("https ")?, Scheme::Other(scheme) if scheme == "https ")); 852 /// assert!(matches!(Scheme::try_from("ssh")?, Scheme::Ssh)); 853 /// assert!(matches!(Scheme::try_from("Ssh")?, Scheme::Other(scheme) if scheme == "Ssh")); 854 /// // Even though one can construct an empty `Scheme` via `Scheme::Other` or `Scheme::NoneOther`, 855 /// // one cannot parse one. 856 /// assert!(Scheme::try_from("").is_err()); 857 /// # Ok::<_, webauthn_rp::AggErr>(()) 858 /// ``` 859 #[inline] 860 fn try_from(value: &'a str) -> Result<Self, Self::Error> { 861 match value { 862 "" => Err(SchemeParseErr), 863 "https" => Ok(Self::Https), 864 "ssh" => Ok(Self::Ssh), 865 _ => Ok(Self::Other(value)), 866 } 867 } 868 } 869 /// A TCP/UDP port. This can be used to make 870 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient. 871 #[derive(Clone, Copy, Debug, Default)] 872 pub enum Port { 873 /// A port must not exist when validating the origin. 874 #[default] 875 None, 876 /// Any port, or no port at all, is allowed to exist when validating the origin. 877 Any, 878 /// The contained `u16` port must exist when validating the origin. 879 Val(u16), 880 /// [`Self::None`] or [`Self::Val`]. 881 NoneVal(u16), 882 } 883 impl Port { 884 /// `self` is any `Port`; however `other` is assumed to only be a `Port` from a `DomainOrigin` returned 885 /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Port::None` or `Port::Val`. 886 #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")] 887 fn is_equal_to_origin_port(self, other: Self) -> bool { 888 match self { 889 Self::None => matches!(other, Self::None), 890 Self::Any => true, 891 Self::Val(port) => match other { 892 Self::None => false, 893 // There is a bug in code so we want to crash and burn. 894 Self::Any | Self::NoneVal(_) => { 895 unreachable!("there is a bug in DomainOrigin::try_from") 896 } 897 Self::Val(port_other) => port == port_other, 898 }, 899 Self::NoneVal(port) => match other { 900 Self::None => true, 901 // There is a bug in code so we want to crash and burn. 902 Self::Any | Self::NoneVal(_) => { 903 unreachable!("there is a bug in DomainOrigin::try_from") 904 } 905 Self::Val(port_other) => port == port_other, 906 }, 907 } 908 } 909 } 910 impl FromStr for Port { 911 type Err = PortParseErr; 912 /// Parses `s` as a 16-bit unsigned integer without leading 0s returning [`Self::Val`] with the contained 913 /// `u16`. 914 /// 915 /// # Errors 916 /// 917 /// Errors iff `s` is not a valid 16-bit unsigned integer in decimal notation without leading 0s. 918 /// 919 /// # Examples 920 /// 921 /// ``` 922 /// # use webauthn_rp::request::{error::PortParseErr, Port}; 923 /// assert!(matches!("443".parse()?, Port::Val(443))); 924 /// // TCP/UDP ports have to be in canonical form: 925 /// assert!("022" 926 /// .parse::<Port>() 927 /// .map_or_else(|err| matches!(err, PortParseErr::NotCanonical), |_| false)); 928 /// # Ok::<_, webauthn_rp::AggErr>(()) 929 /// ``` 930 #[inline] 931 fn from_str(s: &str) -> Result<Self, Self::Err> { 932 s.parse().map_err(PortParseErr::ParseInt).and_then(|port| { 933 if s.len() 934 == match port { 935 ..=9 => 1, 936 10..=99 => 2, 937 100..=999 => 3, 938 1_000..=9_999 => 4, 939 10_000.. => 5, 940 } 941 { 942 Ok(Self::Val(port)) 943 } else { 944 Err(PortParseErr::NotCanonical) 945 } 946 }) 947 } 948 } 949 /// A [`tuple origin`](https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-tuple). 950 /// 951 /// This can be used to make [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) 952 /// more convenient. 953 #[derive(Clone, Copy, Debug)] 954 pub struct DomainOrigin<'a, 'b> { 955 /// The scheme. 956 pub scheme: Scheme<'a>, 957 /// The host. 958 pub host: &'b str, 959 /// The TCP/UDP port. 960 pub port: Port, 961 } 962 impl<'b> DomainOrigin<'_, 'b> { 963 /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and 964 /// [`Self::port`] as [`Port::None`]. 965 /// 966 /// # Examples 967 /// 968 /// ``` 969 /// # extern crate alloc; 970 /// # use alloc::borrow::Cow; 971 /// # use webauthn_rp::{request::DomainOrigin, response::Origin}; 972 /// assert_eq!( 973 /// DomainOrigin::new("www.example.com"), 974 /// Origin(Cow::Borrowed("https://www.example.com")) 975 /// ); 976 /// // `DomainOrigin::new` does not allow _any_ port to exist. 977 /// assert_ne!( 978 /// DomainOrigin::new("www.example.com"), 979 /// Origin(Cow::Borrowed("https://www.example.com:443")) 980 /// ); 981 /// ``` 982 #[expect(single_use_lifetimes, reason = "false positive")] 983 #[must_use] 984 #[inline] 985 pub const fn new<'c: 'b>(host: &'c str) -> Self { 986 Self { 987 scheme: Scheme::Https, 988 host, 989 port: Port::None, 990 } 991 } 992 /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and 993 /// [`Self::port`] as [`Port::Any`]. 994 /// 995 /// # Examples 996 /// 997 /// ``` 998 /// # extern crate alloc; 999 /// # use alloc::borrow::Cow; 1000 /// # use webauthn_rp::{request::DomainOrigin, response::Origin}; 1001 /// // Any port is allowed to exist. 1002 /// assert_eq!( 1003 /// DomainOrigin::new_ignore_port("www.example.com"), 1004 /// Origin(Cow::Borrowed("https://www.example.com:1234")) 1005 /// ); 1006 /// // A port doesn't have to exist at all either. 1007 /// assert_eq!( 1008 /// DomainOrigin::new_ignore_port("www.example.com"), 1009 /// Origin(Cow::Borrowed("https://www.example.com")) 1010 /// ); 1011 /// ``` 1012 #[expect(single_use_lifetimes, reason = "false positive")] 1013 #[must_use] 1014 #[inline] 1015 pub const fn new_ignore_port<'c: 'b>(host: &'c str) -> Self { 1016 Self { 1017 scheme: Scheme::Https, 1018 host, 1019 port: Port::Any, 1020 } 1021 } 1022 } 1023 impl PartialEq<Origin<'_>> for DomainOrigin<'_, '_> { 1024 /// Returns `true` iff [`DomainOrigin::scheme`], [`DomainOrigin::host`], and [`DomainOrigin::port`] are the 1025 /// same after calling [`DomainOrigin::try_from`] on `other.0.as_str()`. 1026 /// 1027 /// Note that [`Scheme`] and [`Port`] need not be the same variant. For example [`Scheme::Https`] and 1028 /// [`Scheme::Other`] containing `"https"` will be treated the same. 1029 #[inline] 1030 fn eq(&self, other: &Origin<'_>) -> bool { 1031 DomainOrigin::try_from(other.0.as_ref()).is_ok_and(|dom| { 1032 self.scheme.is_equal_to_origin_scheme(dom.scheme) 1033 && self.host == dom.host 1034 && self.port.is_equal_to_origin_port(dom.port) 1035 }) 1036 } 1037 } 1038 impl PartialEq<Origin<'_>> for &DomainOrigin<'_, '_> { 1039 #[inline] 1040 fn eq(&self, other: &Origin<'_>) -> bool { 1041 **self == *other 1042 } 1043 } 1044 impl PartialEq<&Origin<'_>> for DomainOrigin<'_, '_> { 1045 #[inline] 1046 fn eq(&self, other: &&Origin<'_>) -> bool { 1047 *self == **other 1048 } 1049 } 1050 impl PartialEq<DomainOrigin<'_, '_>> for Origin<'_> { 1051 #[inline] 1052 fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool { 1053 *other == *self 1054 } 1055 } 1056 impl PartialEq<DomainOrigin<'_, '_>> for &Origin<'_> { 1057 #[inline] 1058 fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool { 1059 *other == **self 1060 } 1061 } 1062 impl PartialEq<&DomainOrigin<'_, '_>> for Origin<'_> { 1063 #[inline] 1064 fn eq(&self, other: &&DomainOrigin<'_, '_>) -> bool { 1065 **other == *self 1066 } 1067 } 1068 impl<'a: 'b + 'c, 'b, 'c> TryFrom<&'a str> for DomainOrigin<'b, 'c> { 1069 type Error = DomainOriginParseErr; 1070 /// `value` is parsed according to the following extended regex: 1071 /// 1072 /// `^([^:]*:\/\/)?[^:]*(:.*)?$` 1073 /// 1074 /// where the `[^:]*` of the first capturing group is parsed according to [`Scheme::try_from`], and 1075 /// the `.*` of the second capturing group is parsed according to [`Port::from_str`]. 1076 /// 1077 /// # Errors 1078 /// 1079 /// Errors iff `Scheme::try_from` or `Port::from_str` fail when applicable. 1080 /// 1081 /// # Examples 1082 /// 1083 /// ``` 1084 /// # use webauthn_rp::request::{DomainOrigin, Port, Scheme}; 1085 /// assert!( 1086 /// DomainOrigin::try_from("https://www.example.com:443").map_or(false, |dom| matches!( 1087 /// dom.scheme, 1088 /// Scheme::Https 1089 /// ) && dom.host 1090 /// == "www.example.com" 1091 /// && matches!(dom.port, Port::Val(port) if port == 443)) 1092 /// ); 1093 /// // Parsing is done in a case sensitive way. 1094 /// assert!(DomainOrigin::try_from("Https://www.EXample.com").map_or( 1095 /// false, 1096 /// |dom| matches!(dom.scheme, Scheme::Other(scheme) if scheme == "Https") 1097 /// && dom.host == "www.EXample.com" 1098 /// && matches!(dom.port, Port::None) 1099 /// )); 1100 /// ``` 1101 #[inline] 1102 fn try_from(value: &'a str) -> Result<Self, Self::Error> { 1103 // Any string that contains `':'` is not a [valid domain](https://url.spec.whatwg.org/#valid-domain), and 1104 // and `"//"` never exists in a `Port`; thus if `"://"` exists, it's either invalid or delimits the scheme 1105 // from the rest of the origin. 1106 match value.split_once("://") { 1107 None => Ok((Scheme::None, value)), 1108 Some((poss_scheme, rem)) => Scheme::try_from(poss_scheme) 1109 .map_err(DomainOriginParseErr::Scheme) 1110 .map(|scheme| (scheme, rem)), 1111 } 1112 .and_then(|(scheme, rem)| { 1113 // `':'` never exists in a valid domain; thus if it exists, it's either invalid or 1114 // separates the domain from the port. 1115 rem.split_once(':') 1116 .map_or_else( 1117 || Ok((rem, Port::None)), 1118 |(rem2, poss_port)| { 1119 Port::from_str(poss_port) 1120 .map_err(DomainOriginParseErr::Port) 1121 .map(|port| (rem2, port)) 1122 }, 1123 ) 1124 .map(|(host, port)| Self { scheme, host, port }) 1125 }) 1126 } 1127 } 1128 /// [`PublicKeyCredentialDescriptor`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptor) 1129 /// associated with a registered credential. 1130 #[derive(Clone, Debug)] 1131 pub struct PublicKeyCredentialDescriptor<T> { 1132 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-id). 1133 pub id: CredentialId<T>, 1134 /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports). 1135 pub transports: AuthTransports, 1136 } 1137 /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement). 1138 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 1139 pub enum UserVerificationRequirement { 1140 /// [`required`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-required). 1141 Required, 1142 /// [`discouraged`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-discouraged). 1143 /// 1144 /// Note some authenticators always require user verification when registering a credential (e.g., 1145 /// [CTAP 2.0](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html) 1146 /// authenticators that have had a PIN enabled). 1147 Discouraged, 1148 /// [`preferred`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-preferred). 1149 Preferred, 1150 } 1151 /// [`PublicKeyCredentialHints`](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint). 1152 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 1153 pub enum Hint { 1154 /// No hints. 1155 #[default] 1156 None, 1157 /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-security-key). 1158 SecurityKey, 1159 /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-client-device). 1160 ClientDevice, 1161 /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-hybrid). 1162 Hybrid, 1163 /// [`Self::SecurityKey`] and [`Self::ClientDevice`]. 1164 SecurityKeyClientDevice, 1165 /// [`Self::ClientDevice`] and [`Self::SecurityKey`]. 1166 ClientDeviceSecurityKey, 1167 /// [`Self::SecurityKey`] and [`Self::Hybrid`]. 1168 SecurityKeyHybrid, 1169 /// [`Self::Hybrid`] and [`Self::SecurityKey`]. 1170 HybridSecurityKey, 1171 /// [`Self::ClientDevice`] and [`Self::Hybrid`]. 1172 ClientDeviceHybrid, 1173 /// [`Self::Hybrid`] and [`Self::ClientDevice`]. 1174 HybridClientDevice, 1175 /// [`Self::SecurityKeyClientDevice`] and [`Self::Hybrid`]. 1176 SecurityKeyClientDeviceHybrid, 1177 /// [`Self::SecurityKeyHybrid`] and [`Self::ClientDevice`]. 1178 SecurityKeyHybridClientDevice, 1179 /// [`Self::ClientDeviceSecurityKey`] and [`Self::Hybrid`]. 1180 ClientDeviceSecurityKeyHybrid, 1181 /// [`Self::ClientDeviceHybrid`] and [`Self::SecurityKey`]. 1182 ClientDeviceHybridSecurityKey, 1183 /// [`Self::HybridSecurityKey`] and [`Self::ClientDevice`]. 1184 HybridSecurityKeyClientDevice, 1185 /// [`Self::HybridClientDevice`] and [`Self::SecurityKey`]. 1186 HybridClientDeviceSecurityKey, 1187 } 1188 /// Controls if the response to a requested extension is required to be sent back. 1189 /// 1190 /// Note when requiring an extension, the extension must not only be sent back but also 1191 /// contain at least one expected field (e.g., [`ClientExtensionsOutputs::cred_props`] must be 1192 /// `Some(CredentialPropertiesOutput { rk: Some(_) })`. 1193 /// 1194 /// If one wants to additionally control the values of an extension, use [`ExtensionInfo`]. 1195 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 1196 pub enum ExtensionReq { 1197 /// The response to a requested extension is required to be sent back. 1198 Require, 1199 /// The response to a requested extension is allowed, but not required, to be sent back. 1200 Allow, 1201 } 1202 /// Dictates how an extension should be processed. 1203 /// 1204 /// If one wants to only control if the extension should be returned, use [`ExtensionReq`]. 1205 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 1206 pub enum ExtensionInfo { 1207 /// Require the associated extension and enforce its value. 1208 RequireEnforceValue, 1209 /// Require the associated extension but don't enforce its value. 1210 RequireDontEnforceValue, 1211 /// Allow the associated extension to exist and enforce its value when it does exist. 1212 AllowEnforceValue, 1213 /// Allow the associated extension to exist but don't enforce its value. 1214 AllowDontEnforceValue, 1215 } 1216 impl Display for ExtensionInfo { 1217 #[inline] 1218 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1219 f.write_str(match *self { 1220 Self::RequireEnforceValue => "require the corresponding extension response and enforce its value", 1221 Self::RequireDontEnforceValue => "require the corresponding extension response but don't enforce its value", 1222 Self::AllowEnforceValue => "don't require the corresponding extension response; but if sent, enforce its value", 1223 Self::AllowDontEnforceValue => "don't require the corresponding extension response; and if sent, don't enforce its value", 1224 }) 1225 } 1226 } 1227 /// [`CredentialMediationRequirement`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement). 1228 /// 1229 /// Note [`silent`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-silent) 1230 /// is not supported for WebAuthn credentials, and 1231 /// [`optional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-optional) 1232 /// is just an alias for [`Self::Required`]. 1233 #[expect(clippy::doc_markdown, reason = "false positive")] 1234 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 1235 pub enum CredentialMediationRequirement { 1236 /// [`required`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-required). 1237 /// 1238 /// This is the default mediation for ceremonies. 1239 #[default] 1240 Required, 1241 /// [`conditional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-conditional). 1242 /// 1243 /// Note that when registering a new credential with [`CredentialCreationOptions::mediation`] set to 1244 /// `Self::Conditional`, [`UserVerificationRequirement::Required`] MUST NOT be used unless user verification 1245 /// can be explicitly performed during the ceremony. 1246 Conditional, 1247 } 1248 /// Backup requirements for the credential. 1249 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 1250 pub enum BackupReq { 1251 /// No requirements (i.e., any [`Backup`] is allowed). 1252 #[default] 1253 None, 1254 /// Credential must not be eligible for backup. 1255 NotEligible, 1256 /// Credential must be eligible for backup. 1257 /// 1258 /// Note the existence of a backup is ignored. If a backup must exist, then use [`Self::Exists`]; if a 1259 /// backup must not exist, then use [`Self::EligibleNotExists`]. 1260 Eligible, 1261 /// Credential must be eligible for backup, but a backup must not exist. 1262 EligibleNotExists, 1263 /// Credential must be backed up. 1264 Exists, 1265 } 1266 impl From<Backup> for BackupReq { 1267 /// One may want to create `BackupReq` based on the previous `Backup` such that the subsequent `Backup` is 1268 /// essentially unchanged. 1269 /// 1270 /// Specifically this transforms [`Backup::NotEligible`] to [`Self::NotEligible`] and [`Backup::Eligible`] and 1271 /// [`Backup::Exists`] to [`Self::Eligible`]. Note this means that a credential that 1272 /// is eligible to be backed up but currently does not have a backup will be allowed to change such that it 1273 /// is backed up. Similarly, a credential that is backed up is allowed to change such that a backup no longer 1274 /// exists. 1275 #[inline] 1276 fn from(value: Backup) -> Self { 1277 if matches!(value, Backup::NotEligible) { 1278 Self::NotEligible 1279 } else { 1280 Self::Eligible 1281 } 1282 } 1283 } 1284 /// A container of "credentials". 1285 /// 1286 /// This is mainly a way to unify [`Vec`] of [`PublicKeyCredentialDescriptor`] 1287 /// and [`AllowedCredentials`]. This can be useful in situations when one only 1288 /// deals with [`AllowedCredential`]s with empty [`CredentialSpecificExtension`]s 1289 /// essentially making them the same as [`PublicKeyCredentialDescriptor`]s. 1290 /// 1291 /// # Examples 1292 /// 1293 /// ``` 1294 /// # use webauthn_rp::{ 1295 /// # request::{ 1296 /// # auth::AllowedCredentials, register::UserHandle, Credentials, PublicKeyCredentialDescriptor, 1297 /// # }, 1298 /// # response::{AuthTransports, CredentialId}, 1299 /// # }; 1300 /// /// Fetches all credentials under `user_handle` to be allowed during authentication for non-discoverable 1301 /// /// requests. 1302 /// # #[cfg(feature = "custom")] 1303 /// fn get_allowed_credentials<const LEN: usize>(user_handle: &UserHandle<LEN>) -> AllowedCredentials { 1304 /// get_credentials(user_handle) 1305 /// } 1306 /// /// Fetches all credentials under `user_handle` to be excluded during registration. 1307 /// # #[cfg(feature = "custom")] 1308 /// fn get_excluded_credentials<const LEN: usize>( 1309 /// user_handle: &UserHandle<LEN>, 1310 /// ) -> Vec<PublicKeyCredentialDescriptor<Box<[u8]>>> { 1311 /// get_credentials(user_handle) 1312 /// } 1313 /// /// Used to fetch the excluded `PublicKeyCredentialDescriptor`s associated with `user_handle` during 1314 /// /// registration as well as the `AllowedCredentials` containing `AllowedCredential`s with no credential-specific 1315 /// /// extensions which is used for non-discoverable requests. 1316 /// # #[cfg(feature = "custom")] 1317 /// fn get_credentials<const LEN: usize, T>(user_handle: &UserHandle<LEN>) -> T 1318 /// where 1319 /// T: Credentials, 1320 /// PublicKeyCredentialDescriptor<Box<[u8]>>: Into<T::Credential>, 1321 /// { 1322 /// let iter = get_cred_parts(user_handle); 1323 /// let len = iter.size_hint().0; 1324 /// iter.fold(T::with_capacity(len), |mut creds, parts| { 1325 /// creds.push( 1326 /// PublicKeyCredentialDescriptor { 1327 /// id: parts.0, 1328 /// transports: parts.1, 1329 /// } 1330 /// .into(), 1331 /// ); 1332 /// creds 1333 /// }) 1334 /// } 1335 /// /// Fetches all `CredentialId`s and associated `AuthTransports` under `user_handle` 1336 /// /// from the database. 1337 /// # #[cfg(feature = "custom")] 1338 /// fn get_cred_parts<const LEN: usize>( 1339 /// user_handle: &UserHandle<LEN>, 1340 /// ) -> impl Iterator<Item = (CredentialId<Box<[u8]>>, AuthTransports)> { 1341 /// // ⋮ 1342 /// # [( 1343 /// # CredentialId::try_from(vec![0; 16].into_boxed_slice()).unwrap(), 1344 /// # AuthTransports::NONE, 1345 /// # )] 1346 /// # .into_iter() 1347 /// } 1348 /// ``` 1349 pub trait Credentials: Sized { 1350 /// The "credential"s that make up `Self`. 1351 type Credential; 1352 /// Returns `Self`. 1353 #[inline] 1354 #[must_use] 1355 fn new() -> Self { 1356 Self::with_capacity(0) 1357 } 1358 /// Returns `Self` with at least `capacity` allocated. 1359 fn with_capacity(capacity: usize) -> Self; 1360 /// Adds `cred` to `self`. 1361 /// 1362 /// Returns `true` iff `cred` was added. 1363 fn push(&mut self, cred: Self::Credential) -> bool; 1364 /// Returns the number of [`Self::Credential`]s in `Self`. 1365 fn len(&self) -> usize; 1366 /// Returns `true` iff [`Self::len`] is `0`. 1367 #[inline] 1368 fn is_empty(&self) -> bool { 1369 self.len() == 0 1370 } 1371 } 1372 impl<T> Credentials for Vec<T> { 1373 type Credential = T; 1374 #[inline] 1375 fn with_capacity(capacity: usize) -> Self { 1376 Self::with_capacity(capacity) 1377 } 1378 #[inline] 1379 fn push(&mut self, cred: Self::Credential) -> bool { 1380 self.push(cred); 1381 true 1382 } 1383 #[inline] 1384 fn len(&self) -> usize { 1385 self.len() 1386 } 1387 } 1388 /// Additional options that control how [`Ceremony::partial_validate`] works. 1389 struct CeremonyOptions<'origins, 'top_origins, O, T> { 1390 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1391 /// 1392 /// When this is empty, the origin that will be used will be based on 1393 /// the [`RpId`] passed to [`RegistrationServerState::verify`]. If [`RpId::Domain`], then the [`DomainOrigin`] returned from 1394 /// passing [`AsciiDomain::as_ref`] to [`DomainOrigin::new`] will be used; otherwise the [`Url`] in 1395 /// [`RpId::Url`] will be used. 1396 allowed_origins: &'origins [O], 1397 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 1398 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1399 /// 1400 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 1401 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 1402 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 1403 allowed_top_origins: Option<&'top_origins [T]>, 1404 /// The required [`Backup`] state of the credential. 1405 backup_requirement: BackupReq, 1406 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 1407 #[cfg(feature = "serde_relaxed")] 1408 client_data_json_relaxed: bool, 1409 } 1410 impl<'o, 't, O, T> From<&RegistrationVerificationOptions<'o, 't, O, T>> 1411 for CeremonyOptions<'o, 't, O, T> 1412 { 1413 fn from(value: &RegistrationVerificationOptions<'o, 't, O, T>) -> Self { 1414 Self { 1415 allowed_origins: value.allowed_origins, 1416 allowed_top_origins: value.allowed_top_origins, 1417 backup_requirement: value.backup_requirement, 1418 #[cfg(feature = "serde_relaxed")] 1419 client_data_json_relaxed: value.client_data_json_relaxed, 1420 } 1421 } 1422 } 1423 /// Functionality common to both registration and authentication ceremonies. 1424 /// 1425 /// Designed to be implemented on the _request_ side. 1426 trait Ceremony<const USER_LEN: usize, const DISCOVERABLE: bool> { 1427 /// The type of response that is associated with the ceremony. 1428 type R: Response; 1429 /// Challenge. 1430 fn rand_challenge(&self) -> SentChallenge; 1431 /// `Instant` the ceremony was expires. 1432 #[cfg(not(feature = "serializable_server_state"))] 1433 fn expiry(&self) -> Instant; 1434 /// `Instant` the ceremony was expires. 1435 #[cfg(feature = "serializable_server_state")] 1436 fn expiry(&self) -> SystemTime; 1437 /// User verification requirement. 1438 fn user_verification(&self) -> UserVerificationRequirement; 1439 /// Performs validation of ceremony criteria common to both ceremony types. 1440 #[expect( 1441 clippy::type_complexity, 1442 reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable" 1443 )] 1444 #[expect(clippy::too_many_lines, reason = "102 lines is fine")] 1445 fn partial_validate<'a, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>>( 1446 &self, 1447 rp_id: &RpId, 1448 resp: &'a Self::R, 1449 key: <<Self::R as Response>::Auth as AuthResponse>::CredKey<'_>, 1450 options: &CeremonyOptions<'_, '_, O, T>, 1451 ) -> Result< 1452 <<Self::R as Response>::Auth as AuthResponse>::Auth<'a>, 1453 CeremonyErr< 1454 <<<Self::R as Response>::Auth as AuthResponse>::Auth<'a> as AuthDataContainer<'a>>::Err, 1455 >, 1456 > { 1457 // [Registration ceremony](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential) 1458 // is handled by: 1459 // 1460 // 1. Calling code. 1461 // 2. Client code and the construction of `resp` (hopefully via [`Registration::deserialize`]). 1462 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAttestation::deserialize`]). 1463 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1464 // 5. Below via [`CollectedClientData::from_client_data_json_relaxed`]. 1465 // 6. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1466 // 7. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1467 // 8. Below. 1468 // 9. Below. 1469 // 10. Below. 1470 // 11. Below. 1471 // 12. Below via [`AuthenticatorAttestation::new`]. 1472 // 13. Below via [`AttestationObject::parse_data`]. 1473 // 14. Below. 1474 // 15. [`RegistrationServerState::verify`]. 1475 // 16. Below. 1476 // 17. Below via [`AuthenticatorData::from_cbor`]. 1477 // 18. Below. 1478 // 19. Below. 1479 // 20. [`RegistrationServerState::verify`]. 1480 // 21. Below via [`AttestationObject::parse_data`]. 1481 // 22. Below via [`AttestationObject::parse_data`]. 1482 // 23. N/A since only none and self attestations are supported. 1483 // 24. Always satisfied since only none and self attestations are supported (Item 3 is N/A). 1484 // 25. Below via [`AttestedCredentialData::from_cbor`]. 1485 // 26. Calling code. 1486 // 27. [`RegistrationServerState::verify`]. 1487 // 28. N/A since only none and self attestations are supported. 1488 // 29. [`RegistrationServerState::verify`]. 1489 // 1490 // 1491 // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) 1492 // is handled by: 1493 // 1494 // 1. Calling code. 1495 // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]). 1496 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]). 1497 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1498 // 5. [`AuthenticationServerState::verify`]. 1499 // 6. [`AuthenticationServerState::verify`]. 1500 // 7. Informative only in that it defines variables. 1501 // 8. Below via [`CollectedClientData::from_client_data_json_relaxed`]. 1502 // 9. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1503 // 10. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1504 // 11. Below. 1505 // 12. Below. 1506 // 13. Below. 1507 // 14. Below. 1508 // 15. Below. 1509 // 16. Below via [`AuthenticatorData::from_cbor`]. 1510 // 17. Below. 1511 // 18. Below via [`AuthenticatorData::from_cbor`]. 1512 // 19. Below. 1513 // 20. Below via [`AuthenticatorAssertion::new`]. 1514 // 21. Below. 1515 // 22. [`AuthenticationServerState::verify`]. 1516 // 23. [`AuthenticationServerState::verify`]. 1517 // 24. [`AuthenticationServerState::verify`]. 1518 // 25. [`AuthenticationServerState::verify`]. 1519 1520 // Enforce timeout. 1521 #[cfg(not(feature = "serializable_server_state"))] 1522 let active = self.expiry() >= Instant::now(); 1523 #[cfg(feature = "serializable_server_state")] 1524 let active = self.expiry() >= SystemTime::now(); 1525 if active { 1526 #[cfg(feature = "serde_relaxed")] 1527 let relaxed = options.client_data_json_relaxed; 1528 #[cfg(not(feature = "serde_relaxed"))] 1529 let relaxed = false; 1530 resp.auth() 1531 // Steps 5–7, 12–13, 17, 21–22, and 25 of the registration ceremony. 1532 // Steps 8–10, 16, 18, and 20–21 of the authentication ceremony. 1533 .parse_data_and_verify_sig(key, relaxed) 1534 .map_err(CeremonyErr::AuthResp) 1535 .and_then(|(client_data_json, auth_response)| { 1536 if options.allowed_origins.is_empty() { 1537 if match *rp_id { 1538 RpId::Domain(ref dom) => { 1539 // Steps 9 and 12 of the registration and authentication ceremonies 1540 // respectively. 1541 DomainOrigin::new(dom.as_ref()) == client_data_json.origin 1542 } 1543 // Steps 9 and 12 of the registration and authentication ceremonies 1544 // respectively. 1545 RpId::Url(ref url) => url == client_data_json.origin, 1546 RpId::StaticDomain(dom) => { 1547 DomainOrigin::new(dom.0) == client_data_json.origin 1548 } 1549 } { 1550 Ok(()) 1551 } else { 1552 Err(CeremonyErr::OriginMismatch) 1553 } 1554 } else { 1555 options 1556 .allowed_origins 1557 .iter() 1558 // Steps 9 and 12 of the registration and authentication ceremonies 1559 // respectively. 1560 .find(|o| **o == client_data_json.origin) 1561 .ok_or(CeremonyErr::OriginMismatch) 1562 .map(|_| ()) 1563 } 1564 .and_then(|()| { 1565 // Steps 10–11 of the registration ceremony. 1566 // Steps 13–14 of the authentication ceremony. 1567 match options.allowed_top_origins { 1568 None => { 1569 if client_data_json.cross_origin { 1570 Err(CeremonyErr::CrossOrigin) 1571 } else if client_data_json.top_origin.is_some() { 1572 Err(CeremonyErr::TopOriginMismatch) 1573 } else { 1574 Ok(()) 1575 } 1576 } 1577 Some(top_origins) => client_data_json.top_origin.map_or(Ok(()), |t| { 1578 top_origins 1579 .iter() 1580 .find(|top| **top == t) 1581 .ok_or(CeremonyErr::TopOriginMismatch) 1582 .map(|_| ()) 1583 }), 1584 } 1585 .and_then(|()| { 1586 // Steps 8 and 11 of the registration and authentication ceremonies 1587 // respectively. 1588 if self.rand_challenge() == client_data_json.challenge { 1589 let auth_data = auth_response.authenticator_data(); 1590 rp_id 1591 // Steps 14 and 15 of the registration and authentication ceremonies 1592 // respectively. 1593 .validate_rp_id_hash(auth_data.rp_hash()) 1594 .and_then(|()| { 1595 let flag = auth_data.flag(); 1596 // Steps 16 and 17 of the registration and authentication ceremonies 1597 // respectively. 1598 if flag.user_verified 1599 || !matches!( 1600 self.user_verification(), 1601 UserVerificationRequirement::Required 1602 ) 1603 { 1604 // Steps 18–19 of the registration ceremony. 1605 // Step 19 of the authentication ceremony. 1606 match options.backup_requirement { 1607 BackupReq::None => Ok(()), 1608 BackupReq::NotEligible => { 1609 if matches!(flag.backup, Backup::NotEligible) { 1610 Ok(()) 1611 } else { 1612 Err(CeremonyErr::BackupEligible) 1613 } 1614 } 1615 BackupReq::Eligible => { 1616 if matches!(flag.backup, Backup::NotEligible) { 1617 Err(CeremonyErr::BackupNotEligible) 1618 } else { 1619 Ok(()) 1620 } 1621 } 1622 BackupReq::EligibleNotExists => { 1623 if matches!(flag.backup, Backup::Eligible) { 1624 Ok(()) 1625 } else { 1626 Err(CeremonyErr::BackupExists) 1627 } 1628 } 1629 BackupReq::Exists => { 1630 if matches!(flag.backup, Backup::Exists) { 1631 Ok(()) 1632 } else { 1633 Err(CeremonyErr::BackupDoesNotExist) 1634 } 1635 } 1636 } 1637 } else { 1638 Err(CeremonyErr::UserNotVerified) 1639 } 1640 }) 1641 .map(|()| auth_response) 1642 } else { 1643 Err(CeremonyErr::ChallengeMismatch) 1644 } 1645 }) 1646 }) 1647 }) 1648 } else { 1649 Err(CeremonyErr::Timeout) 1650 } 1651 } 1652 } 1653 /// "Ceremonies" stored on the server that expire after a certain duration. 1654 /// 1655 /// Types like [`RegistrationServerState`] and [`DiscoverableAuthenticationServerState`] are based on [`Challenge`]s 1656 /// that expire after a certain duration. 1657 pub trait TimedCeremony { 1658 /// Returns the `Instant` the ceremony expires. 1659 /// 1660 /// Note when `serializable_server_state` is enabled, [`SystemTime`] is returned instead. 1661 #[cfg_attr(docsrs, doc(auto_cfg = false))] 1662 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1663 fn expiration(&self) -> Instant; 1664 /// Returns the `SystemTime` the ceremony expires. 1665 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1666 fn expiration(&self) -> SystemTime; 1667 } 1668 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues). 1669 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 1670 pub struct PrfInput<'first, 'second> { 1671 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 1672 pub first: &'first [u8], 1673 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 1674 pub second: Option<&'second [u8]>, 1675 } 1676 impl<'first, 'second> PrfInput<'first, 'second> { 1677 /// Returns a `PrfInput` with [`Self::first`] set to `first` and [`Self::second`] set to `None`. 1678 #[expect(single_use_lifetimes, reason = "false positive")] 1679 #[inline] 1680 #[must_use] 1681 pub const fn with_first<'a: 'first>(first: &'a [u8]) -> Self { 1682 Self { 1683 first, 1684 second: None, 1685 } 1686 } 1687 /// Same as [`Self::with_first`] except [`Self::second`] is set to `Some` containing `second`. 1688 #[expect(single_use_lifetimes, reason = "false positive")] 1689 #[inline] 1690 #[must_use] 1691 pub const fn with_two<'a: 'first, 'b: 'second>(first: &'a [u8], second: &'b [u8]) -> Self { 1692 Self { 1693 first, 1694 second: Some(second), 1695 } 1696 } 1697 } 1698 /// The number of milliseconds in 5 minutes. 1699 /// 1700 /// This is the recommended default timeout duration for ceremonies 1701 /// [in the spec](https://www.w3.org/TR/webauthn-3/#sctn-timeout-recommended-range). 1702 pub const FIVE_MINUTES: NonZeroU32 = NonZeroU32::new(300_000).unwrap(); 1703 #[cfg(test)] 1704 mod tests { 1705 use super::AsciiDomainStatic; 1706 #[cfg(feature = "custom")] 1707 use super::{ 1708 super::{ 1709 AggErr, AuthenticatedCredential, 1710 response::{ 1711 AuthTransports, AuthenticatorAttachment, Backup, CredentialId, 1712 auth::{ 1713 DiscoverableAuthentication, DiscoverableAuthenticatorAssertion, 1714 NonDiscoverableAuthentication, NonDiscoverableAuthenticatorAssertion, 1715 }, 1716 register::{ 1717 AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation, 1718 AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputs, 1719 ClientExtensionsOutputsStaticState, CompressedP256PubKey, CompressedP384PubKey, 1720 CompressedPubKeyOwned, CredentialProtectionPolicy, DynamicState, Ed25519PubKey, 1721 MlDsa44PubKey, MlDsa65PubKey, MlDsa87PubKey, Registration, RsaPubKey, 1722 StaticState, UncompressedPubKey, 1723 }, 1724 }, 1725 }, 1726 Challenge, Credentials as _, ExtensionInfo, ExtensionReq, PrfInput, 1727 PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, 1728 auth::{ 1729 AllowedCredential, AllowedCredentials, AuthenticationVerificationOptions, 1730 CredentialSpecificExtension, DiscoverableCredentialRequestOptions, 1731 Extension as AuthExt, NonDiscoverableCredentialRequestOptions, PrfInputOwned, 1732 }, 1733 register::{ 1734 CredProtect, CredentialCreationOptions, DisplayName, Extension as RegExt, 1735 FourToSixtyThree, PublicKeyCredentialUserEntity, RegistrationVerificationOptions, 1736 UserHandle, 1737 }, 1738 }; 1739 #[cfg(feature = "custom")] 1740 use ed25519_dalek::{Signer as _, SigningKey}; 1741 #[cfg(feature = "custom")] 1742 use ml_dsa::{ 1743 MlDsa44, MlDsa65, MlDsa87, Signature as MlDsaSignature, SigningKey as MlDsaSigKey, 1744 }; 1745 #[cfg(feature = "custom")] 1746 use p256::{ 1747 ecdsa::{DerSignature as P256DerSig, SigningKey as P256Key}, 1748 elliptic_curve::sec1::Tag, 1749 }; 1750 #[cfg(feature = "custom")] 1751 use p384::ecdsa::{DerSignature as P384DerSig, SigningKey as P384Key}; 1752 #[cfg(feature = "custom")] 1753 use rsa::{ 1754 BoxedUint, RsaPrivateKey, 1755 pkcs1v15::SigningKey as RsaKey, 1756 sha2::{Digest as _, Sha256}, 1757 signature::{Keypair as _, SignatureEncoding as _}, 1758 traits::PublicKeyParts as _, 1759 }; 1760 use serde_json as _; 1761 #[cfg(feature = "custom")] 1762 const CBOR_UINT: u8 = 0b000_00000; 1763 #[cfg(feature = "custom")] 1764 const CBOR_NEG: u8 = 0b001_00000; 1765 #[cfg(feature = "custom")] 1766 const CBOR_BYTES: u8 = 0b010_00000; 1767 #[cfg(feature = "custom")] 1768 const CBOR_TEXT: u8 = 0b011_00000; 1769 #[cfg(feature = "custom")] 1770 const CBOR_MAP: u8 = 0b101_00000; 1771 #[cfg(feature = "custom")] 1772 const CBOR_SIMPLE: u8 = 0b111_00000; 1773 #[cfg(feature = "custom")] 1774 const CBOR_TRUE: u8 = CBOR_SIMPLE | 21; 1775 #[test] 1776 fn ascii_domain_static() { 1777 /// No trailing dot, max label length, max domain length. 1778 const LONG: AsciiDomainStatic = AsciiDomainStatic::new( 1779 "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 1780 ) 1781 .unwrap(); 1782 /// Trailing dot, min label length, max domain length. 1783 const LONG_TRAILING: AsciiDomainStatic = AsciiDomainStatic::new("w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.").unwrap(); 1784 /// Single character domain. 1785 const SHORT: AsciiDomainStatic = AsciiDomainStatic::new("w").unwrap(); 1786 let long_label = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"; 1787 assert_eq!(long_label.len(), 63); 1788 let mut long = format!("{long_label}.{long_label}.{long_label}.{long_label}"); 1789 _ = long.pop(); 1790 _ = long.pop(); 1791 assert_eq!(LONG.0.len(), 253); 1792 assert_eq!(LONG.0, long.as_str()); 1793 let trailing = "w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w."; 1794 assert_eq!(LONG_TRAILING.0.len(), 254); 1795 assert_eq!(LONG_TRAILING.0, trailing); 1796 assert_eq!(SHORT.0.len(), 1); 1797 assert_eq!(SHORT.0, "w"); 1798 assert!(AsciiDomainStatic::new("www.Example.com").is_none()); 1799 assert!(AsciiDomainStatic::new("").is_none()); 1800 assert!(AsciiDomainStatic::new(".").is_none()); 1801 assert!(AsciiDomainStatic::new("www..c").is_none()); 1802 let too_long_label = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"; 1803 assert_eq!(too_long_label.len(), 64); 1804 assert!(AsciiDomainStatic::new(too_long_label).is_none()); 1805 let dom_254_no_trailing_dot = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"; 1806 assert_eq!(dom_254_no_trailing_dot.len(), 254); 1807 assert!(AsciiDomainStatic::new(dom_254_no_trailing_dot).is_none()); 1808 assert!(AsciiDomainStatic::new("\u{3bb}.com").is_none()); 1809 } 1810 #[cfg(feature = "custom")] 1811 const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap(); 1812 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 1813 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 1814 #[expect(clippy::too_many_lines, reason = "a lot to test")] 1815 #[test] 1816 #[cfg(feature = "custom")] 1817 fn eddsa_reg() -> Result<(), AggErr> { 1818 let id = UserHandle::from([0]); 1819 let mut opts = CredentialCreationOptions::passkey( 1820 RP_ID, 1821 PublicKeyCredentialUserEntity { 1822 name: "foo".try_into()?, 1823 id: &id, 1824 display_name: DisplayName::Blank, 1825 }, 1826 Vec::new(), 1827 ); 1828 opts.public_key.challenge = Challenge(0); 1829 opts.public_key.extensions = RegExt { 1830 cred_props: None, 1831 cred_protect: CredProtect::UserVerificationRequired( 1832 false, 1833 ExtensionInfo::RequireEnforceValue, 1834 ), 1835 min_pin_length: Some((FourToSixtyThree::Ten, ExtensionInfo::RequireEnforceValue)), 1836 prf: Some(( 1837 PrfInput { 1838 first: [0].as_slice(), 1839 second: None, 1840 }, 1841 ExtensionInfo::RequireEnforceValue, 1842 )), 1843 }; 1844 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 1845 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 1846 let mut attestation_object = Vec::new(); 1847 attestation_object.extend_from_slice( 1848 [ 1849 CBOR_MAP | 3, 1850 CBOR_TEXT | 3, 1851 b'f', 1852 b'm', 1853 b't', 1854 CBOR_TEXT | 6, 1855 b'p', 1856 b'a', 1857 b'c', 1858 b'k', 1859 b'e', 1860 b'd', 1861 CBOR_TEXT | 7, 1862 b'a', 1863 b't', 1864 b't', 1865 b'S', 1866 b't', 1867 b'm', 1868 b't', 1869 CBOR_MAP | 2, 1870 CBOR_TEXT | 3, 1871 b'a', 1872 b'l', 1873 b'g', 1874 // COSE EdDSA. 1875 CBOR_NEG | 7, 1876 CBOR_TEXT | 3, 1877 b's', 1878 b'i', 1879 b'g', 1880 CBOR_BYTES | 24, 1881 64, 1882 0, 1883 0, 1884 0, 1885 0, 1886 0, 1887 0, 1888 0, 1889 0, 1890 0, 1891 0, 1892 0, 1893 0, 1894 0, 1895 0, 1896 0, 1897 0, 1898 0, 1899 0, 1900 0, 1901 0, 1902 0, 1903 0, 1904 0, 1905 0, 1906 0, 1907 0, 1908 0, 1909 0, 1910 0, 1911 0, 1912 0, 1913 0, 1914 0, 1915 0, 1916 0, 1917 0, 1918 0, 1919 0, 1920 0, 1921 0, 1922 0, 1923 0, 1924 0, 1925 0, 1926 0, 1927 0, 1928 0, 1929 0, 1930 0, 1931 0, 1932 0, 1933 0, 1934 0, 1935 0, 1936 0, 1937 0, 1938 0, 1939 0, 1940 0, 1941 0, 1942 0, 1943 0, 1944 0, 1945 0, 1946 CBOR_TEXT | 8, 1947 b'a', 1948 b'u', 1949 b't', 1950 b'h', 1951 b'D', 1952 b'a', 1953 b't', 1954 b'a', 1955 CBOR_BYTES | 24, 1956 // Length is 154. 1957 154, 1958 // RP ID HASH. 1959 // This will be overwritten later. 1960 0, 1961 0, 1962 0, 1963 0, 1964 0, 1965 0, 1966 0, 1967 0, 1968 0, 1969 0, 1970 0, 1971 0, 1972 0, 1973 0, 1974 0, 1975 0, 1976 0, 1977 0, 1978 0, 1979 0, 1980 0, 1981 0, 1982 0, 1983 0, 1984 0, 1985 0, 1986 0, 1987 0, 1988 0, 1989 0, 1990 0, 1991 0, 1992 // FLAGS. 1993 // UP, UV, AT, and ED (right-to-left). 1994 0b1100_0101, 1995 // COUNTER. 1996 // 0 as 32-bit big endian. 1997 0, 1998 0, 1999 0, 2000 0, 2001 // AAGUID. 2002 0, 2003 0, 2004 0, 2005 0, 2006 0, 2007 0, 2008 0, 2009 0, 2010 0, 2011 0, 2012 0, 2013 0, 2014 0, 2015 0, 2016 0, 2017 0, 2018 // L. 2019 // CREDENTIAL ID length is 16 as 16-bit big endian. 2020 0, 2021 16, 2022 // CREDENTIAL ID. 2023 0, 2024 0, 2025 0, 2026 0, 2027 0, 2028 0, 2029 0, 2030 0, 2031 0, 2032 0, 2033 0, 2034 0, 2035 0, 2036 0, 2037 0, 2038 0, 2039 CBOR_MAP | 4, 2040 // COSE kty. 2041 CBOR_UINT | 1, 2042 // COSE OKP. 2043 CBOR_UINT | 1, 2044 // COSE alg. 2045 CBOR_UINT | 3, 2046 // COSE EdDSA. 2047 CBOR_NEG | 7, 2048 // COSE OKP crv. 2049 CBOR_NEG, 2050 // COSE Ed25519. 2051 CBOR_UINT | 6, 2052 // COSE OKP x. 2053 CBOR_NEG | 1, 2054 CBOR_BYTES | 24, 2055 // Length is 32. 2056 32, 2057 // Compressed-y coordinate. 2058 // This will be overwritten later. 2059 0, 2060 0, 2061 0, 2062 0, 2063 0, 2064 0, 2065 0, 2066 0, 2067 0, 2068 0, 2069 0, 2070 0, 2071 0, 2072 0, 2073 0, 2074 0, 2075 0, 2076 0, 2077 0, 2078 0, 2079 0, 2080 0, 2081 0, 2082 0, 2083 0, 2084 0, 2085 0, 2086 0, 2087 0, 2088 0, 2089 0, 2090 0, 2091 CBOR_MAP | 3, 2092 CBOR_TEXT | 11, 2093 b'c', 2094 b'r', 2095 b'e', 2096 b'd', 2097 b'P', 2098 b'r', 2099 b'o', 2100 b't', 2101 b'e', 2102 b'c', 2103 b't', 2104 // userVerificationRequired. 2105 CBOR_UINT | 3, 2106 // CBOR text of length 11. 2107 CBOR_TEXT | 11, 2108 b'h', 2109 b'm', 2110 b'a', 2111 b'c', 2112 b'-', 2113 b's', 2114 b'e', 2115 b'c', 2116 b'r', 2117 b'e', 2118 b't', 2119 CBOR_TRUE, 2120 CBOR_TEXT | 12, 2121 b'm', 2122 b'i', 2123 b'n', 2124 b'P', 2125 b'i', 2126 b'n', 2127 b'L', 2128 b'e', 2129 b'n', 2130 b'g', 2131 b't', 2132 b'h', 2133 CBOR_UINT | 16, 2134 ] 2135 .as_slice(), 2136 ); 2137 attestation_object.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 2138 let sig_key = SigningKey::from_bytes(&[0; 32]); 2139 let ver_key = sig_key.verifying_key(); 2140 let pub_key = ver_key.as_bytes(); 2141 attestation_object[107..139].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 2142 attestation_object[188..220].copy_from_slice(pub_key); 2143 let sig = sig_key.sign(&attestation_object[107..]); 2144 attestation_object[32..96].copy_from_slice(sig.to_bytes().as_slice()); 2145 attestation_object.truncate(261); 2146 assert!(matches!(opts.start_ceremony()?.0.verify( 2147 RP_ID, 2148 &Registration { 2149 response: AuthenticatorAttestation::new( 2150 client_data_json, 2151 attestation_object, 2152 AuthTransports::NONE, 2153 ), 2154 authenticator_attachment: AuthenticatorAttachment::None, 2155 client_extension_results: ClientExtensionsOutputs { 2156 cred_props: None, 2157 prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true, }), 2158 }, 2159 }, 2160 &RegistrationVerificationOptions::<&str, &str>::default(), 2161 )?.static_state.credential_public_key, UncompressedPubKey::Ed25519(k) if k.into_inner() == pub_key)); 2162 Ok(()) 2163 } 2164 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 2165 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 2166 #[expect(clippy::too_many_lines, reason = "a lot to test")] 2167 #[test] 2168 #[cfg(feature = "custom")] 2169 fn eddsa_auth() -> Result<(), AggErr> { 2170 let mut creds = AllowedCredentials::with_capacity(1); 2171 _ = creds.push(AllowedCredential { 2172 credential: PublicKeyCredentialDescriptor { 2173 id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 2174 transports: AuthTransports::NONE, 2175 }, 2176 extension: CredentialSpecificExtension { 2177 prf: Some(PrfInputOwned { 2178 first: Vec::new(), 2179 second: Some(Vec::new()), 2180 ext_req: ExtensionReq::Require, 2181 }), 2182 }, 2183 }); 2184 let mut opts = NonDiscoverableCredentialRequestOptions::second_factor(RP_ID, creds); 2185 opts.options.user_verification = UserVerificationRequirement::Required; 2186 opts.options.challenge = Challenge(0); 2187 opts.options.extensions = AuthExt { prf: None }; 2188 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2189 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 2190 let mut authenticator_data = Vec::with_capacity(164); 2191 authenticator_data.extend_from_slice( 2192 [ 2193 // rpIdHash. 2194 // This will be overwritten later. 2195 0, 2196 0, 2197 0, 2198 0, 2199 0, 2200 0, 2201 0, 2202 0, 2203 0, 2204 0, 2205 0, 2206 0, 2207 0, 2208 0, 2209 0, 2210 0, 2211 0, 2212 0, 2213 0, 2214 0, 2215 0, 2216 0, 2217 0, 2218 0, 2219 0, 2220 0, 2221 0, 2222 0, 2223 0, 2224 0, 2225 0, 2226 0, 2227 // flags. 2228 // UP, UV, and ED (right-to-left). 2229 0b1000_0101, 2230 // signCount. 2231 // 0 as 32-bit big endian. 2232 0, 2233 0, 2234 0, 2235 0, 2236 CBOR_MAP | 1, 2237 CBOR_TEXT | 11, 2238 b'h', 2239 b'm', 2240 b'a', 2241 b'c', 2242 b'-', 2243 b's', 2244 b'e', 2245 b'c', 2246 b'r', 2247 b'e', 2248 b't', 2249 CBOR_BYTES | 24, 2250 // Length is 80. 2251 80, 2252 // Two HMAC outputs concatenated and encrypted. 2253 0, 2254 0, 2255 0, 2256 0, 2257 0, 2258 0, 2259 0, 2260 0, 2261 0, 2262 0, 2263 0, 2264 0, 2265 0, 2266 0, 2267 0, 2268 0, 2269 0, 2270 0, 2271 0, 2272 0, 2273 0, 2274 0, 2275 0, 2276 0, 2277 0, 2278 0, 2279 0, 2280 0, 2281 0, 2282 0, 2283 0, 2284 0, 2285 0, 2286 0, 2287 0, 2288 0, 2289 0, 2290 0, 2291 0, 2292 0, 2293 0, 2294 0, 2295 0, 2296 0, 2297 0, 2298 0, 2299 0, 2300 0, 2301 0, 2302 0, 2303 0, 2304 0, 2305 0, 2306 0, 2307 0, 2308 0, 2309 0, 2310 0, 2311 0, 2312 0, 2313 0, 2314 0, 2315 0, 2316 0, 2317 0, 2318 0, 2319 0, 2320 0, 2321 0, 2322 0, 2323 0, 2324 0, 2325 0, 2326 0, 2327 0, 2328 0, 2329 0, 2330 0, 2331 0, 2332 0, 2333 ] 2334 .as_slice(), 2335 ); 2336 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 2337 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 2338 let ed_priv = SigningKey::from([0; 32]); 2339 let sig = ed_priv.sign(authenticator_data.as_slice()).to_vec(); 2340 authenticator_data.truncate(132); 2341 assert!(!opts.start_ceremony()?.0.verify( 2342 RP_ID, 2343 &NonDiscoverableAuthentication { 2344 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 2345 response: NonDiscoverableAuthenticatorAssertion::with_user( 2346 client_data_json, 2347 authenticator_data, 2348 sig, 2349 UserHandle::from([0]), 2350 ), 2351 authenticator_attachment: AuthenticatorAttachment::None, 2352 }, 2353 &mut AuthenticatedCredential::new( 2354 CredentialId::try_from([0; 16].as_slice())?, 2355 &UserHandle::from([0]), 2356 StaticState { 2357 credential_public_key: CompressedPubKeyOwned::Ed25519(Ed25519PubKey::from( 2358 ed_priv.verifying_key().to_bytes() 2359 ),), 2360 extensions: AuthenticatorExtensionOutputStaticState { 2361 cred_protect: CredentialProtectionPolicy::None, 2362 hmac_secret: Some(true), 2363 }, 2364 client_extension_results: ClientExtensionsOutputsStaticState { 2365 prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true }), 2366 } 2367 }, 2368 DynamicState { 2369 user_verified: true, 2370 backup: Backup::NotEligible, 2371 sign_count: 0, 2372 authenticator_attachment: AuthenticatorAttachment::None, 2373 }, 2374 )?, 2375 &AuthenticationVerificationOptions::<&str, &str>::default(), 2376 )?); 2377 Ok(()) 2378 } 2379 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 2380 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 2381 #[expect(clippy::too_many_lines, reason = "a lot to test")] 2382 #[test] 2383 #[cfg(feature = "custom")] 2384 fn mldsa87_reg() -> Result<(), AggErr> { 2385 let id = UserHandle::from([0]); 2386 let mut opts = CredentialCreationOptions::passkey( 2387 RP_ID, 2388 PublicKeyCredentialUserEntity { 2389 name: "foo".try_into()?, 2390 id: &id, 2391 display_name: DisplayName::Blank, 2392 }, 2393 Vec::new(), 2394 ); 2395 opts.public_key.challenge = Challenge(0); 2396 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2397 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 2398 let mut attestation_object = Vec::with_capacity(2736); 2399 attestation_object.extend_from_slice( 2400 [ 2401 CBOR_MAP | 3, 2402 CBOR_TEXT | 3, 2403 b'f', 2404 b'm', 2405 b't', 2406 CBOR_TEXT | 4, 2407 b'n', 2408 b'o', 2409 b'n', 2410 b'e', 2411 CBOR_TEXT | 7, 2412 b'a', 2413 b't', 2414 b't', 2415 b'S', 2416 b't', 2417 b'm', 2418 b't', 2419 CBOR_MAP, 2420 CBOR_TEXT | 8, 2421 b'a', 2422 b'u', 2423 b't', 2424 b'h', 2425 b'D', 2426 b'a', 2427 b't', 2428 b'a', 2429 CBOR_BYTES | 25, 2430 10, 2431 113, 2432 // `rpIdHash`. 2433 0, 2434 0, 2435 0, 2436 0, 2437 0, 2438 0, 2439 0, 2440 0, 2441 0, 2442 0, 2443 0, 2444 0, 2445 0, 2446 0, 2447 0, 2448 0, 2449 0, 2450 0, 2451 0, 2452 0, 2453 0, 2454 0, 2455 0, 2456 0, 2457 0, 2458 0, 2459 0, 2460 0, 2461 0, 2462 0, 2463 0, 2464 0, 2465 // `flags`. 2466 0b0100_0101, 2467 // `signCount`. 2468 0, 2469 0, 2470 0, 2471 0, 2472 // `aaguid`. 2473 0, 2474 0, 2475 0, 2476 0, 2477 0, 2478 0, 2479 0, 2480 0, 2481 0, 2482 0, 2483 0, 2484 0, 2485 0, 2486 0, 2487 0, 2488 0, 2489 // `credentialIdLength`. 2490 0, 2491 16, 2492 // `credentialId`. 2493 0, 2494 0, 2495 0, 2496 0, 2497 0, 2498 0, 2499 0, 2500 0, 2501 0, 2502 0, 2503 0, 2504 0, 2505 0, 2506 0, 2507 0, 2508 0, 2509 // ML-DSA-87 COSE key. 2510 CBOR_MAP | 3, 2511 // COSE kty. 2512 CBOR_UINT | 1, 2513 // COSE AKP 2514 CBOR_UINT | 7, 2515 // COSE alg. 2516 CBOR_UINT | 3, 2517 CBOR_NEG | 24, 2518 // COSE ML-DSA-87. 2519 49, 2520 // `pub`. 2521 CBOR_NEG, 2522 CBOR_BYTES | 25, 2523 // Length is 2592 as 16-bit big-endian. 2524 10, 2525 32, 2526 // Encoded key. 2527 1, 2528 1, 2529 1, 2530 1, 2531 1, 2532 1, 2533 1, 2534 1, 2535 1, 2536 1, 2537 1, 2538 1, 2539 1, 2540 1, 2541 1, 2542 1, 2543 1, 2544 1, 2545 1, 2546 1, 2547 1, 2548 1, 2549 1, 2550 1, 2551 1, 2552 1, 2553 1, 2554 1, 2555 1, 2556 1, 2557 1, 2558 1, 2559 1, 2560 1, 2561 1, 2562 1, 2563 1, 2564 1, 2565 1, 2566 1, 2567 1, 2568 1, 2569 1, 2570 1, 2571 1, 2572 1, 2573 1, 2574 1, 2575 1, 2576 1, 2577 1, 2578 1, 2579 1, 2580 1, 2581 1, 2582 1, 2583 1, 2584 1, 2585 1, 2586 1, 2587 1, 2588 1, 2589 1, 2590 1, 2591 1, 2592 1, 2593 1, 2594 1, 2595 1, 2596 1, 2597 1, 2598 1, 2599 1, 2600 1, 2601 1, 2602 1, 2603 1, 2604 1, 2605 1, 2606 1, 2607 1, 2608 1, 2609 1, 2610 1, 2611 1, 2612 1, 2613 1, 2614 1, 2615 1, 2616 1, 2617 1, 2618 1, 2619 1, 2620 1, 2621 1, 2622 1, 2623 1, 2624 1, 2625 1, 2626 1, 2627 1, 2628 1, 2629 1, 2630 1, 2631 1, 2632 1, 2633 1, 2634 1, 2635 1, 2636 1, 2637 1, 2638 1, 2639 1, 2640 1, 2641 1, 2642 1, 2643 1, 2644 1, 2645 1, 2646 1, 2647 1, 2648 1, 2649 1, 2650 1, 2651 1, 2652 1, 2653 1, 2654 1, 2655 1, 2656 1, 2657 1, 2658 1, 2659 1, 2660 1, 2661 1, 2662 1, 2663 1, 2664 1, 2665 1, 2666 1, 2667 1, 2668 1, 2669 1, 2670 1, 2671 1, 2672 1, 2673 1, 2674 1, 2675 1, 2676 1, 2677 1, 2678 1, 2679 1, 2680 1, 2681 1, 2682 1, 2683 1, 2684 1, 2685 1, 2686 1, 2687 1, 2688 1, 2689 1, 2690 1, 2691 1, 2692 1, 2693 1, 2694 1, 2695 1, 2696 1, 2697 1, 2698 1, 2699 1, 2700 1, 2701 1, 2702 1, 2703 1, 2704 1, 2705 1, 2706 1, 2707 1, 2708 1, 2709 1, 2710 1, 2711 1, 2712 1, 2713 1, 2714 1, 2715 1, 2716 1, 2717 1, 2718 1, 2719 1, 2720 1, 2721 1, 2722 1, 2723 1, 2724 1, 2725 1, 2726 1, 2727 1, 2728 1, 2729 1, 2730 1, 2731 1, 2732 1, 2733 1, 2734 1, 2735 1, 2736 1, 2737 1, 2738 1, 2739 1, 2740 1, 2741 1, 2742 1, 2743 1, 2744 1, 2745 1, 2746 1, 2747 1, 2748 1, 2749 1, 2750 1, 2751 1, 2752 1, 2753 1, 2754 1, 2755 1, 2756 1, 2757 1, 2758 1, 2759 1, 2760 1, 2761 1, 2762 1, 2763 1, 2764 1, 2765 1, 2766 1, 2767 1, 2768 1, 2769 1, 2770 1, 2771 1, 2772 1, 2773 1, 2774 1, 2775 1, 2776 1, 2777 1, 2778 1, 2779 1, 2780 1, 2781 1, 2782 1, 2783 1, 2784 1, 2785 1, 2786 1, 2787 1, 2788 1, 2789 1, 2790 1, 2791 1, 2792 1, 2793 1, 2794 1, 2795 1, 2796 1, 2797 1, 2798 1, 2799 1, 2800 1, 2801 1, 2802 1, 2803 1, 2804 1, 2805 1, 2806 1, 2807 1, 2808 1, 2809 1, 2810 1, 2811 1, 2812 1, 2813 1, 2814 1, 2815 1, 2816 1, 2817 1, 2818 1, 2819 1, 2820 1, 2821 1, 2822 1, 2823 1, 2824 1, 2825 1, 2826 1, 2827 1, 2828 1, 2829 1, 2830 1, 2831 1, 2832 1, 2833 1, 2834 1, 2835 1, 2836 1, 2837 1, 2838 1, 2839 1, 2840 1, 2841 1, 2842 1, 2843 1, 2844 1, 2845 1, 2846 1, 2847 1, 2848 1, 2849 1, 2850 1, 2851 1, 2852 1, 2853 1, 2854 1, 2855 1, 2856 1, 2857 1, 2858 1, 2859 1, 2860 1, 2861 1, 2862 1, 2863 1, 2864 1, 2865 1, 2866 1, 2867 1, 2868 1, 2869 1, 2870 1, 2871 1, 2872 1, 2873 1, 2874 1, 2875 1, 2876 1, 2877 1, 2878 1, 2879 1, 2880 1, 2881 1, 2882 1, 2883 1, 2884 1, 2885 1, 2886 1, 2887 1, 2888 1, 2889 1, 2890 1, 2891 1, 2892 1, 2893 1, 2894 1, 2895 1, 2896 1, 2897 1, 2898 1, 2899 1, 2900 1, 2901 1, 2902 1, 2903 1, 2904 1, 2905 1, 2906 1, 2907 1, 2908 1, 2909 1, 2910 1, 2911 1, 2912 1, 2913 1, 2914 1, 2915 1, 2916 1, 2917 1, 2918 1, 2919 1, 2920 1, 2921 1, 2922 1, 2923 1, 2924 1, 2925 1, 2926 1, 2927 1, 2928 1, 2929 1, 2930 1, 2931 1, 2932 1, 2933 1, 2934 1, 2935 1, 2936 1, 2937 1, 2938 1, 2939 1, 2940 1, 2941 1, 2942 1, 2943 1, 2944 1, 2945 1, 2946 1, 2947 1, 2948 1, 2949 1, 2950 1, 2951 1, 2952 1, 2953 1, 2954 1, 2955 1, 2956 1, 2957 1, 2958 1, 2959 1, 2960 1, 2961 1, 2962 1, 2963 1, 2964 1, 2965 1, 2966 1, 2967 1, 2968 1, 2969 1, 2970 1, 2971 1, 2972 1, 2973 1, 2974 1, 2975 1, 2976 1, 2977 1, 2978 1, 2979 1, 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 1, 3013 1, 3014 1, 3015 1, 3016 1, 3017 1, 3018 1, 3019 1, 3020 1, 3021 1, 3022 1, 3023 1, 3024 1, 3025 1, 3026 1, 3027 1, 3028 1, 3029 1, 3030 1, 3031 1, 3032 1, 3033 1, 3034 1, 3035 1, 3036 1, 3037 1, 3038 1, 3039 1, 3040 1, 3041 1, 3042 1, 3043 1, 3044 1, 3045 1, 3046 1, 3047 1, 3048 1, 3049 1, 3050 1, 3051 1, 3052 1, 3053 1, 3054 1, 3055 1, 3056 1, 3057 1, 3058 1, 3059 1, 3060 1, 3061 1, 3062 1, 3063 1, 3064 1, 3065 1, 3066 1, 3067 1, 3068 1, 3069 1, 3070 1, 3071 1, 3072 1, 3073 1, 3074 1, 3075 1, 3076 1, 3077 1, 3078 1, 3079 1, 3080 1, 3081 1, 3082 1, 3083 1, 3084 1, 3085 1, 3086 1, 3087 1, 3088 1, 3089 1, 3090 1, 3091 1, 3092 1, 3093 1, 3094 1, 3095 1, 3096 1, 3097 1, 3098 1, 3099 1, 3100 1, 3101 1, 3102 1, 3103 1, 3104 1, 3105 1, 3106 1, 3107 1, 3108 1, 3109 1, 3110 1, 3111 1, 3112 1, 3113 1, 3114 1, 3115 1, 3116 1, 3117 1, 3118 1, 3119 1, 3120 1, 3121 1, 3122 1, 3123 1, 3124 1, 3125 1, 3126 1, 3127 1, 3128 1, 3129 1, 3130 1, 3131 1, 3132 1, 3133 1, 3134 1, 3135 1, 3136 1, 3137 1, 3138 1, 3139 1, 3140 1, 3141 1, 3142 1, 3143 1, 3144 1, 3145 1, 3146 1, 3147 1, 3148 1, 3149 1, 3150 1, 3151 1, 3152 1, 3153 1, 3154 1, 3155 1, 3156 1, 3157 1, 3158 1, 3159 1, 3160 1, 3161 1, 3162 1, 3163 1, 3164 1, 3165 1, 3166 1, 3167 1, 3168 1, 3169 1, 3170 1, 3171 1, 3172 1, 3173 1, 3174 1, 3175 1, 3176 1, 3177 1, 3178 1, 3179 1, 3180 1, 3181 1, 3182 1, 3183 1, 3184 1, 3185 1, 3186 1, 3187 1, 3188 1, 3189 1, 3190 1, 3191 1, 3192 1, 3193 1, 3194 1, 3195 1, 3196 1, 3197 1, 3198 1, 3199 1, 3200 1, 3201 1, 3202 1, 3203 1, 3204 1, 3205 1, 3206 1, 3207 1, 3208 1, 3209 1, 3210 1, 3211 1, 3212 1, 3213 1, 3214 1, 3215 1, 3216 1, 3217 1, 3218 1, 3219 1, 3220 1, 3221 1, 3222 1, 3223 1, 3224 1, 3225 1, 3226 1, 3227 1, 3228 1, 3229 1, 3230 1, 3231 1, 3232 1, 3233 1, 3234 1, 3235 1, 3236 1, 3237 1, 3238 1, 3239 1, 3240 1, 3241 1, 3242 1, 3243 1, 3244 1, 3245 1, 3246 1, 3247 1, 3248 1, 3249 1, 3250 1, 3251 1, 3252 1, 3253 1, 3254 1, 3255 1, 3256 1, 3257 1, 3258 1, 3259 1, 3260 1, 3261 1, 3262 1, 3263 1, 3264 1, 3265 1, 3266 1, 3267 1, 3268 1, 3269 1, 3270 1, 3271 1, 3272 1, 3273 1, 3274 1, 3275 1, 3276 1, 3277 1, 3278 1, 3279 1, 3280 1, 3281 1, 3282 1, 3283 1, 3284 1, 3285 1, 3286 1, 3287 1, 3288 1, 3289 1, 3290 1, 3291 1, 3292 1, 3293 1, 3294 1, 3295 1, 3296 1, 3297 1, 3298 1, 3299 1, 3300 1, 3301 1, 3302 1, 3303 1, 3304 1, 3305 1, 3306 1, 3307 1, 3308 1, 3309 1, 3310 1, 3311 1, 3312 1, 3313 1, 3314 1, 3315 1, 3316 1, 3317 1, 3318 1, 3319 1, 3320 1, 3321 1, 3322 1, 3323 1, 3324 1, 3325 1, 3326 1, 3327 1, 3328 1, 3329 1, 3330 1, 3331 1, 3332 1, 3333 1, 3334 1, 3335 1, 3336 1, 3337 1, 3338 1, 3339 1, 3340 1, 3341 1, 3342 1, 3343 1, 3344 1, 3345 1, 3346 1, 3347 1, 3348 1, 3349 1, 3350 1, 3351 1, 3352 1, 3353 1, 3354 1, 3355 1, 3356 1, 3357 1, 3358 1, 3359 1, 3360 1, 3361 1, 3362 1, 3363 1, 3364 1, 3365 1, 3366 1, 3367 1, 3368 1, 3369 1, 3370 1, 3371 1, 3372 1, 3373 1, 3374 1, 3375 1, 3376 1, 3377 1, 3378 1, 3379 1, 3380 1, 3381 1, 3382 1, 3383 1, 3384 1, 3385 1, 3386 1, 3387 1, 3388 1, 3389 1, 3390 1, 3391 1, 3392 1, 3393 1, 3394 1, 3395 1, 3396 1, 3397 1, 3398 1, 3399 1, 3400 1, 3401 1, 3402 1, 3403 1, 3404 1, 3405 1, 3406 1, 3407 1, 3408 1, 3409 1, 3410 1, 3411 1, 3412 1, 3413 1, 3414 1, 3415 1, 3416 1, 3417 1, 3418 1, 3419 1, 3420 1, 3421 1, 3422 1, 3423 1, 3424 1, 3425 1, 3426 1, 3427 1, 3428 1, 3429 1, 3430 1, 3431 1, 3432 1, 3433 1, 3434 1, 3435 1, 3436 1, 3437 1, 3438 1, 3439 1, 3440 1, 3441 1, 3442 1, 3443 1, 3444 1, 3445 1, 3446 1, 3447 1, 3448 1, 3449 1, 3450 1, 3451 1, 3452 1, 3453 1, 3454 1, 3455 1, 3456 1, 3457 1, 3458 1, 3459 1, 3460 1, 3461 1, 3462 1, 3463 1, 3464 1, 3465 1, 3466 1, 3467 1, 3468 1, 3469 1, 3470 1, 3471 1, 3472 1, 3473 1, 3474 1, 3475 1, 3476 1, 3477 1, 3478 1, 3479 1, 3480 1, 3481 1, 3482 1, 3483 1, 3484 1, 3485 1, 3486 1, 3487 1, 3488 1, 3489 1, 3490 1, 3491 1, 3492 1, 3493 1, 3494 1, 3495 1, 3496 1, 3497 1, 3498 1, 3499 1, 3500 1, 3501 1, 3502 1, 3503 1, 3504 1, 3505 1, 3506 1, 3507 1, 3508 1, 3509 1, 3510 1, 3511 1, 3512 1, 3513 1, 3514 1, 3515 1, 3516 1, 3517 1, 3518 1, 3519 1, 3520 1, 3521 1, 3522 1, 3523 1, 3524 1, 3525 1, 3526 1, 3527 1, 3528 1, 3529 1, 3530 1, 3531 1, 3532 1, 3533 1, 3534 1, 3535 1, 3536 1, 3537 1, 3538 1, 3539 1, 3540 1, 3541 1, 3542 1, 3543 1, 3544 1, 3545 1, 3546 1, 3547 1, 3548 1, 3549 1, 3550 1, 3551 1, 3552 1, 3553 1, 3554 1, 3555 1, 3556 1, 3557 1, 3558 1, 3559 1, 3560 1, 3561 1, 3562 1, 3563 1, 3564 1, 3565 1, 3566 1, 3567 1, 3568 1, 3569 1, 3570 1, 3571 1, 3572 1, 3573 1, 3574 1, 3575 1, 3576 1, 3577 1, 3578 1, 3579 1, 3580 1, 3581 1, 3582 1, 3583 1, 3584 1, 3585 1, 3586 1, 3587 1, 3588 1, 3589 1, 3590 1, 3591 1, 3592 1, 3593 1, 3594 1, 3595 1, 3596 1, 3597 1, 3598 1, 3599 1, 3600 1, 3601 1, 3602 1, 3603 1, 3604 1, 3605 1, 3606 1, 3607 1, 3608 1, 3609 1, 3610 1, 3611 1, 3612 1, 3613 1, 3614 1, 3615 1, 3616 1, 3617 1, 3618 1, 3619 1, 3620 1, 3621 1, 3622 1, 3623 1, 3624 1, 3625 1, 3626 1, 3627 1, 3628 1, 3629 1, 3630 1, 3631 1, 3632 1, 3633 1, 3634 1, 3635 1, 3636 1, 3637 1, 3638 1, 3639 1, 3640 1, 3641 1, 3642 1, 3643 1, 3644 1, 3645 1, 3646 1, 3647 1, 3648 1, 3649 1, 3650 1, 3651 1, 3652 1, 3653 1, 3654 1, 3655 1, 3656 1, 3657 1, 3658 1, 3659 1, 3660 1, 3661 1, 3662 1, 3663 1, 3664 1, 3665 1, 3666 1, 3667 1, 3668 1, 3669 1, 3670 1, 3671 1, 3672 1, 3673 1, 3674 1, 3675 1, 3676 1, 3677 1, 3678 1, 3679 1, 3680 1, 3681 1, 3682 1, 3683 1, 3684 1, 3685 1, 3686 1, 3687 1, 3688 1, 3689 1, 3690 1, 3691 1, 3692 1, 3693 1, 3694 1, 3695 1, 3696 1, 3697 1, 3698 1, 3699 1, 3700 1, 3701 1, 3702 1, 3703 1, 3704 1, 3705 1, 3706 1, 3707 1, 3708 1, 3709 1, 3710 1, 3711 1, 3712 1, 3713 1, 3714 1, 3715 1, 3716 1, 3717 1, 3718 1, 3719 1, 3720 1, 3721 1, 3722 1, 3723 1, 3724 1, 3725 1, 3726 1, 3727 1, 3728 1, 3729 1, 3730 1, 3731 1, 3732 1, 3733 1, 3734 1, 3735 1, 3736 1, 3737 1, 3738 1, 3739 1, 3740 1, 3741 1, 3742 1, 3743 1, 3744 1, 3745 1, 3746 1, 3747 1, 3748 1, 3749 1, 3750 1, 3751 1, 3752 1, 3753 1, 3754 1, 3755 1, 3756 1, 3757 1, 3758 1, 3759 1, 3760 1, 3761 1, 3762 1, 3763 1, 3764 1, 3765 1, 3766 1, 3767 1, 3768 1, 3769 1, 3770 1, 3771 1, 3772 1, 3773 1, 3774 1, 3775 1, 3776 1, 3777 1, 3778 1, 3779 1, 3780 1, 3781 1, 3782 1, 3783 1, 3784 1, 3785 1, 3786 1, 3787 1, 3788 1, 3789 1, 3790 1, 3791 1, 3792 1, 3793 1, 3794 1, 3795 1, 3796 1, 3797 1, 3798 1, 3799 1, 3800 1, 3801 1, 3802 1, 3803 1, 3804 1, 3805 1, 3806 1, 3807 1, 3808 1, 3809 1, 3810 1, 3811 1, 3812 1, 3813 1, 3814 1, 3815 1, 3816 1, 3817 1, 3818 1, 3819 1, 3820 1, 3821 1, 3822 1, 3823 1, 3824 1, 3825 1, 3826 1, 3827 1, 3828 1, 3829 1, 3830 1, 3831 1, 3832 1, 3833 1, 3834 1, 3835 1, 3836 1, 3837 1, 3838 1, 3839 1, 3840 1, 3841 1, 3842 1, 3843 1, 3844 1, 3845 1, 3846 1, 3847 1, 3848 1, 3849 1, 3850 1, 3851 1, 3852 1, 3853 1, 3854 1, 3855 1, 3856 1, 3857 1, 3858 1, 3859 1, 3860 1, 3861 1, 3862 1, 3863 1, 3864 1, 3865 1, 3866 1, 3867 1, 3868 1, 3869 1, 3870 1, 3871 1, 3872 1, 3873 1, 3874 1, 3875 1, 3876 1, 3877 1, 3878 1, 3879 1, 3880 1, 3881 1, 3882 1, 3883 1, 3884 1, 3885 1, 3886 1, 3887 1, 3888 1, 3889 1, 3890 1, 3891 1, 3892 1, 3893 1, 3894 1, 3895 1, 3896 1, 3897 1, 3898 1, 3899 1, 3900 1, 3901 1, 3902 1, 3903 1, 3904 1, 3905 1, 3906 1, 3907 1, 3908 1, 3909 1, 3910 1, 3911 1, 3912 1, 3913 1, 3914 1, 3915 1, 3916 1, 3917 1, 3918 1, 3919 1, 3920 1, 3921 1, 3922 1, 3923 1, 3924 1, 3925 1, 3926 1, 3927 1, 3928 1, 3929 1, 3930 1, 3931 1, 3932 1, 3933 1, 3934 1, 3935 1, 3936 1, 3937 1, 3938 1, 3939 1, 3940 1, 3941 1, 3942 1, 3943 1, 3944 1, 3945 1, 3946 1, 3947 1, 3948 1, 3949 1, 3950 1, 3951 1, 3952 1, 3953 1, 3954 1, 3955 1, 3956 1, 3957 1, 3958 1, 3959 1, 3960 1, 3961 1, 3962 1, 3963 1, 3964 1, 3965 1, 3966 1, 3967 1, 3968 1, 3969 1, 3970 1, 3971 1, 3972 1, 3973 1, 3974 1, 3975 1, 3976 1, 3977 1, 3978 1, 3979 1, 3980 1, 3981 1, 3982 1, 3983 1, 3984 1, 3985 1, 3986 1, 3987 1, 3988 1, 3989 1, 3990 1, 3991 1, 3992 1, 3993 1, 3994 1, 3995 1, 3996 1, 3997 1, 3998 1, 3999 1, 4000 1, 4001 1, 4002 1, 4003 1, 4004 1, 4005 1, 4006 1, 4007 1, 4008 1, 4009 1, 4010 1, 4011 1, 4012 1, 4013 1, 4014 1, 4015 1, 4016 1, 4017 1, 4018 1, 4019 1, 4020 1, 4021 1, 4022 1, 4023 1, 4024 1, 4025 1, 4026 1, 4027 1, 4028 1, 4029 1, 4030 1, 4031 1, 4032 1, 4033 1, 4034 1, 4035 1, 4036 1, 4037 1, 4038 1, 4039 1, 4040 1, 4041 1, 4042 1, 4043 1, 4044 1, 4045 1, 4046 1, 4047 1, 4048 1, 4049 1, 4050 1, 4051 1, 4052 1, 4053 1, 4054 1, 4055 1, 4056 1, 4057 1, 4058 1, 4059 1, 4060 1, 4061 1, 4062 1, 4063 1, 4064 1, 4065 1, 4066 1, 4067 1, 4068 1, 4069 1, 4070 1, 4071 1, 4072 1, 4073 1, 4074 1, 4075 1, 4076 1, 4077 1, 4078 1, 4079 1, 4080 1, 4081 1, 4082 1, 4083 1, 4084 1, 4085 1, 4086 1, 4087 1, 4088 1, 4089 1, 4090 1, 4091 1, 4092 1, 4093 1, 4094 1, 4095 1, 4096 1, 4097 1, 4098 1, 4099 1, 4100 1, 4101 1, 4102 1, 4103 1, 4104 1, 4105 1, 4106 1, 4107 1, 4108 1, 4109 1, 4110 1, 4111 1, 4112 1, 4113 1, 4114 1, 4115 1, 4116 1, 4117 1, 4118 1, 4119 1, 4120 1, 4121 1, 4122 1, 4123 1, 4124 1, 4125 1, 4126 1, 4127 1, 4128 1, 4129 1, 4130 1, 4131 1, 4132 1, 4133 1, 4134 1, 4135 1, 4136 1, 4137 1, 4138 1, 4139 1, 4140 1, 4141 1, 4142 1, 4143 1, 4144 1, 4145 1, 4146 1, 4147 1, 4148 1, 4149 1, 4150 1, 4151 1, 4152 1, 4153 1, 4154 1, 4155 1, 4156 1, 4157 1, 4158 1, 4159 1, 4160 1, 4161 1, 4162 1, 4163 1, 4164 1, 4165 1, 4166 1, 4167 1, 4168 1, 4169 1, 4170 1, 4171 1, 4172 1, 4173 1, 4174 1, 4175 1, 4176 1, 4177 1, 4178 1, 4179 1, 4180 1, 4181 1, 4182 1, 4183 1, 4184 1, 4185 1, 4186 1, 4187 1, 4188 1, 4189 1, 4190 1, 4191 1, 4192 1, 4193 1, 4194 1, 4195 1, 4196 1, 4197 1, 4198 1, 4199 1, 4200 1, 4201 1, 4202 1, 4203 1, 4204 1, 4205 1, 4206 1, 4207 1, 4208 1, 4209 1, 4210 1, 4211 1, 4212 1, 4213 1, 4214 1, 4215 1, 4216 1, 4217 1, 4218 1, 4219 1, 4220 1, 4221 1, 4222 1, 4223 1, 4224 1, 4225 1, 4226 1, 4227 1, 4228 1, 4229 1, 4230 1, 4231 1, 4232 1, 4233 1, 4234 1, 4235 1, 4236 1, 4237 1, 4238 1, 4239 1, 4240 1, 4241 1, 4242 1, 4243 1, 4244 1, 4245 1, 4246 1, 4247 1, 4248 1, 4249 1, 4250 1, 4251 1, 4252 1, 4253 1, 4254 1, 4255 1, 4256 1, 4257 1, 4258 1, 4259 1, 4260 1, 4261 1, 4262 1, 4263 1, 4264 1, 4265 1, 4266 1, 4267 1, 4268 1, 4269 1, 4270 1, 4271 1, 4272 1, 4273 1, 4274 1, 4275 1, 4276 1, 4277 1, 4278 1, 4279 1, 4280 1, 4281 1, 4282 1, 4283 1, 4284 1, 4285 1, 4286 1, 4287 1, 4288 1, 4289 1, 4290 1, 4291 1, 4292 1, 4293 1, 4294 1, 4295 1, 4296 1, 4297 1, 4298 1, 4299 1, 4300 1, 4301 1, 4302 1, 4303 1, 4304 1, 4305 1, 4306 1, 4307 1, 4308 1, 4309 1, 4310 1, 4311 1, 4312 1, 4313 1, 4314 1, 4315 1, 4316 1, 4317 1, 4318 1, 4319 1, 4320 1, 4321 1, 4322 1, 4323 1, 4324 1, 4325 1, 4326 1, 4327 1, 4328 1, 4329 1, 4330 1, 4331 1, 4332 1, 4333 1, 4334 1, 4335 1, 4336 1, 4337 1, 4338 1, 4339 1, 4340 1, 4341 1, 4342 1, 4343 1, 4344 1, 4345 1, 4346 1, 4347 1, 4348 1, 4349 1, 4350 1, 4351 1, 4352 1, 4353 1, 4354 1, 4355 1, 4356 1, 4357 1, 4358 1, 4359 1, 4360 1, 4361 1, 4362 1, 4363 1, 4364 1, 4365 1, 4366 1, 4367 1, 4368 1, 4369 1, 4370 1, 4371 1, 4372 1, 4373 1, 4374 1, 4375 1, 4376 1, 4377 1, 4378 1, 4379 1, 4380 1, 4381 1, 4382 1, 4383 1, 4384 1, 4385 1, 4386 1, 4387 1, 4388 1, 4389 1, 4390 1, 4391 1, 4392 1, 4393 1, 4394 1, 4395 1, 4396 1, 4397 1, 4398 1, 4399 1, 4400 1, 4401 1, 4402 1, 4403 1, 4404 1, 4405 1, 4406 1, 4407 1, 4408 1, 4409 1, 4410 1, 4411 1, 4412 1, 4413 1, 4414 1, 4415 1, 4416 1, 4417 1, 4418 1, 4419 1, 4420 1, 4421 1, 4422 1, 4423 1, 4424 1, 4425 1, 4426 1, 4427 1, 4428 1, 4429 1, 4430 1, 4431 1, 4432 1, 4433 1, 4434 1, 4435 1, 4436 1, 4437 1, 4438 1, 4439 1, 4440 1, 4441 1, 4442 1, 4443 1, 4444 1, 4445 1, 4446 1, 4447 1, 4448 1, 4449 1, 4450 1, 4451 1, 4452 1, 4453 1, 4454 1, 4455 1, 4456 1, 4457 1, 4458 1, 4459 1, 4460 1, 4461 1, 4462 1, 4463 1, 4464 1, 4465 1, 4466 1, 4467 1, 4468 1, 4469 1, 4470 1, 4471 1, 4472 1, 4473 1, 4474 1, 4475 1, 4476 1, 4477 1, 4478 1, 4479 1, 4480 1, 4481 1, 4482 1, 4483 1, 4484 1, 4485 1, 4486 1, 4487 1, 4488 1, 4489 1, 4490 1, 4491 1, 4492 1, 4493 1, 4494 1, 4495 1, 4496 1, 4497 1, 4498 1, 4499 1, 4500 1, 4501 1, 4502 1, 4503 1, 4504 1, 4505 1, 4506 1, 4507 1, 4508 1, 4509 1, 4510 1, 4511 1, 4512 1, 4513 1, 4514 1, 4515 1, 4516 1, 4517 1, 4518 1, 4519 1, 4520 1, 4521 1, 4522 1, 4523 1, 4524 1, 4525 1, 4526 1, 4527 1, 4528 1, 4529 1, 4530 1, 4531 1, 4532 1, 4533 1, 4534 1, 4535 1, 4536 1, 4537 1, 4538 1, 4539 1, 4540 1, 4541 1, 4542 1, 4543 1, 4544 1, 4545 1, 4546 1, 4547 1, 4548 1, 4549 1, 4550 1, 4551 1, 4552 1, 4553 1, 4554 1, 4555 1, 4556 1, 4557 1, 4558 1, 4559 1, 4560 1, 4561 1, 4562 1, 4563 1, 4564 1, 4565 1, 4566 1, 4567 1, 4568 1, 4569 1, 4570 1, 4571 1, 4572 1, 4573 1, 4574 1, 4575 1, 4576 1, 4577 1, 4578 1, 4579 1, 4580 1, 4581 1, 4582 1, 4583 1, 4584 1, 4585 1, 4586 1, 4587 1, 4588 1, 4589 1, 4590 1, 4591 1, 4592 1, 4593 1, 4594 1, 4595 1, 4596 1, 4597 1, 4598 1, 4599 1, 4600 1, 4601 1, 4602 1, 4603 1, 4604 1, 4605 1, 4606 1, 4607 1, 4608 1, 4609 1, 4610 1, 4611 1, 4612 1, 4613 1, 4614 1, 4615 1, 4616 1, 4617 1, 4618 1, 4619 1, 4620 1, 4621 1, 4622 1, 4623 1, 4624 1, 4625 1, 4626 1, 4627 1, 4628 1, 4629 1, 4630 1, 4631 1, 4632 1, 4633 1, 4634 1, 4635 1, 4636 1, 4637 1, 4638 1, 4639 1, 4640 1, 4641 1, 4642 1, 4643 1, 4644 1, 4645 1, 4646 1, 4647 1, 4648 1, 4649 1, 4650 1, 4651 1, 4652 1, 4653 1, 4654 1, 4655 1, 4656 1, 4657 1, 4658 1, 4659 1, 4660 1, 4661 1, 4662 1, 4663 1, 4664 1, 4665 1, 4666 1, 4667 1, 4668 1, 4669 1, 4670 1, 4671 1, 4672 1, 4673 1, 4674 1, 4675 1, 4676 1, 4677 1, 4678 1, 4679 1, 4680 1, 4681 1, 4682 1, 4683 1, 4684 1, 4685 1, 4686 1, 4687 1, 4688 1, 4689 1, 4690 1, 4691 1, 4692 1, 4693 1, 4694 1, 4695 1, 4696 1, 4697 1, 4698 1, 4699 1, 4700 1, 4701 1, 4702 1, 4703 1, 4704 1, 4705 1, 4706 1, 4707 1, 4708 1, 4709 1, 4710 1, 4711 1, 4712 1, 4713 1, 4714 1, 4715 1, 4716 1, 4717 1, 4718 1, 4719 1, 4720 1, 4721 1, 4722 1, 4723 1, 4724 1, 4725 1, 4726 1, 4727 1, 4728 1, 4729 1, 4730 1, 4731 1, 4732 1, 4733 1, 4734 1, 4735 1, 4736 1, 4737 1, 4738 1, 4739 1, 4740 1, 4741 1, 4742 1, 4743 1, 4744 1, 4745 1, 4746 1, 4747 1, 4748 1, 4749 1, 4750 1, 4751 1, 4752 1, 4753 1, 4754 1, 4755 1, 4756 1, 4757 1, 4758 1, 4759 1, 4760 1, 4761 1, 4762 1, 4763 1, 4764 1, 4765 1, 4766 1, 4767 1, 4768 1, 4769 1, 4770 1, 4771 1, 4772 1, 4773 1, 4774 1, 4775 1, 4776 1, 4777 1, 4778 1, 4779 1, 4780 1, 4781 1, 4782 1, 4783 1, 4784 1, 4785 1, 4786 1, 4787 1, 4788 1, 4789 1, 4790 1, 4791 1, 4792 1, 4793 1, 4794 1, 4795 1, 4796 1, 4797 1, 4798 1, 4799 1, 4800 1, 4801 1, 4802 1, 4803 1, 4804 1, 4805 1, 4806 1, 4807 1, 4808 1, 4809 1, 4810 1, 4811 1, 4812 1, 4813 1, 4814 1, 4815 1, 4816 1, 4817 1, 4818 1, 4819 1, 4820 1, 4821 1, 4822 1, 4823 1, 4824 1, 4825 1, 4826 1, 4827 1, 4828 1, 4829 1, 4830 1, 4831 1, 4832 1, 4833 1, 4834 1, 4835 1, 4836 1, 4837 1, 4838 1, 4839 1, 4840 1, 4841 1, 4842 1, 4843 1, 4844 1, 4845 1, 4846 1, 4847 1, 4848 1, 4849 1, 4850 1, 4851 1, 4852 1, 4853 1, 4854 1, 4855 1, 4856 1, 4857 1, 4858 1, 4859 1, 4860 1, 4861 1, 4862 1, 4863 1, 4864 1, 4865 1, 4866 1, 4867 1, 4868 1, 4869 1, 4870 1, 4871 1, 4872 1, 4873 1, 4874 1, 4875 1, 4876 1, 4877 1, 4878 1, 4879 1, 4880 1, 4881 1, 4882 1, 4883 1, 4884 1, 4885 1, 4886 1, 4887 1, 4888 1, 4889 1, 4890 1, 4891 1, 4892 1, 4893 1, 4894 1, 4895 1, 4896 1, 4897 1, 4898 1, 4899 1, 4900 1, 4901 1, 4902 1, 4903 1, 4904 1, 4905 1, 4906 1, 4907 1, 4908 1, 4909 1, 4910 1, 4911 1, 4912 1, 4913 1, 4914 1, 4915 1, 4916 1, 4917 1, 4918 1, 4919 1, 4920 1, 4921 1, 4922 1, 4923 1, 4924 1, 4925 1, 4926 1, 4927 1, 4928 1, 4929 1, 4930 1, 4931 1, 4932 1, 4933 1, 4934 1, 4935 1, 4936 1, 4937 1, 4938 1, 4939 1, 4940 1, 4941 1, 4942 1, 4943 1, 4944 1, 4945 1, 4946 1, 4947 1, 4948 1, 4949 1, 4950 1, 4951 1, 4952 1, 4953 1, 4954 1, 4955 1, 4956 1, 4957 1, 4958 1, 4959 1, 4960 1, 4961 1, 4962 1, 4963 1, 4964 1, 4965 1, 4966 1, 4967 1, 4968 1, 4969 1, 4970 1, 4971 1, 4972 1, 4973 1, 4974 1, 4975 1, 4976 1, 4977 1, 4978 1, 4979 1, 4980 1, 4981 1, 4982 1, 4983 1, 4984 1, 4985 1, 4986 1, 4987 1, 4988 1, 4989 1, 4990 1, 4991 1, 4992 1, 4993 1, 4994 1, 4995 1, 4996 1, 4997 1, 4998 1, 4999 1, 5000 1, 5001 1, 5002 1, 5003 1, 5004 1, 5005 1, 5006 1, 5007 1, 5008 1, 5009 1, 5010 1, 5011 1, 5012 1, 5013 1, 5014 1, 5015 1, 5016 1, 5017 1, 5018 1, 5019 1, 5020 1, 5021 1, 5022 1, 5023 1, 5024 1, 5025 1, 5026 1, 5027 1, 5028 1, 5029 1, 5030 1, 5031 1, 5032 1, 5033 1, 5034 1, 5035 1, 5036 1, 5037 1, 5038 1, 5039 1, 5040 1, 5041 1, 5042 1, 5043 1, 5044 1, 5045 1, 5046 1, 5047 1, 5048 1, 5049 1, 5050 1, 5051 1, 5052 1, 5053 1, 5054 1, 5055 1, 5056 1, 5057 1, 5058 1, 5059 1, 5060 1, 5061 1, 5062 1, 5063 1, 5064 1, 5065 1, 5066 1, 5067 1, 5068 1, 5069 1, 5070 1, 5071 1, 5072 1, 5073 1, 5074 1, 5075 1, 5076 1, 5077 1, 5078 1, 5079 1, 5080 1, 5081 1, 5082 1, 5083 1, 5084 1, 5085 1, 5086 1, 5087 1, 5088 1, 5089 1, 5090 1, 5091 1, 5092 1, 5093 1, 5094 1, 5095 1, 5096 1, 5097 1, 5098 1, 5099 1, 5100 1, 5101 1, 5102 1, 5103 1, 5104 1, 5105 1, 5106 1, 5107 1, 5108 1, 5109 1, 5110 1, 5111 1, 5112 1, 5113 1, 5114 1, 5115 1, 5116 1, 5117 1, 5118 1, 5119 ] 5120 .as_slice(), 5121 ); 5122 attestation_object[31..63].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 5123 assert!(matches!(opts.start_ceremony()?.0.verify( 5124 RP_ID, 5125 &Registration { 5126 response: AuthenticatorAttestation::new( 5127 client_data_json, 5128 attestation_object, 5129 AuthTransports::NONE, 5130 ), 5131 authenticator_attachment: AuthenticatorAttachment::None, 5132 client_extension_results: ClientExtensionsOutputs { 5133 cred_props: None, 5134 prf: None, 5135 }, 5136 }, 5137 &RegistrationVerificationOptions::<&str, &str>::default(), 5138 )?.static_state.credential_public_key, UncompressedPubKey::MlDsa87(k) if **k.inner() == [1; 2592])); 5139 Ok(()) 5140 } 5141 #[expect( 5142 clippy::panic_in_result_fn, 5143 clippy::unwrap_in_result, 5144 clippy::unwrap_used, 5145 reason = "OK in tests" 5146 )] 5147 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 5148 #[test] 5149 #[cfg(feature = "custom")] 5150 fn mldsa87_auth() -> Result<(), AggErr> { 5151 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 5152 opts.public_key.challenge = Challenge(0); 5153 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 5154 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 5155 let mut authenticator_data = Vec::with_capacity(69); 5156 authenticator_data.extend_from_slice( 5157 [ 5158 // rpIdHash. 5159 // This will be overwritten later. 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 // flags. 5193 // UP and UV (right-to-left). 5194 0b0000_0101, 5195 // signCount. 5196 // 0 as 32-bit big endian. 5197 0, 5198 0, 5199 0, 5200 0, 5201 ] 5202 .as_slice(), 5203 ); 5204 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 5205 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 5206 let mldsa87_key = MlDsaSigKey::<MlDsa87>::from_seed((&[0; 32]).into()); 5207 let sig: MlDsaSignature<MlDsa87> = mldsa87_key.sign(authenticator_data.as_slice()); 5208 let pub_key = mldsa87_key.verifying_key().encode(); 5209 authenticator_data.truncate(37); 5210 assert!(!opts.start_ceremony()?.0.verify( 5211 RP_ID, 5212 &DiscoverableAuthentication { 5213 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 5214 response: DiscoverableAuthenticatorAssertion::new( 5215 client_data_json, 5216 authenticator_data, 5217 sig.encode().0.to_vec(), 5218 UserHandle::from([0]), 5219 ), 5220 authenticator_attachment: AuthenticatorAttachment::None, 5221 }, 5222 &mut AuthenticatedCredential::new( 5223 CredentialId::try_from([0; 16].as_slice())?, 5224 &UserHandle::from([0]), 5225 StaticState { 5226 credential_public_key: CompressedPubKeyOwned::MlDsa87( 5227 MlDsa87PubKey::try_from(Box::from(pub_key.as_slice())).unwrap() 5228 ), 5229 extensions: AuthenticatorExtensionOutputStaticState { 5230 cred_protect: CredentialProtectionPolicy::None, 5231 hmac_secret: None, 5232 }, 5233 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 5234 }, 5235 DynamicState { 5236 user_verified: true, 5237 backup: Backup::NotEligible, 5238 sign_count: 0, 5239 authenticator_attachment: AuthenticatorAttachment::None, 5240 }, 5241 )?, 5242 &AuthenticationVerificationOptions::<&str, &str>::default(), 5243 )?); 5244 Ok(()) 5245 } 5246 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 5247 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 5248 #[expect(clippy::too_many_lines, reason = "a lot to test")] 5249 #[test] 5250 #[cfg(feature = "custom")] 5251 fn mldsa65_reg() -> Result<(), AggErr> { 5252 let id = UserHandle::from([0]); 5253 let mut opts = CredentialCreationOptions::passkey( 5254 RP_ID, 5255 PublicKeyCredentialUserEntity { 5256 name: "foo".try_into()?, 5257 id: &id, 5258 display_name: DisplayName::Blank, 5259 }, 5260 Vec::new(), 5261 ); 5262 opts.public_key.challenge = Challenge(0); 5263 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 5264 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 5265 let mut attestation_object = Vec::with_capacity(2096); 5266 attestation_object.extend_from_slice( 5267 [ 5268 CBOR_MAP | 3, 5269 CBOR_TEXT | 3, 5270 b'f', 5271 b'm', 5272 b't', 5273 CBOR_TEXT | 4, 5274 b'n', 5275 b'o', 5276 b'n', 5277 b'e', 5278 CBOR_TEXT | 7, 5279 b'a', 5280 b't', 5281 b't', 5282 b'S', 5283 b't', 5284 b'm', 5285 b't', 5286 CBOR_MAP, 5287 CBOR_TEXT | 8, 5288 b'a', 5289 b'u', 5290 b't', 5291 b'h', 5292 b'D', 5293 b'a', 5294 b't', 5295 b'a', 5296 CBOR_BYTES | 25, 5297 7, 5298 241, 5299 // `rpIdHash`. 5300 0, 5301 0, 5302 0, 5303 0, 5304 0, 5305 0, 5306 0, 5307 0, 5308 0, 5309 0, 5310 0, 5311 0, 5312 0, 5313 0, 5314 0, 5315 0, 5316 0, 5317 0, 5318 0, 5319 0, 5320 0, 5321 0, 5322 0, 5323 0, 5324 0, 5325 0, 5326 0, 5327 0, 5328 0, 5329 0, 5330 0, 5331 0, 5332 // `flags`. 5333 0b0100_0101, 5334 // `signCount`. 5335 0, 5336 0, 5337 0, 5338 0, 5339 // `aaguid`. 5340 0, 5341 0, 5342 0, 5343 0, 5344 0, 5345 0, 5346 0, 5347 0, 5348 0, 5349 0, 5350 0, 5351 0, 5352 0, 5353 0, 5354 0, 5355 0, 5356 // `credentialIdLength`. 5357 0, 5358 16, 5359 // `credentialId`. 5360 0, 5361 0, 5362 0, 5363 0, 5364 0, 5365 0, 5366 0, 5367 0, 5368 0, 5369 0, 5370 0, 5371 0, 5372 0, 5373 0, 5374 0, 5375 0, 5376 // ML-DSA-65 COSE key. 5377 CBOR_MAP | 3, 5378 // COSE kty. 5379 CBOR_UINT | 1, 5380 // COSE AKP 5381 CBOR_UINT | 7, 5382 // COSE alg. 5383 CBOR_UINT | 3, 5384 CBOR_NEG | 24, 5385 // COSE ML-DSA-65. 5386 48, 5387 // `pub`. 5388 CBOR_NEG, 5389 CBOR_BYTES | 25, 5390 // Length is 1952 as 16-bit big-endian. 5391 7, 5392 160, 5393 // Encoded key. 5394 1, 5395 1, 5396 1, 5397 1, 5398 1, 5399 1, 5400 1, 5401 1, 5402 1, 5403 1, 5404 1, 5405 1, 5406 1, 5407 1, 5408 1, 5409 1, 5410 1, 5411 1, 5412 1, 5413 1, 5414 1, 5415 1, 5416 1, 5417 1, 5418 1, 5419 1, 5420 1, 5421 1, 5422 1, 5423 1, 5424 1, 5425 1, 5426 1, 5427 1, 5428 1, 5429 1, 5430 1, 5431 1, 5432 1, 5433 1, 5434 1, 5435 1, 5436 1, 5437 1, 5438 1, 5439 1, 5440 1, 5441 1, 5442 1, 5443 1, 5444 1, 5445 1, 5446 1, 5447 1, 5448 1, 5449 1, 5450 1, 5451 1, 5452 1, 5453 1, 5454 1, 5455 1, 5456 1, 5457 1, 5458 1, 5459 1, 5460 1, 5461 1, 5462 1, 5463 1, 5464 1, 5465 1, 5466 1, 5467 1, 5468 1, 5469 1, 5470 1, 5471 1, 5472 1, 5473 1, 5474 1, 5475 1, 5476 1, 5477 1, 5478 1, 5479 1, 5480 1, 5481 1, 5482 1, 5483 1, 5484 1, 5485 1, 5486 1, 5487 1, 5488 1, 5489 1, 5490 1, 5491 1, 5492 1, 5493 1, 5494 1, 5495 1, 5496 1, 5497 1, 5498 1, 5499 1, 5500 1, 5501 1, 5502 1, 5503 1, 5504 1, 5505 1, 5506 1, 5507 1, 5508 1, 5509 1, 5510 1, 5511 1, 5512 1, 5513 1, 5514 1, 5515 1, 5516 1, 5517 1, 5518 1, 5519 1, 5520 1, 5521 1, 5522 1, 5523 1, 5524 1, 5525 1, 5526 1, 5527 1, 5528 1, 5529 1, 5530 1, 5531 1, 5532 1, 5533 1, 5534 1, 5535 1, 5536 1, 5537 1, 5538 1, 5539 1, 5540 1, 5541 1, 5542 1, 5543 1, 5544 1, 5545 1, 5546 1, 5547 1, 5548 1, 5549 1, 5550 1, 5551 1, 5552 1, 5553 1, 5554 1, 5555 1, 5556 1, 5557 1, 5558 1, 5559 1, 5560 1, 5561 1, 5562 1, 5563 1, 5564 1, 5565 1, 5566 1, 5567 1, 5568 1, 5569 1, 5570 1, 5571 1, 5572 1, 5573 1, 5574 1, 5575 1, 5576 1, 5577 1, 5578 1, 5579 1, 5580 1, 5581 1, 5582 1, 5583 1, 5584 1, 5585 1, 5586 1, 5587 1, 5588 1, 5589 1, 5590 1, 5591 1, 5592 1, 5593 1, 5594 1, 5595 1, 5596 1, 5597 1, 5598 1, 5599 1, 5600 1, 5601 1, 5602 1, 5603 1, 5604 1, 5605 1, 5606 1, 5607 1, 5608 1, 5609 1, 5610 1, 5611 1, 5612 1, 5613 1, 5614 1, 5615 1, 5616 1, 5617 1, 5618 1, 5619 1, 5620 1, 5621 1, 5622 1, 5623 1, 5624 1, 5625 1, 5626 1, 5627 1, 5628 1, 5629 1, 5630 1, 5631 1, 5632 1, 5633 1, 5634 1, 5635 1, 5636 1, 5637 1, 5638 1, 5639 1, 5640 1, 5641 1, 5642 1, 5643 1, 5644 1, 5645 1, 5646 1, 5647 1, 5648 1, 5649 1, 5650 1, 5651 1, 5652 1, 5653 1, 5654 1, 5655 1, 5656 1, 5657 1, 5658 1, 5659 1, 5660 1, 5661 1, 5662 1, 5663 1, 5664 1, 5665 1, 5666 1, 5667 1, 5668 1, 5669 1, 5670 1, 5671 1, 5672 1, 5673 1, 5674 1, 5675 1, 5676 1, 5677 1, 5678 1, 5679 1, 5680 1, 5681 1, 5682 1, 5683 1, 5684 1, 5685 1, 5686 1, 5687 1, 5688 1, 5689 1, 5690 1, 5691 1, 5692 1, 5693 1, 5694 1, 5695 1, 5696 1, 5697 1, 5698 1, 5699 1, 5700 1, 5701 1, 5702 1, 5703 1, 5704 1, 5705 1, 5706 1, 5707 1, 5708 1, 5709 1, 5710 1, 5711 1, 5712 1, 5713 1, 5714 1, 5715 1, 5716 1, 5717 1, 5718 1, 5719 1, 5720 1, 5721 1, 5722 1, 5723 1, 5724 1, 5725 1, 5726 1, 5727 1, 5728 1, 5729 1, 5730 1, 5731 1, 5732 1, 5733 1, 5734 1, 5735 1, 5736 1, 5737 1, 5738 1, 5739 1, 5740 1, 5741 1, 5742 1, 5743 1, 5744 1, 5745 1, 5746 1, 5747 1, 5748 1, 5749 1, 5750 1, 5751 1, 5752 1, 5753 1, 5754 1, 5755 1, 5756 1, 5757 1, 5758 1, 5759 1, 5760 1, 5761 1, 5762 1, 5763 1, 5764 1, 5765 1, 5766 1, 5767 1, 5768 1, 5769 1, 5770 1, 5771 1, 5772 1, 5773 1, 5774 1, 5775 1, 5776 1, 5777 1, 5778 1, 5779 1, 5780 1, 5781 1, 5782 1, 5783 1, 5784 1, 5785 1, 5786 1, 5787 1, 5788 1, 5789 1, 5790 1, 5791 1, 5792 1, 5793 1, 5794 1, 5795 1, 5796 1, 5797 1, 5798 1, 5799 1, 5800 1, 5801 1, 5802 1, 5803 1, 5804 1, 5805 1, 5806 1, 5807 1, 5808 1, 5809 1, 5810 1, 5811 1, 5812 1, 5813 1, 5814 1, 5815 1, 5816 1, 5817 1, 5818 1, 5819 1, 5820 1, 5821 1, 5822 1, 5823 1, 5824 1, 5825 1, 5826 1, 5827 1, 5828 1, 5829 1, 5830 1, 5831 1, 5832 1, 5833 1, 5834 1, 5835 1, 5836 1, 5837 1, 5838 1, 5839 1, 5840 1, 5841 1, 5842 1, 5843 1, 5844 1, 5845 1, 5846 1, 5847 1, 5848 1, 5849 1, 5850 1, 5851 1, 5852 1, 5853 1, 5854 1, 5855 1, 5856 1, 5857 1, 5858 1, 5859 1, 5860 1, 5861 1, 5862 1, 5863 1, 5864 1, 5865 1, 5866 1, 5867 1, 5868 1, 5869 1, 5870 1, 5871 1, 5872 1, 5873 1, 5874 1, 5875 1, 5876 1, 5877 1, 5878 1, 5879 1, 5880 1, 5881 1, 5882 1, 5883 1, 5884 1, 5885 1, 5886 1, 5887 1, 5888 1, 5889 1, 5890 1, 5891 1, 5892 1, 5893 1, 5894 1, 5895 1, 5896 1, 5897 1, 5898 1, 5899 1, 5900 1, 5901 1, 5902 1, 5903 1, 5904 1, 5905 1, 5906 1, 5907 1, 5908 1, 5909 1, 5910 1, 5911 1, 5912 1, 5913 1, 5914 1, 5915 1, 5916 1, 5917 1, 5918 1, 5919 1, 5920 1, 5921 1, 5922 1, 5923 1, 5924 1, 5925 1, 5926 1, 5927 1, 5928 1, 5929 1, 5930 1, 5931 1, 5932 1, 5933 1, 5934 1, 5935 1, 5936 1, 5937 1, 5938 1, 5939 1, 5940 1, 5941 1, 5942 1, 5943 1, 5944 1, 5945 1, 5946 1, 5947 1, 5948 1, 5949 1, 5950 1, 5951 1, 5952 1, 5953 1, 5954 1, 5955 1, 5956 1, 5957 1, 5958 1, 5959 1, 5960 1, 5961 1, 5962 1, 5963 1, 5964 1, 5965 1, 5966 1, 5967 1, 5968 1, 5969 1, 5970 1, 5971 1, 5972 1, 5973 1, 5974 1, 5975 1, 5976 1, 5977 1, 5978 1, 5979 1, 5980 1, 5981 1, 5982 1, 5983 1, 5984 1, 5985 1, 5986 1, 5987 1, 5988 1, 5989 1, 5990 1, 5991 1, 5992 1, 5993 1, 5994 1, 5995 1, 5996 1, 5997 1, 5998 1, 5999 1, 6000 1, 6001 1, 6002 1, 6003 1, 6004 1, 6005 1, 6006 1, 6007 1, 6008 1, 6009 1, 6010 1, 6011 1, 6012 1, 6013 1, 6014 1, 6015 1, 6016 1, 6017 1, 6018 1, 6019 1, 6020 1, 6021 1, 6022 1, 6023 1, 6024 1, 6025 1, 6026 1, 6027 1, 6028 1, 6029 1, 6030 1, 6031 1, 6032 1, 6033 1, 6034 1, 6035 1, 6036 1, 6037 1, 6038 1, 6039 1, 6040 1, 6041 1, 6042 1, 6043 1, 6044 1, 6045 1, 6046 1, 6047 1, 6048 1, 6049 1, 6050 1, 6051 1, 6052 1, 6053 1, 6054 1, 6055 1, 6056 1, 6057 1, 6058 1, 6059 1, 6060 1, 6061 1, 6062 1, 6063 1, 6064 1, 6065 1, 6066 1, 6067 1, 6068 1, 6069 1, 6070 1, 6071 1, 6072 1, 6073 1, 6074 1, 6075 1, 6076 1, 6077 1, 6078 1, 6079 1, 6080 1, 6081 1, 6082 1, 6083 1, 6084 1, 6085 1, 6086 1, 6087 1, 6088 1, 6089 1, 6090 1, 6091 1, 6092 1, 6093 1, 6094 1, 6095 1, 6096 1, 6097 1, 6098 1, 6099 1, 6100 1, 6101 1, 6102 1, 6103 1, 6104 1, 6105 1, 6106 1, 6107 1, 6108 1, 6109 1, 6110 1, 6111 1, 6112 1, 6113 1, 6114 1, 6115 1, 6116 1, 6117 1, 6118 1, 6119 1, 6120 1, 6121 1, 6122 1, 6123 1, 6124 1, 6125 1, 6126 1, 6127 1, 6128 1, 6129 1, 6130 1, 6131 1, 6132 1, 6133 1, 6134 1, 6135 1, 6136 1, 6137 1, 6138 1, 6139 1, 6140 1, 6141 1, 6142 1, 6143 1, 6144 1, 6145 1, 6146 1, 6147 1, 6148 1, 6149 1, 6150 1, 6151 1, 6152 1, 6153 1, 6154 1, 6155 1, 6156 1, 6157 1, 6158 1, 6159 1, 6160 1, 6161 1, 6162 1, 6163 1, 6164 1, 6165 1, 6166 1, 6167 1, 6168 1, 6169 1, 6170 1, 6171 1, 6172 1, 6173 1, 6174 1, 6175 1, 6176 1, 6177 1, 6178 1, 6179 1, 6180 1, 6181 1, 6182 1, 6183 1, 6184 1, 6185 1, 6186 1, 6187 1, 6188 1, 6189 1, 6190 1, 6191 1, 6192 1, 6193 1, 6194 1, 6195 1, 6196 1, 6197 1, 6198 1, 6199 1, 6200 1, 6201 1, 6202 1, 6203 1, 6204 1, 6205 1, 6206 1, 6207 1, 6208 1, 6209 1, 6210 1, 6211 1, 6212 1, 6213 1, 6214 1, 6215 1, 6216 1, 6217 1, 6218 1, 6219 1, 6220 1, 6221 1, 6222 1, 6223 1, 6224 1, 6225 1, 6226 1, 6227 1, 6228 1, 6229 1, 6230 1, 6231 1, 6232 1, 6233 1, 6234 1, 6235 1, 6236 1, 6237 1, 6238 1, 6239 1, 6240 1, 6241 1, 6242 1, 6243 1, 6244 1, 6245 1, 6246 1, 6247 1, 6248 1, 6249 1, 6250 1, 6251 1, 6252 1, 6253 1, 6254 1, 6255 1, 6256 1, 6257 1, 6258 1, 6259 1, 6260 1, 6261 1, 6262 1, 6263 1, 6264 1, 6265 1, 6266 1, 6267 1, 6268 1, 6269 1, 6270 1, 6271 1, 6272 1, 6273 1, 6274 1, 6275 1, 6276 1, 6277 1, 6278 1, 6279 1, 6280 1, 6281 1, 6282 1, 6283 1, 6284 1, 6285 1, 6286 1, 6287 1, 6288 1, 6289 1, 6290 1, 6291 1, 6292 1, 6293 1, 6294 1, 6295 1, 6296 1, 6297 1, 6298 1, 6299 1, 6300 1, 6301 1, 6302 1, 6303 1, 6304 1, 6305 1, 6306 1, 6307 1, 6308 1, 6309 1, 6310 1, 6311 1, 6312 1, 6313 1, 6314 1, 6315 1, 6316 1, 6317 1, 6318 1, 6319 1, 6320 1, 6321 1, 6322 1, 6323 1, 6324 1, 6325 1, 6326 1, 6327 1, 6328 1, 6329 1, 6330 1, 6331 1, 6332 1, 6333 1, 6334 1, 6335 1, 6336 1, 6337 1, 6338 1, 6339 1, 6340 1, 6341 1, 6342 1, 6343 1, 6344 1, 6345 1, 6346 1, 6347 1, 6348 1, 6349 1, 6350 1, 6351 1, 6352 1, 6353 1, 6354 1, 6355 1, 6356 1, 6357 1, 6358 1, 6359 1, 6360 1, 6361 1, 6362 1, 6363 1, 6364 1, 6365 1, 6366 1, 6367 1, 6368 1, 6369 1, 6370 1, 6371 1, 6372 1, 6373 1, 6374 1, 6375 1, 6376 1, 6377 1, 6378 1, 6379 1, 6380 1, 6381 1, 6382 1, 6383 1, 6384 1, 6385 1, 6386 1, 6387 1, 6388 1, 6389 1, 6390 1, 6391 1, 6392 1, 6393 1, 6394 1, 6395 1, 6396 1, 6397 1, 6398 1, 6399 1, 6400 1, 6401 1, 6402 1, 6403 1, 6404 1, 6405 1, 6406 1, 6407 1, 6408 1, 6409 1, 6410 1, 6411 1, 6412 1, 6413 1, 6414 1, 6415 1, 6416 1, 6417 1, 6418 1, 6419 1, 6420 1, 6421 1, 6422 1, 6423 1, 6424 1, 6425 1, 6426 1, 6427 1, 6428 1, 6429 1, 6430 1, 6431 1, 6432 1, 6433 1, 6434 1, 6435 1, 6436 1, 6437 1, 6438 1, 6439 1, 6440 1, 6441 1, 6442 1, 6443 1, 6444 1, 6445 1, 6446 1, 6447 1, 6448 1, 6449 1, 6450 1, 6451 1, 6452 1, 6453 1, 6454 1, 6455 1, 6456 1, 6457 1, 6458 1, 6459 1, 6460 1, 6461 1, 6462 1, 6463 1, 6464 1, 6465 1, 6466 1, 6467 1, 6468 1, 6469 1, 6470 1, 6471 1, 6472 1, 6473 1, 6474 1, 6475 1, 6476 1, 6477 1, 6478 1, 6479 1, 6480 1, 6481 1, 6482 1, 6483 1, 6484 1, 6485 1, 6486 1, 6487 1, 6488 1, 6489 1, 6490 1, 6491 1, 6492 1, 6493 1, 6494 1, 6495 1, 6496 1, 6497 1, 6498 1, 6499 1, 6500 1, 6501 1, 6502 1, 6503 1, 6504 1, 6505 1, 6506 1, 6507 1, 6508 1, 6509 1, 6510 1, 6511 1, 6512 1, 6513 1, 6514 1, 6515 1, 6516 1, 6517 1, 6518 1, 6519 1, 6520 1, 6521 1, 6522 1, 6523 1, 6524 1, 6525 1, 6526 1, 6527 1, 6528 1, 6529 1, 6530 1, 6531 1, 6532 1, 6533 1, 6534 1, 6535 1, 6536 1, 6537 1, 6538 1, 6539 1, 6540 1, 6541 1, 6542 1, 6543 1, 6544 1, 6545 1, 6546 1, 6547 1, 6548 1, 6549 1, 6550 1, 6551 1, 6552 1, 6553 1, 6554 1, 6555 1, 6556 1, 6557 1, 6558 1, 6559 1, 6560 1, 6561 1, 6562 1, 6563 1, 6564 1, 6565 1, 6566 1, 6567 1, 6568 1, 6569 1, 6570 1, 6571 1, 6572 1, 6573 1, 6574 1, 6575 1, 6576 1, 6577 1, 6578 1, 6579 1, 6580 1, 6581 1, 6582 1, 6583 1, 6584 1, 6585 1, 6586 1, 6587 1, 6588 1, 6589 1, 6590 1, 6591 1, 6592 1, 6593 1, 6594 1, 6595 1, 6596 1, 6597 1, 6598 1, 6599 1, 6600 1, 6601 1, 6602 1, 6603 1, 6604 1, 6605 1, 6606 1, 6607 1, 6608 1, 6609 1, 6610 1, 6611 1, 6612 1, 6613 1, 6614 1, 6615 1, 6616 1, 6617 1, 6618 1, 6619 1, 6620 1, 6621 1, 6622 1, 6623 1, 6624 1, 6625 1, 6626 1, 6627 1, 6628 1, 6629 1, 6630 1, 6631 1, 6632 1, 6633 1, 6634 1, 6635 1, 6636 1, 6637 1, 6638 1, 6639 1, 6640 1, 6641 1, 6642 1, 6643 1, 6644 1, 6645 1, 6646 1, 6647 1, 6648 1, 6649 1, 6650 1, 6651 1, 6652 1, 6653 1, 6654 1, 6655 1, 6656 1, 6657 1, 6658 1, 6659 1, 6660 1, 6661 1, 6662 1, 6663 1, 6664 1, 6665 1, 6666 1, 6667 1, 6668 1, 6669 1, 6670 1, 6671 1, 6672 1, 6673 1, 6674 1, 6675 1, 6676 1, 6677 1, 6678 1, 6679 1, 6680 1, 6681 1, 6682 1, 6683 1, 6684 1, 6685 1, 6686 1, 6687 1, 6688 1, 6689 1, 6690 1, 6691 1, 6692 1, 6693 1, 6694 1, 6695 1, 6696 1, 6697 1, 6698 1, 6699 1, 6700 1, 6701 1, 6702 1, 6703 1, 6704 1, 6705 1, 6706 1, 6707 1, 6708 1, 6709 1, 6710 1, 6711 1, 6712 1, 6713 1, 6714 1, 6715 1, 6716 1, 6717 1, 6718 1, 6719 1, 6720 1, 6721 1, 6722 1, 6723 1, 6724 1, 6725 1, 6726 1, 6727 1, 6728 1, 6729 1, 6730 1, 6731 1, 6732 1, 6733 1, 6734 1, 6735 1, 6736 1, 6737 1, 6738 1, 6739 1, 6740 1, 6741 1, 6742 1, 6743 1, 6744 1, 6745 1, 6746 1, 6747 1, 6748 1, 6749 1, 6750 1, 6751 1, 6752 1, 6753 1, 6754 1, 6755 1, 6756 1, 6757 1, 6758 1, 6759 1, 6760 1, 6761 1, 6762 1, 6763 1, 6764 1, 6765 1, 6766 1, 6767 1, 6768 1, 6769 1, 6770 1, 6771 1, 6772 1, 6773 1, 6774 1, 6775 1, 6776 1, 6777 1, 6778 1, 6779 1, 6780 1, 6781 1, 6782 1, 6783 1, 6784 1, 6785 1, 6786 1, 6787 1, 6788 1, 6789 1, 6790 1, 6791 1, 6792 1, 6793 1, 6794 1, 6795 1, 6796 1, 6797 1, 6798 1, 6799 1, 6800 1, 6801 1, 6802 1, 6803 1, 6804 1, 6805 1, 6806 1, 6807 1, 6808 1, 6809 1, 6810 1, 6811 1, 6812 1, 6813 1, 6814 1, 6815 1, 6816 1, 6817 1, 6818 1, 6819 1, 6820 1, 6821 1, 6822 1, 6823 1, 6824 1, 6825 1, 6826 1, 6827 1, 6828 1, 6829 1, 6830 1, 6831 1, 6832 1, 6833 1, 6834 1, 6835 1, 6836 1, 6837 1, 6838 1, 6839 1, 6840 1, 6841 1, 6842 1, 6843 1, 6844 1, 6845 1, 6846 1, 6847 1, 6848 1, 6849 1, 6850 1, 6851 1, 6852 1, 6853 1, 6854 1, 6855 1, 6856 1, 6857 1, 6858 1, 6859 1, 6860 1, 6861 1, 6862 1, 6863 1, 6864 1, 6865 1, 6866 1, 6867 1, 6868 1, 6869 1, 6870 1, 6871 1, 6872 1, 6873 1, 6874 1, 6875 1, 6876 1, 6877 1, 6878 1, 6879 1, 6880 1, 6881 1, 6882 1, 6883 1, 6884 1, 6885 1, 6886 1, 6887 1, 6888 1, 6889 1, 6890 1, 6891 1, 6892 1, 6893 1, 6894 1, 6895 1, 6896 1, 6897 1, 6898 1, 6899 1, 6900 1, 6901 1, 6902 1, 6903 1, 6904 1, 6905 1, 6906 1, 6907 1, 6908 1, 6909 1, 6910 1, 6911 1, 6912 1, 6913 1, 6914 1, 6915 1, 6916 1, 6917 1, 6918 1, 6919 1, 6920 1, 6921 1, 6922 1, 6923 1, 6924 1, 6925 1, 6926 1, 6927 1, 6928 1, 6929 1, 6930 1, 6931 1, 6932 1, 6933 1, 6934 1, 6935 1, 6936 1, 6937 1, 6938 1, 6939 1, 6940 1, 6941 1, 6942 1, 6943 1, 6944 1, 6945 1, 6946 1, 6947 1, 6948 1, 6949 1, 6950 1, 6951 1, 6952 1, 6953 1, 6954 1, 6955 1, 6956 1, 6957 1, 6958 1, 6959 1, 6960 1, 6961 1, 6962 1, 6963 1, 6964 1, 6965 1, 6966 1, 6967 1, 6968 1, 6969 1, 6970 1, 6971 1, 6972 1, 6973 1, 6974 1, 6975 1, 6976 1, 6977 1, 6978 1, 6979 1, 6980 1, 6981 1, 6982 1, 6983 1, 6984 1, 6985 1, 6986 1, 6987 1, 6988 1, 6989 1, 6990 1, 6991 1, 6992 1, 6993 1, 6994 1, 6995 1, 6996 1, 6997 1, 6998 1, 6999 1, 7000 1, 7001 1, 7002 1, 7003 1, 7004 1, 7005 1, 7006 1, 7007 1, 7008 1, 7009 1, 7010 1, 7011 1, 7012 1, 7013 1, 7014 1, 7015 1, 7016 1, 7017 1, 7018 1, 7019 1, 7020 1, 7021 1, 7022 1, 7023 1, 7024 1, 7025 1, 7026 1, 7027 1, 7028 1, 7029 1, 7030 1, 7031 1, 7032 1, 7033 1, 7034 1, 7035 1, 7036 1, 7037 1, 7038 1, 7039 1, 7040 1, 7041 1, 7042 1, 7043 1, 7044 1, 7045 1, 7046 1, 7047 1, 7048 1, 7049 1, 7050 1, 7051 1, 7052 1, 7053 1, 7054 1, 7055 1, 7056 1, 7057 1, 7058 1, 7059 1, 7060 1, 7061 1, 7062 1, 7063 1, 7064 1, 7065 1, 7066 1, 7067 1, 7068 1, 7069 1, 7070 1, 7071 1, 7072 1, 7073 1, 7074 1, 7075 1, 7076 1, 7077 1, 7078 1, 7079 1, 7080 1, 7081 1, 7082 1, 7083 1, 7084 1, 7085 1, 7086 1, 7087 1, 7088 1, 7089 1, 7090 1, 7091 1, 7092 1, 7093 1, 7094 1, 7095 1, 7096 1, 7097 1, 7098 1, 7099 1, 7100 1, 7101 1, 7102 1, 7103 1, 7104 1, 7105 1, 7106 1, 7107 1, 7108 1, 7109 1, 7110 1, 7111 1, 7112 1, 7113 1, 7114 1, 7115 1, 7116 1, 7117 1, 7118 1, 7119 1, 7120 1, 7121 1, 7122 1, 7123 1, 7124 1, 7125 1, 7126 1, 7127 1, 7128 1, 7129 1, 7130 1, 7131 1, 7132 1, 7133 1, 7134 1, 7135 1, 7136 1, 7137 1, 7138 1, 7139 1, 7140 1, 7141 1, 7142 1, 7143 1, 7144 1, 7145 1, 7146 1, 7147 1, 7148 1, 7149 1, 7150 1, 7151 1, 7152 1, 7153 1, 7154 1, 7155 1, 7156 1, 7157 1, 7158 1, 7159 1, 7160 1, 7161 1, 7162 1, 7163 1, 7164 1, 7165 1, 7166 1, 7167 1, 7168 1, 7169 1, 7170 1, 7171 1, 7172 1, 7173 1, 7174 1, 7175 1, 7176 1, 7177 1, 7178 1, 7179 1, 7180 1, 7181 1, 7182 1, 7183 1, 7184 1, 7185 1, 7186 1, 7187 1, 7188 1, 7189 1, 7190 1, 7191 1, 7192 1, 7193 1, 7194 1, 7195 1, 7196 1, 7197 1, 7198 1, 7199 1, 7200 1, 7201 1, 7202 1, 7203 1, 7204 1, 7205 1, 7206 1, 7207 1, 7208 1, 7209 1, 7210 1, 7211 1, 7212 1, 7213 1, 7214 1, 7215 1, 7216 1, 7217 1, 7218 1, 7219 1, 7220 1, 7221 1, 7222 1, 7223 1, 7224 1, 7225 1, 7226 1, 7227 1, 7228 1, 7229 1, 7230 1, 7231 1, 7232 1, 7233 1, 7234 1, 7235 1, 7236 1, 7237 1, 7238 1, 7239 1, 7240 1, 7241 1, 7242 1, 7243 1, 7244 1, 7245 1, 7246 1, 7247 1, 7248 1, 7249 1, 7250 1, 7251 1, 7252 1, 7253 1, 7254 1, 7255 1, 7256 1, 7257 1, 7258 1, 7259 1, 7260 1, 7261 1, 7262 1, 7263 1, 7264 1, 7265 1, 7266 1, 7267 1, 7268 1, 7269 1, 7270 1, 7271 1, 7272 1, 7273 1, 7274 1, 7275 1, 7276 1, 7277 1, 7278 1, 7279 1, 7280 1, 7281 1, 7282 1, 7283 1, 7284 1, 7285 1, 7286 1, 7287 1, 7288 1, 7289 1, 7290 1, 7291 1, 7292 1, 7293 1, 7294 1, 7295 1, 7296 1, 7297 1, 7298 1, 7299 1, 7300 1, 7301 1, 7302 1, 7303 1, 7304 1, 7305 1, 7306 1, 7307 1, 7308 1, 7309 1, 7310 1, 7311 1, 7312 1, 7313 1, 7314 1, 7315 1, 7316 1, 7317 1, 7318 1, 7319 1, 7320 1, 7321 1, 7322 1, 7323 1, 7324 1, 7325 1, 7326 1, 7327 1, 7328 1, 7329 1, 7330 1, 7331 1, 7332 1, 7333 1, 7334 1, 7335 1, 7336 1, 7337 1, 7338 1, 7339 1, 7340 1, 7341 1, 7342 1, 7343 1, 7344 1, 7345 1, 7346 ] 7347 .as_slice(), 7348 ); 7349 attestation_object[31..63].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 7350 assert!(matches!(opts.start_ceremony()?.0.verify( 7351 RP_ID, 7352 &Registration { 7353 response: AuthenticatorAttestation::new( 7354 client_data_json, 7355 attestation_object, 7356 AuthTransports::NONE, 7357 ), 7358 authenticator_attachment: AuthenticatorAttachment::None, 7359 client_extension_results: ClientExtensionsOutputs { 7360 cred_props: None, 7361 prf: None, 7362 }, 7363 }, 7364 &RegistrationVerificationOptions::<&str, &str>::default(), 7365 )?.static_state.credential_public_key, UncompressedPubKey::MlDsa65(k) if **k.inner() == [1; 1952])); 7366 Ok(()) 7367 } 7368 #[expect( 7369 clippy::panic_in_result_fn, 7370 clippy::unwrap_in_result, 7371 clippy::unwrap_used, 7372 reason = "OK in tests" 7373 )] 7374 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 7375 #[test] 7376 #[cfg(feature = "custom")] 7377 fn mldsa65_auth() -> Result<(), AggErr> { 7378 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 7379 opts.public_key.challenge = Challenge(0); 7380 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 7381 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 7382 let mut authenticator_data = Vec::with_capacity(69); 7383 authenticator_data.extend_from_slice( 7384 [ 7385 // rpIdHash. 7386 // This will be overwritten later. 7387 0, 7388 0, 7389 0, 7390 0, 7391 0, 7392 0, 7393 0, 7394 0, 7395 0, 7396 0, 7397 0, 7398 0, 7399 0, 7400 0, 7401 0, 7402 0, 7403 0, 7404 0, 7405 0, 7406 0, 7407 0, 7408 0, 7409 0, 7410 0, 7411 0, 7412 0, 7413 0, 7414 0, 7415 0, 7416 0, 7417 0, 7418 0, 7419 // flags. 7420 // UP and UV (right-to-left). 7421 0b0000_0101, 7422 // signCount. 7423 // 0 as 32-bit big endian. 7424 0, 7425 0, 7426 0, 7427 0, 7428 ] 7429 .as_slice(), 7430 ); 7431 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 7432 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 7433 let mldsa65_key = MlDsaSigKey::<MlDsa65>::from_seed((&[0; 32]).into()); 7434 let sig: MlDsaSignature<MlDsa65> = mldsa65_key.sign(authenticator_data.as_slice()); 7435 let pub_key = mldsa65_key.verifying_key().encode(); 7436 authenticator_data.truncate(37); 7437 assert!(!opts.start_ceremony()?.0.verify( 7438 RP_ID, 7439 &DiscoverableAuthentication { 7440 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 7441 response: DiscoverableAuthenticatorAssertion::new( 7442 client_data_json, 7443 authenticator_data, 7444 sig.encode().0.to_vec(), 7445 UserHandle::from([0]), 7446 ), 7447 authenticator_attachment: AuthenticatorAttachment::None, 7448 }, 7449 &mut AuthenticatedCredential::new( 7450 CredentialId::try_from([0; 16].as_slice())?, 7451 &UserHandle::from([0]), 7452 StaticState { 7453 credential_public_key: CompressedPubKeyOwned::MlDsa65( 7454 MlDsa65PubKey::try_from(Box::from(pub_key.as_slice())).unwrap() 7455 ), 7456 extensions: AuthenticatorExtensionOutputStaticState { 7457 cred_protect: CredentialProtectionPolicy::None, 7458 hmac_secret: None, 7459 }, 7460 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 7461 }, 7462 DynamicState { 7463 user_verified: true, 7464 backup: Backup::NotEligible, 7465 sign_count: 0, 7466 authenticator_attachment: AuthenticatorAttachment::None, 7467 }, 7468 )?, 7469 &AuthenticationVerificationOptions::<&str, &str>::default(), 7470 )?); 7471 Ok(()) 7472 } 7473 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 7474 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 7475 #[expect(clippy::too_many_lines, reason = "a lot to test")] 7476 #[test] 7477 #[cfg(feature = "custom")] 7478 fn mldsa44_reg() -> Result<(), AggErr> { 7479 let id = UserHandle::from([0]); 7480 let mut opts = CredentialCreationOptions::passkey( 7481 RP_ID, 7482 PublicKeyCredentialUserEntity { 7483 name: "foo".try_into()?, 7484 id: &id, 7485 display_name: DisplayName::Blank, 7486 }, 7487 Vec::new(), 7488 ); 7489 opts.public_key.challenge = Challenge(0); 7490 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 7491 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 7492 let mut attestation_object = Vec::with_capacity(1456); 7493 attestation_object.extend_from_slice( 7494 [ 7495 CBOR_MAP | 3, 7496 CBOR_TEXT | 3, 7497 b'f', 7498 b'm', 7499 b't', 7500 CBOR_TEXT | 4, 7501 b'n', 7502 b'o', 7503 b'n', 7504 b'e', 7505 CBOR_TEXT | 7, 7506 b'a', 7507 b't', 7508 b't', 7509 b'S', 7510 b't', 7511 b'm', 7512 b't', 7513 CBOR_MAP, 7514 CBOR_TEXT | 8, 7515 b'a', 7516 b'u', 7517 b't', 7518 b'h', 7519 b'D', 7520 b'a', 7521 b't', 7522 b'a', 7523 CBOR_BYTES | 25, 7524 5, 7525 113, 7526 // `rpIdHash`. 7527 0, 7528 0, 7529 0, 7530 0, 7531 0, 7532 0, 7533 0, 7534 0, 7535 0, 7536 0, 7537 0, 7538 0, 7539 0, 7540 0, 7541 0, 7542 0, 7543 0, 7544 0, 7545 0, 7546 0, 7547 0, 7548 0, 7549 0, 7550 0, 7551 0, 7552 0, 7553 0, 7554 0, 7555 0, 7556 0, 7557 0, 7558 0, 7559 // `flags`. 7560 0b0100_0101, 7561 // `signCount`. 7562 0, 7563 0, 7564 0, 7565 0, 7566 // `aaguid`. 7567 0, 7568 0, 7569 0, 7570 0, 7571 0, 7572 0, 7573 0, 7574 0, 7575 0, 7576 0, 7577 0, 7578 0, 7579 0, 7580 0, 7581 0, 7582 0, 7583 // `credentialIdLength`. 7584 0, 7585 16, 7586 // `credentialId`. 7587 0, 7588 0, 7589 0, 7590 0, 7591 0, 7592 0, 7593 0, 7594 0, 7595 0, 7596 0, 7597 0, 7598 0, 7599 0, 7600 0, 7601 0, 7602 0, 7603 // ML-DSA-44 COSE key. 7604 CBOR_MAP | 3, 7605 // COSE kty. 7606 CBOR_UINT | 1, 7607 // COSE AKP 7608 CBOR_UINT | 7, 7609 // COSE alg. 7610 CBOR_UINT | 3, 7611 CBOR_NEG | 24, 7612 // COSE ML-DSA-44. 7613 47, 7614 // `pub`. 7615 CBOR_NEG, 7616 CBOR_BYTES | 25, 7617 // Length is 1312 as 16-bit big-endian. 7618 5, 7619 32, 7620 // Encoded key. 7621 1, 7622 1, 7623 1, 7624 1, 7625 1, 7626 1, 7627 1, 7628 1, 7629 1, 7630 1, 7631 1, 7632 1, 7633 1, 7634 1, 7635 1, 7636 1, 7637 1, 7638 1, 7639 1, 7640 1, 7641 1, 7642 1, 7643 1, 7644 1, 7645 1, 7646 1, 7647 1, 7648 1, 7649 1, 7650 1, 7651 1, 7652 1, 7653 1, 7654 1, 7655 1, 7656 1, 7657 1, 7658 1, 7659 1, 7660 1, 7661 1, 7662 1, 7663 1, 7664 1, 7665 1, 7666 1, 7667 1, 7668 1, 7669 1, 7670 1, 7671 1, 7672 1, 7673 1, 7674 1, 7675 1, 7676 1, 7677 1, 7678 1, 7679 1, 7680 1, 7681 1, 7682 1, 7683 1, 7684 1, 7685 1, 7686 1, 7687 1, 7688 1, 7689 1, 7690 1, 7691 1, 7692 1, 7693 1, 7694 1, 7695 1, 7696 1, 7697 1, 7698 1, 7699 1, 7700 1, 7701 1, 7702 1, 7703 1, 7704 1, 7705 1, 7706 1, 7707 1, 7708 1, 7709 1, 7710 1, 7711 1, 7712 1, 7713 1, 7714 1, 7715 1, 7716 1, 7717 1, 7718 1, 7719 1, 7720 1, 7721 1, 7722 1, 7723 1, 7724 1, 7725 1, 7726 1, 7727 1, 7728 1, 7729 1, 7730 1, 7731 1, 7732 1, 7733 1, 7734 1, 7735 1, 7736 1, 7737 1, 7738 1, 7739 1, 7740 1, 7741 1, 7742 1, 7743 1, 7744 1, 7745 1, 7746 1, 7747 1, 7748 1, 7749 1, 7750 1, 7751 1, 7752 1, 7753 1, 7754 1, 7755 1, 7756 1, 7757 1, 7758 1, 7759 1, 7760 1, 7761 1, 7762 1, 7763 1, 7764 1, 7765 1, 7766 1, 7767 1, 7768 1, 7769 1, 7770 1, 7771 1, 7772 1, 7773 1, 7774 1, 7775 1, 7776 1, 7777 1, 7778 1, 7779 1, 7780 1, 7781 1, 7782 1, 7783 1, 7784 1, 7785 1, 7786 1, 7787 1, 7788 1, 7789 1, 7790 1, 7791 1, 7792 1, 7793 1, 7794 1, 7795 1, 7796 1, 7797 1, 7798 1, 7799 1, 7800 1, 7801 1, 7802 1, 7803 1, 7804 1, 7805 1, 7806 1, 7807 1, 7808 1, 7809 1, 7810 1, 7811 1, 7812 1, 7813 1, 7814 1, 7815 1, 7816 1, 7817 1, 7818 1, 7819 1, 7820 1, 7821 1, 7822 1, 7823 1, 7824 1, 7825 1, 7826 1, 7827 1, 7828 1, 7829 1, 7830 1, 7831 1, 7832 1, 7833 1, 7834 1, 7835 1, 7836 1, 7837 1, 7838 1, 7839 1, 7840 1, 7841 1, 7842 1, 7843 1, 7844 1, 7845 1, 7846 1, 7847 1, 7848 1, 7849 1, 7850 1, 7851 1, 7852 1, 7853 1, 7854 1, 7855 1, 7856 1, 7857 1, 7858 1, 7859 1, 7860 1, 7861 1, 7862 1, 7863 1, 7864 1, 7865 1, 7866 1, 7867 1, 7868 1, 7869 1, 7870 1, 7871 1, 7872 1, 7873 1, 7874 1, 7875 1, 7876 1, 7877 1, 7878 1, 7879 1, 7880 1, 7881 1, 7882 1, 7883 1, 7884 1, 7885 1, 7886 1, 7887 1, 7888 1, 7889 1, 7890 1, 7891 1, 7892 1, 7893 1, 7894 1, 7895 1, 7896 1, 7897 1, 7898 1, 7899 1, 7900 1, 7901 1, 7902 1, 7903 1, 7904 1, 7905 1, 7906 1, 7907 1, 7908 1, 7909 1, 7910 1, 7911 1, 7912 1, 7913 1, 7914 1, 7915 1, 7916 1, 7917 1, 7918 1, 7919 1, 7920 1, 7921 1, 7922 1, 7923 1, 7924 1, 7925 1, 7926 1, 7927 1, 7928 1, 7929 1, 7930 1, 7931 1, 7932 1, 7933 1, 7934 1, 7935 1, 7936 1, 7937 1, 7938 1, 7939 1, 7940 1, 7941 1, 7942 1, 7943 1, 7944 1, 7945 1, 7946 1, 7947 1, 7948 1, 7949 1, 7950 1, 7951 1, 7952 1, 7953 1, 7954 1, 7955 1, 7956 1, 7957 1, 7958 1, 7959 1, 7960 1, 7961 1, 7962 1, 7963 1, 7964 1, 7965 1, 7966 1, 7967 1, 7968 1, 7969 1, 7970 1, 7971 1, 7972 1, 7973 1, 7974 1, 7975 1, 7976 1, 7977 1, 7978 1, 7979 1, 7980 1, 7981 1, 7982 1, 7983 1, 7984 1, 7985 1, 7986 1, 7987 1, 7988 1, 7989 1, 7990 1, 7991 1, 7992 1, 7993 1, 7994 1, 7995 1, 7996 1, 7997 1, 7998 1, 7999 1, 8000 1, 8001 1, 8002 1, 8003 1, 8004 1, 8005 1, 8006 1, 8007 1, 8008 1, 8009 1, 8010 1, 8011 1, 8012 1, 8013 1, 8014 1, 8015 1, 8016 1, 8017 1, 8018 1, 8019 1, 8020 1, 8021 1, 8022 1, 8023 1, 8024 1, 8025 1, 8026 1, 8027 1, 8028 1, 8029 1, 8030 1, 8031 1, 8032 1, 8033 1, 8034 1, 8035 1, 8036 1, 8037 1, 8038 1, 8039 1, 8040 1, 8041 1, 8042 1, 8043 1, 8044 1, 8045 1, 8046 1, 8047 1, 8048 1, 8049 1, 8050 1, 8051 1, 8052 1, 8053 1, 8054 1, 8055 1, 8056 1, 8057 1, 8058 1, 8059 1, 8060 1, 8061 1, 8062 1, 8063 1, 8064 1, 8065 1, 8066 1, 8067 1, 8068 1, 8069 1, 8070 1, 8071 1, 8072 1, 8073 1, 8074 1, 8075 1, 8076 1, 8077 1, 8078 1, 8079 1, 8080 1, 8081 1, 8082 1, 8083 1, 8084 1, 8085 1, 8086 1, 8087 1, 8088 1, 8089 1, 8090 1, 8091 1, 8092 1, 8093 1, 8094 1, 8095 1, 8096 1, 8097 1, 8098 1, 8099 1, 8100 1, 8101 1, 8102 1, 8103 1, 8104 1, 8105 1, 8106 1, 8107 1, 8108 1, 8109 1, 8110 1, 8111 1, 8112 1, 8113 1, 8114 1, 8115 1, 8116 1, 8117 1, 8118 1, 8119 1, 8120 1, 8121 1, 8122 1, 8123 1, 8124 1, 8125 1, 8126 1, 8127 1, 8128 1, 8129 1, 8130 1, 8131 1, 8132 1, 8133 1, 8134 1, 8135 1, 8136 1, 8137 1, 8138 1, 8139 1, 8140 1, 8141 1, 8142 1, 8143 1, 8144 1, 8145 1, 8146 1, 8147 1, 8148 1, 8149 1, 8150 1, 8151 1, 8152 1, 8153 1, 8154 1, 8155 1, 8156 1, 8157 1, 8158 1, 8159 1, 8160 1, 8161 1, 8162 1, 8163 1, 8164 1, 8165 1, 8166 1, 8167 1, 8168 1, 8169 1, 8170 1, 8171 1, 8172 1, 8173 1, 8174 1, 8175 1, 8176 1, 8177 1, 8178 1, 8179 1, 8180 1, 8181 1, 8182 1, 8183 1, 8184 1, 8185 1, 8186 1, 8187 1, 8188 1, 8189 1, 8190 1, 8191 1, 8192 1, 8193 1, 8194 1, 8195 1, 8196 1, 8197 1, 8198 1, 8199 1, 8200 1, 8201 1, 8202 1, 8203 1, 8204 1, 8205 1, 8206 1, 8207 1, 8208 1, 8209 1, 8210 1, 8211 1, 8212 1, 8213 1, 8214 1, 8215 1, 8216 1, 8217 1, 8218 1, 8219 1, 8220 1, 8221 1, 8222 1, 8223 1, 8224 1, 8225 1, 8226 1, 8227 1, 8228 1, 8229 1, 8230 1, 8231 1, 8232 1, 8233 1, 8234 1, 8235 1, 8236 1, 8237 1, 8238 1, 8239 1, 8240 1, 8241 1, 8242 1, 8243 1, 8244 1, 8245 1, 8246 1, 8247 1, 8248 1, 8249 1, 8250 1, 8251 1, 8252 1, 8253 1, 8254 1, 8255 1, 8256 1, 8257 1, 8258 1, 8259 1, 8260 1, 8261 1, 8262 1, 8263 1, 8264 1, 8265 1, 8266 1, 8267 1, 8268 1, 8269 1, 8270 1, 8271 1, 8272 1, 8273 1, 8274 1, 8275 1, 8276 1, 8277 1, 8278 1, 8279 1, 8280 1, 8281 1, 8282 1, 8283 1, 8284 1, 8285 1, 8286 1, 8287 1, 8288 1, 8289 1, 8290 1, 8291 1, 8292 1, 8293 1, 8294 1, 8295 1, 8296 1, 8297 1, 8298 1, 8299 1, 8300 1, 8301 1, 8302 1, 8303 1, 8304 1, 8305 1, 8306 1, 8307 1, 8308 1, 8309 1, 8310 1, 8311 1, 8312 1, 8313 1, 8314 1, 8315 1, 8316 1, 8317 1, 8318 1, 8319 1, 8320 1, 8321 1, 8322 1, 8323 1, 8324 1, 8325 1, 8326 1, 8327 1, 8328 1, 8329 1, 8330 1, 8331 1, 8332 1, 8333 1, 8334 1, 8335 1, 8336 1, 8337 1, 8338 1, 8339 1, 8340 1, 8341 1, 8342 1, 8343 1, 8344 1, 8345 1, 8346 1, 8347 1, 8348 1, 8349 1, 8350 1, 8351 1, 8352 1, 8353 1, 8354 1, 8355 1, 8356 1, 8357 1, 8358 1, 8359 1, 8360 1, 8361 1, 8362 1, 8363 1, 8364 1, 8365 1, 8366 1, 8367 1, 8368 1, 8369 1, 8370 1, 8371 1, 8372 1, 8373 1, 8374 1, 8375 1, 8376 1, 8377 1, 8378 1, 8379 1, 8380 1, 8381 1, 8382 1, 8383 1, 8384 1, 8385 1, 8386 1, 8387 1, 8388 1, 8389 1, 8390 1, 8391 1, 8392 1, 8393 1, 8394 1, 8395 1, 8396 1, 8397 1, 8398 1, 8399 1, 8400 1, 8401 1, 8402 1, 8403 1, 8404 1, 8405 1, 8406 1, 8407 1, 8408 1, 8409 1, 8410 1, 8411 1, 8412 1, 8413 1, 8414 1, 8415 1, 8416 1, 8417 1, 8418 1, 8419 1, 8420 1, 8421 1, 8422 1, 8423 1, 8424 1, 8425 1, 8426 1, 8427 1, 8428 1, 8429 1, 8430 1, 8431 1, 8432 1, 8433 1, 8434 1, 8435 1, 8436 1, 8437 1, 8438 1, 8439 1, 8440 1, 8441 1, 8442 1, 8443 1, 8444 1, 8445 1, 8446 1, 8447 1, 8448 1, 8449 1, 8450 1, 8451 1, 8452 1, 8453 1, 8454 1, 8455 1, 8456 1, 8457 1, 8458 1, 8459 1, 8460 1, 8461 1, 8462 1, 8463 1, 8464 1, 8465 1, 8466 1, 8467 1, 8468 1, 8469 1, 8470 1, 8471 1, 8472 1, 8473 1, 8474 1, 8475 1, 8476 1, 8477 1, 8478 1, 8479 1, 8480 1, 8481 1, 8482 1, 8483 1, 8484 1, 8485 1, 8486 1, 8487 1, 8488 1, 8489 1, 8490 1, 8491 1, 8492 1, 8493 1, 8494 1, 8495 1, 8496 1, 8497 1, 8498 1, 8499 1, 8500 1, 8501 1, 8502 1, 8503 1, 8504 1, 8505 1, 8506 1, 8507 1, 8508 1, 8509 1, 8510 1, 8511 1, 8512 1, 8513 1, 8514 1, 8515 1, 8516 1, 8517 1, 8518 1, 8519 1, 8520 1, 8521 1, 8522 1, 8523 1, 8524 1, 8525 1, 8526 1, 8527 1, 8528 1, 8529 1, 8530 1, 8531 1, 8532 1, 8533 1, 8534 1, 8535 1, 8536 1, 8537 1, 8538 1, 8539 1, 8540 1, 8541 1, 8542 1, 8543 1, 8544 1, 8545 1, 8546 1, 8547 1, 8548 1, 8549 1, 8550 1, 8551 1, 8552 1, 8553 1, 8554 1, 8555 1, 8556 1, 8557 1, 8558 1, 8559 1, 8560 1, 8561 1, 8562 1, 8563 1, 8564 1, 8565 1, 8566 1, 8567 1, 8568 1, 8569 1, 8570 1, 8571 1, 8572 1, 8573 1, 8574 1, 8575 1, 8576 1, 8577 1, 8578 1, 8579 1, 8580 1, 8581 1, 8582 1, 8583 1, 8584 1, 8585 1, 8586 1, 8587 1, 8588 1, 8589 1, 8590 1, 8591 1, 8592 1, 8593 1, 8594 1, 8595 1, 8596 1, 8597 1, 8598 1, 8599 1, 8600 1, 8601 1, 8602 1, 8603 1, 8604 1, 8605 1, 8606 1, 8607 1, 8608 1, 8609 1, 8610 1, 8611 1, 8612 1, 8613 1, 8614 1, 8615 1, 8616 1, 8617 1, 8618 1, 8619 1, 8620 1, 8621 1, 8622 1, 8623 1, 8624 1, 8625 1, 8626 1, 8627 1, 8628 1, 8629 1, 8630 1, 8631 1, 8632 1, 8633 1, 8634 1, 8635 1, 8636 1, 8637 1, 8638 1, 8639 1, 8640 1, 8641 1, 8642 1, 8643 1, 8644 1, 8645 1, 8646 1, 8647 1, 8648 1, 8649 1, 8650 1, 8651 1, 8652 1, 8653 1, 8654 1, 8655 1, 8656 1, 8657 1, 8658 1, 8659 1, 8660 1, 8661 1, 8662 1, 8663 1, 8664 1, 8665 1, 8666 1, 8667 1, 8668 1, 8669 1, 8670 1, 8671 1, 8672 1, 8673 1, 8674 1, 8675 1, 8676 1, 8677 1, 8678 1, 8679 1, 8680 1, 8681 1, 8682 1, 8683 1, 8684 1, 8685 1, 8686 1, 8687 1, 8688 1, 8689 1, 8690 1, 8691 1, 8692 1, 8693 1, 8694 1, 8695 1, 8696 1, 8697 1, 8698 1, 8699 1, 8700 1, 8701 1, 8702 1, 8703 1, 8704 1, 8705 1, 8706 1, 8707 1, 8708 1, 8709 1, 8710 1, 8711 1, 8712 1, 8713 1, 8714 1, 8715 1, 8716 1, 8717 1, 8718 1, 8719 1, 8720 1, 8721 1, 8722 1, 8723 1, 8724 1, 8725 1, 8726 1, 8727 1, 8728 1, 8729 1, 8730 1, 8731 1, 8732 1, 8733 1, 8734 1, 8735 1, 8736 1, 8737 1, 8738 1, 8739 1, 8740 1, 8741 1, 8742 1, 8743 1, 8744 1, 8745 1, 8746 1, 8747 1, 8748 1, 8749 1, 8750 1, 8751 1, 8752 1, 8753 1, 8754 1, 8755 1, 8756 1, 8757 1, 8758 1, 8759 1, 8760 1, 8761 1, 8762 1, 8763 1, 8764 1, 8765 1, 8766 1, 8767 1, 8768 1, 8769 1, 8770 1, 8771 1, 8772 1, 8773 1, 8774 1, 8775 1, 8776 1, 8777 1, 8778 1, 8779 1, 8780 1, 8781 1, 8782 1, 8783 1, 8784 1, 8785 1, 8786 1, 8787 1, 8788 1, 8789 1, 8790 1, 8791 1, 8792 1, 8793 1, 8794 1, 8795 1, 8796 1, 8797 1, 8798 1, 8799 1, 8800 1, 8801 1, 8802 1, 8803 1, 8804 1, 8805 1, 8806 1, 8807 1, 8808 1, 8809 1, 8810 1, 8811 1, 8812 1, 8813 1, 8814 1, 8815 1, 8816 1, 8817 1, 8818 1, 8819 1, 8820 1, 8821 1, 8822 1, 8823 1, 8824 1, 8825 1, 8826 1, 8827 1, 8828 1, 8829 1, 8830 1, 8831 1, 8832 1, 8833 1, 8834 1, 8835 1, 8836 1, 8837 1, 8838 1, 8839 1, 8840 1, 8841 1, 8842 1, 8843 1, 8844 1, 8845 1, 8846 1, 8847 1, 8848 1, 8849 1, 8850 1, 8851 1, 8852 1, 8853 1, 8854 1, 8855 1, 8856 1, 8857 1, 8858 1, 8859 1, 8860 1, 8861 1, 8862 1, 8863 1, 8864 1, 8865 1, 8866 1, 8867 1, 8868 1, 8869 1, 8870 1, 8871 1, 8872 1, 8873 1, 8874 1, 8875 1, 8876 1, 8877 1, 8878 1, 8879 1, 8880 1, 8881 1, 8882 1, 8883 1, 8884 1, 8885 1, 8886 1, 8887 1, 8888 1, 8889 1, 8890 1, 8891 1, 8892 1, 8893 1, 8894 1, 8895 1, 8896 1, 8897 1, 8898 1, 8899 1, 8900 1, 8901 1, 8902 1, 8903 1, 8904 1, 8905 1, 8906 1, 8907 1, 8908 1, 8909 1, 8910 1, 8911 1, 8912 1, 8913 1, 8914 1, 8915 1, 8916 1, 8917 1, 8918 1, 8919 1, 8920 1, 8921 1, 8922 1, 8923 1, 8924 1, 8925 1, 8926 1, 8927 1, 8928 1, 8929 1, 8930 1, 8931 1, 8932 1, 8933 ] 8934 .as_slice(), 8935 ); 8936 attestation_object[31..63].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 8937 assert!(matches!(opts.start_ceremony()?.0.verify( 8938 RP_ID, 8939 &Registration { 8940 response: AuthenticatorAttestation::new( 8941 client_data_json, 8942 attestation_object, 8943 AuthTransports::NONE, 8944 ), 8945 authenticator_attachment: AuthenticatorAttachment::None, 8946 client_extension_results: ClientExtensionsOutputs { 8947 cred_props: None, 8948 prf: None, 8949 }, 8950 }, 8951 &RegistrationVerificationOptions::<&str, &str>::default(), 8952 )?.static_state.credential_public_key, UncompressedPubKey::MlDsa44(k) if **k.inner() == [1; 1312])); 8953 Ok(()) 8954 } 8955 #[expect( 8956 clippy::panic_in_result_fn, 8957 clippy::unwrap_in_result, 8958 clippy::unwrap_used, 8959 reason = "OK in tests" 8960 )] 8961 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 8962 #[test] 8963 #[cfg(feature = "custom")] 8964 fn mldsa44_auth() -> Result<(), AggErr> { 8965 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 8966 opts.public_key.challenge = Challenge(0); 8967 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 8968 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 8969 let mut authenticator_data = Vec::with_capacity(69); 8970 authenticator_data.extend_from_slice( 8971 [ 8972 // rpIdHash. 8973 // This will be overwritten later. 8974 0, 8975 0, 8976 0, 8977 0, 8978 0, 8979 0, 8980 0, 8981 0, 8982 0, 8983 0, 8984 0, 8985 0, 8986 0, 8987 0, 8988 0, 8989 0, 8990 0, 8991 0, 8992 0, 8993 0, 8994 0, 8995 0, 8996 0, 8997 0, 8998 0, 8999 0, 9000 0, 9001 0, 9002 0, 9003 0, 9004 0, 9005 0, 9006 // flags. 9007 // UP and UV (right-to-left). 9008 0b0000_0101, 9009 // signCount. 9010 // 0 as 32-bit big endian. 9011 0, 9012 0, 9013 0, 9014 0, 9015 ] 9016 .as_slice(), 9017 ); 9018 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 9019 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 9020 let mldsa44_key = MlDsaSigKey::<MlDsa44>::from_seed((&[0; 32]).into()); 9021 let sig: MlDsaSignature<MlDsa44> = mldsa44_key.sign(authenticator_data.as_slice()); 9022 let pub_key = mldsa44_key.verifying_key().encode(); 9023 authenticator_data.truncate(37); 9024 assert!(!opts.start_ceremony()?.0.verify( 9025 RP_ID, 9026 &DiscoverableAuthentication { 9027 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 9028 response: DiscoverableAuthenticatorAssertion::new( 9029 client_data_json, 9030 authenticator_data, 9031 sig.encode().0.to_vec(), 9032 UserHandle::from([0]), 9033 ), 9034 authenticator_attachment: AuthenticatorAttachment::None, 9035 }, 9036 &mut AuthenticatedCredential::new( 9037 CredentialId::try_from([0; 16].as_slice())?, 9038 &UserHandle::from([0]), 9039 StaticState { 9040 credential_public_key: CompressedPubKeyOwned::MlDsa44( 9041 MlDsa44PubKey::try_from(Box::from(pub_key.as_slice())).unwrap() 9042 ), 9043 extensions: AuthenticatorExtensionOutputStaticState { 9044 cred_protect: CredentialProtectionPolicy::None, 9045 hmac_secret: None, 9046 }, 9047 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 9048 }, 9049 DynamicState { 9050 user_verified: true, 9051 backup: Backup::NotEligible, 9052 sign_count: 0, 9053 authenticator_attachment: AuthenticatorAttachment::None, 9054 }, 9055 )?, 9056 &AuthenticationVerificationOptions::<&str, &str>::default(), 9057 )?); 9058 Ok(()) 9059 } 9060 #[expect( 9061 clippy::panic_in_result_fn, 9062 clippy::unwrap_in_result, 9063 clippy::unwrap_used, 9064 reason = "OK in tests" 9065 )] 9066 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 9067 #[expect(clippy::too_many_lines, reason = "a lot to test")] 9068 #[test] 9069 #[cfg(feature = "custom")] 9070 fn es256_reg() -> Result<(), AggErr> { 9071 let id = UserHandle::from([0]); 9072 let mut opts = CredentialCreationOptions::passkey( 9073 RP_ID, 9074 PublicKeyCredentialUserEntity { 9075 name: "foo".try_into()?, 9076 id: &id, 9077 display_name: DisplayName::Blank, 9078 }, 9079 Vec::new(), 9080 ); 9081 opts.public_key.challenge = Challenge(0); 9082 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 9083 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 9084 let mut attestation_object = Vec::with_capacity(210); 9085 attestation_object.extend_from_slice( 9086 [ 9087 CBOR_MAP | 3, 9088 CBOR_TEXT | 3, 9089 b'f', 9090 b'm', 9091 b't', 9092 CBOR_TEXT | 4, 9093 b'n', 9094 b'o', 9095 b'n', 9096 b'e', 9097 CBOR_TEXT | 7, 9098 b'a', 9099 b't', 9100 b't', 9101 b'S', 9102 b't', 9103 b'm', 9104 b't', 9105 CBOR_MAP, 9106 CBOR_TEXT | 8, 9107 b'a', 9108 b'u', 9109 b't', 9110 b'h', 9111 b'D', 9112 b'a', 9113 b't', 9114 b'a', 9115 CBOR_BYTES | 24, 9116 // Length is 148. 9117 148, 9118 // RP ID HASH. 9119 // This will be overwritten later. 9120 0, 9121 0, 9122 0, 9123 0, 9124 0, 9125 0, 9126 0, 9127 0, 9128 0, 9129 0, 9130 0, 9131 0, 9132 0, 9133 0, 9134 0, 9135 0, 9136 0, 9137 0, 9138 0, 9139 0, 9140 0, 9141 0, 9142 0, 9143 0, 9144 0, 9145 0, 9146 0, 9147 0, 9148 0, 9149 0, 9150 0, 9151 0, 9152 // FLAGS. 9153 // UP, UV, and AT (right-to-left). 9154 0b0100_0101, 9155 // COUNTER. 9156 // 0 as 32-bit big endian. 9157 0, 9158 0, 9159 0, 9160 0, 9161 // AAGUID. 9162 0, 9163 0, 9164 0, 9165 0, 9166 0, 9167 0, 9168 0, 9169 0, 9170 0, 9171 0, 9172 0, 9173 0, 9174 0, 9175 0, 9176 0, 9177 0, 9178 // L. 9179 // CREDENTIAL ID length is 16 as 16-bit big endian. 9180 0, 9181 16, 9182 // CREDENTIAL ID. 9183 0, 9184 0, 9185 0, 9186 0, 9187 0, 9188 0, 9189 0, 9190 0, 9191 0, 9192 0, 9193 0, 9194 0, 9195 0, 9196 0, 9197 0, 9198 0, 9199 CBOR_MAP | 5, 9200 // COSE kty. 9201 CBOR_UINT | 1, 9202 // COSE EC2. 9203 CBOR_UINT | 2, 9204 // COSE alg. 9205 CBOR_UINT | 3, 9206 // COSE ES256. 9207 CBOR_NEG | 6, 9208 // COSE EC2 crv. 9209 CBOR_NEG, 9210 // COSE P-256. 9211 CBOR_UINT | 1, 9212 // COSE EC2 x. 9213 CBOR_NEG | 1, 9214 CBOR_BYTES | 24, 9215 // Length is 32. 9216 32, 9217 // X-coordinate. This will be overwritten later. 9218 0, 9219 0, 9220 0, 9221 0, 9222 0, 9223 0, 9224 0, 9225 0, 9226 0, 9227 0, 9228 0, 9229 0, 9230 0, 9231 0, 9232 0, 9233 0, 9234 0, 9235 0, 9236 0, 9237 0, 9238 0, 9239 0, 9240 0, 9241 0, 9242 0, 9243 0, 9244 0, 9245 0, 9246 0, 9247 0, 9248 0, 9249 0, 9250 // COSE EC2 y. 9251 CBOR_NEG | 2, 9252 CBOR_BYTES | 24, 9253 // Length is 32. 9254 32, 9255 // Y-coordinate. This will be overwritten later. 9256 0, 9257 0, 9258 0, 9259 0, 9260 0, 9261 0, 9262 0, 9263 0, 9264 0, 9265 0, 9266 0, 9267 0, 9268 0, 9269 0, 9270 0, 9271 0, 9272 0, 9273 0, 9274 0, 9275 0, 9276 0, 9277 0, 9278 0, 9279 0, 9280 0, 9281 0, 9282 0, 9283 0, 9284 0, 9285 0, 9286 0, 9287 0, 9288 ] 9289 .as_slice(), 9290 ); 9291 attestation_object[30..62].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 9292 let p256_key = P256Key::from_bytes( 9293 &[ 9294 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 9295 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 9296 ] 9297 .into(), 9298 ) 9299 .unwrap() 9300 .verifying_key() 9301 .to_sec1_point(false); 9302 let x = p256_key.x().unwrap(); 9303 let y = p256_key.y().unwrap(); 9304 attestation_object[111..143].copy_from_slice(x); 9305 attestation_object[146..].copy_from_slice(y); 9306 assert!(matches!(opts.start_ceremony()?.0.verify( 9307 RP_ID, 9308 &Registration { 9309 response: AuthenticatorAttestation::new( 9310 client_data_json, 9311 attestation_object, 9312 AuthTransports::NONE, 9313 ), 9314 authenticator_attachment: AuthenticatorAttachment::None, 9315 client_extension_results: ClientExtensionsOutputs { 9316 cred_props: None, 9317 prf: None, 9318 }, 9319 }, 9320 &RegistrationVerificationOptions::<&str, &str>::default(), 9321 )?.static_state.credential_public_key, UncompressedPubKey::P256(k) if *k.x() == **x && *k.y() == **y)); 9322 Ok(()) 9323 } 9324 #[expect( 9325 clippy::panic_in_result_fn, 9326 clippy::unwrap_in_result, 9327 clippy::unwrap_used, 9328 reason = "OK in tests" 9329 )] 9330 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 9331 #[test] 9332 #[cfg(feature = "custom")] 9333 fn es256_auth() -> Result<(), AggErr> { 9334 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 9335 opts.public_key.challenge = Challenge(0); 9336 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 9337 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 9338 let mut authenticator_data = Vec::with_capacity(69); 9339 authenticator_data.extend_from_slice( 9340 [ 9341 // rpIdHash. 9342 // This will be overwritten later. 9343 0, 9344 0, 9345 0, 9346 0, 9347 0, 9348 0, 9349 0, 9350 0, 9351 0, 9352 0, 9353 0, 9354 0, 9355 0, 9356 0, 9357 0, 9358 0, 9359 0, 9360 0, 9361 0, 9362 0, 9363 0, 9364 0, 9365 0, 9366 0, 9367 0, 9368 0, 9369 0, 9370 0, 9371 0, 9372 0, 9373 0, 9374 0, 9375 // flags. 9376 // UP and UV (right-to-left). 9377 0b0000_0101, 9378 // signCount. 9379 // 0 as 32-bit big endian. 9380 0, 9381 0, 9382 0, 9383 0, 9384 ] 9385 .as_slice(), 9386 ); 9387 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 9388 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 9389 let p256_key = P256Key::from_bytes( 9390 &[ 9391 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 9392 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 9393 ] 9394 .into(), 9395 ) 9396 .unwrap(); 9397 let der_sig: P256DerSig = p256_key.sign(authenticator_data.as_slice()); 9398 let pub_key = p256_key.verifying_key().to_sec1_point(true); 9399 authenticator_data.truncate(37); 9400 assert!(!opts.start_ceremony()?.0.verify( 9401 RP_ID, 9402 &DiscoverableAuthentication { 9403 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 9404 response: DiscoverableAuthenticatorAssertion::new( 9405 client_data_json, 9406 authenticator_data, 9407 der_sig.as_bytes().into(), 9408 UserHandle::from([0]), 9409 ), 9410 authenticator_attachment: AuthenticatorAttachment::None, 9411 }, 9412 &mut AuthenticatedCredential::new( 9413 CredentialId::try_from([0; 16].as_slice())?, 9414 &UserHandle::from([0]), 9415 StaticState { 9416 credential_public_key: CompressedPubKeyOwned::P256(CompressedP256PubKey::from( 9417 ( 9418 (*pub_key.x().unwrap()).into(), 9419 pub_key.tag() == Tag::CompressedOddY 9420 ) 9421 ),), 9422 extensions: AuthenticatorExtensionOutputStaticState { 9423 cred_protect: CredentialProtectionPolicy::None, 9424 hmac_secret: None, 9425 }, 9426 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 9427 }, 9428 DynamicState { 9429 user_verified: true, 9430 backup: Backup::NotEligible, 9431 sign_count: 0, 9432 authenticator_attachment: AuthenticatorAttachment::None, 9433 }, 9434 )?, 9435 &AuthenticationVerificationOptions::<&str, &str>::default(), 9436 )?); 9437 Ok(()) 9438 } 9439 #[expect( 9440 clippy::panic_in_result_fn, 9441 clippy::unwrap_in_result, 9442 clippy::unwrap_used, 9443 reason = "OK in tests" 9444 )] 9445 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 9446 #[expect(clippy::too_many_lines, reason = "a lot to test")] 9447 #[test] 9448 #[cfg(feature = "custom")] 9449 fn es384_reg() -> Result<(), AggErr> { 9450 let id = UserHandle::from([0]); 9451 let mut opts = CredentialCreationOptions::passkey( 9452 RP_ID, 9453 PublicKeyCredentialUserEntity { 9454 name: "foo".try_into()?, 9455 id: &id, 9456 display_name: DisplayName::Blank, 9457 }, 9458 Vec::new(), 9459 ); 9460 opts.public_key.challenge = Challenge(0); 9461 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 9462 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 9463 let mut attestation_object = Vec::with_capacity(243); 9464 attestation_object.extend_from_slice( 9465 [ 9466 CBOR_MAP | 3, 9467 CBOR_TEXT | 3, 9468 b'f', 9469 b'm', 9470 b't', 9471 CBOR_TEXT | 4, 9472 b'n', 9473 b'o', 9474 b'n', 9475 b'e', 9476 // CBOR text of length 7. 9477 CBOR_TEXT | 7, 9478 b'a', 9479 b't', 9480 b't', 9481 b'S', 9482 b't', 9483 b'm', 9484 b't', 9485 CBOR_MAP, 9486 CBOR_TEXT | 8, 9487 b'a', 9488 b'u', 9489 b't', 9490 b'h', 9491 b'D', 9492 b'a', 9493 b't', 9494 b'a', 9495 CBOR_BYTES | 24, 9496 // Length is 181. 9497 181, 9498 // RP ID HASH. 9499 // This will be overwritten later. 9500 0, 9501 0, 9502 0, 9503 0, 9504 0, 9505 0, 9506 0, 9507 0, 9508 0, 9509 0, 9510 0, 9511 0, 9512 0, 9513 0, 9514 0, 9515 0, 9516 0, 9517 0, 9518 0, 9519 0, 9520 0, 9521 0, 9522 0, 9523 0, 9524 0, 9525 0, 9526 0, 9527 0, 9528 0, 9529 0, 9530 0, 9531 0, 9532 // FLAGS. 9533 // UP, UV, and AT (right-to-left). 9534 0b0100_0101, 9535 // COUNTER. 9536 // 0 as 32-bit big-endian. 9537 0, 9538 0, 9539 0, 9540 0, 9541 // AAGUID. 9542 0, 9543 0, 9544 0, 9545 0, 9546 0, 9547 0, 9548 0, 9549 0, 9550 0, 9551 0, 9552 0, 9553 0, 9554 0, 9555 0, 9556 0, 9557 0, 9558 // L. 9559 // CREDENTIAL ID length is 16 as 16-bit big endian. 9560 0, 9561 16, 9562 // CREDENTIAL ID. 9563 0, 9564 0, 9565 0, 9566 0, 9567 0, 9568 0, 9569 0, 9570 0, 9571 0, 9572 0, 9573 0, 9574 0, 9575 0, 9576 0, 9577 0, 9578 0, 9579 CBOR_MAP | 5, 9580 // COSE kty. 9581 CBOR_UINT | 1, 9582 // COSE EC2. 9583 CBOR_UINT | 2, 9584 // COSE alg. 9585 CBOR_UINT | 3, 9586 CBOR_NEG | 24, 9587 // COSE ES384. 9588 34, 9589 // COSE EC2 crv. 9590 CBOR_NEG, 9591 // COSE P-384. 9592 CBOR_UINT | 2, 9593 // COSE EC2 x. 9594 CBOR_NEG | 1, 9595 CBOR_BYTES | 24, 9596 // Length is 48. 9597 48, 9598 // X-coordinate. This will be overwritten later. 9599 0, 9600 0, 9601 0, 9602 0, 9603 0, 9604 0, 9605 0, 9606 0, 9607 0, 9608 0, 9609 0, 9610 0, 9611 0, 9612 0, 9613 0, 9614 0, 9615 0, 9616 0, 9617 0, 9618 0, 9619 0, 9620 0, 9621 0, 9622 0, 9623 0, 9624 0, 9625 0, 9626 0, 9627 0, 9628 0, 9629 0, 9630 0, 9631 0, 9632 0, 9633 0, 9634 0, 9635 0, 9636 0, 9637 0, 9638 0, 9639 0, 9640 0, 9641 0, 9642 0, 9643 0, 9644 0, 9645 0, 9646 0, 9647 // COSE EC2 y. 9648 CBOR_NEG | 2, 9649 CBOR_BYTES | 24, 9650 // Length is 48. 9651 48, 9652 // Y-coordinate. This will be overwritten later. 9653 0, 9654 0, 9655 0, 9656 0, 9657 0, 9658 0, 9659 0, 9660 0, 9661 0, 9662 0, 9663 0, 9664 0, 9665 0, 9666 0, 9667 0, 9668 0, 9669 0, 9670 0, 9671 0, 9672 0, 9673 0, 9674 0, 9675 0, 9676 0, 9677 0, 9678 0, 9679 0, 9680 0, 9681 0, 9682 0, 9683 0, 9684 0, 9685 0, 9686 0, 9687 0, 9688 0, 9689 0, 9690 0, 9691 0, 9692 0, 9693 0, 9694 0, 9695 0, 9696 0, 9697 0, 9698 0, 9699 0, 9700 0, 9701 ] 9702 .as_slice(), 9703 ); 9704 attestation_object[30..62].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 9705 let p384_key = P384Key::from_bytes( 9706 &[ 9707 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 9708 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 9709 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 9710 ] 9711 .into(), 9712 ) 9713 .unwrap() 9714 .verifying_key() 9715 .to_sec1_point(false); 9716 let x = p384_key.x().unwrap(); 9717 let y = p384_key.y().unwrap(); 9718 attestation_object[112..160].copy_from_slice(x); 9719 attestation_object[163..].copy_from_slice(y); 9720 assert!(matches!(opts.start_ceremony()?.0.verify( 9721 RP_ID, 9722 &Registration { 9723 response: AuthenticatorAttestation::new( 9724 client_data_json, 9725 attestation_object, 9726 AuthTransports::NONE, 9727 ), 9728 authenticator_attachment: AuthenticatorAttachment::None, 9729 client_extension_results: ClientExtensionsOutputs { 9730 cred_props: None, 9731 prf: None, 9732 }, 9733 }, 9734 &RegistrationVerificationOptions::<&str, &str>::default(), 9735 )?.static_state.credential_public_key, UncompressedPubKey::P384(k) if *k.x() == **x && *k.y() == **y)); 9736 Ok(()) 9737 } 9738 #[expect( 9739 clippy::panic_in_result_fn, 9740 clippy::unwrap_in_result, 9741 clippy::unwrap_used, 9742 reason = "OK in tests" 9743 )] 9744 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 9745 #[test] 9746 #[cfg(feature = "custom")] 9747 fn es384_auth() -> Result<(), AggErr> { 9748 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 9749 opts.public_key.challenge = Challenge(0); 9750 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 9751 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 9752 let mut authenticator_data = Vec::with_capacity(69); 9753 authenticator_data.extend_from_slice( 9754 [ 9755 // rpIdHash. 9756 // This will be overwritten later. 9757 0, 9758 0, 9759 0, 9760 0, 9761 0, 9762 0, 9763 0, 9764 0, 9765 0, 9766 0, 9767 0, 9768 0, 9769 0, 9770 0, 9771 0, 9772 0, 9773 0, 9774 0, 9775 0, 9776 0, 9777 0, 9778 0, 9779 0, 9780 0, 9781 0, 9782 0, 9783 0, 9784 0, 9785 0, 9786 0, 9787 0, 9788 0, 9789 // flags. 9790 // UP and UV (right-to-left). 9791 0b0000_0101, 9792 // signCount. 9793 // 0 as 32-bit big-endian. 9794 0, 9795 0, 9796 0, 9797 0, 9798 ] 9799 .as_slice(), 9800 ); 9801 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 9802 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 9803 let p384_key = P384Key::from_bytes( 9804 &[ 9805 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 9806 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 9807 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 9808 ] 9809 .into(), 9810 ) 9811 .unwrap(); 9812 let der_sig: P384DerSig = p384_key.sign(authenticator_data.as_slice()); 9813 let pub_key = p384_key.verifying_key().to_sec1_point(true); 9814 authenticator_data.truncate(37); 9815 assert!(!opts.start_ceremony()?.0.verify( 9816 RP_ID, 9817 &DiscoverableAuthentication { 9818 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 9819 response: DiscoverableAuthenticatorAssertion::new( 9820 client_data_json, 9821 authenticator_data, 9822 der_sig.as_bytes().into(), 9823 UserHandle::from([0]), 9824 ), 9825 authenticator_attachment: AuthenticatorAttachment::None, 9826 }, 9827 &mut AuthenticatedCredential::new( 9828 CredentialId::try_from([0; 16].as_slice())?, 9829 &UserHandle::from([0]), 9830 StaticState { 9831 credential_public_key: CompressedPubKeyOwned::P384(CompressedP384PubKey::from( 9832 ( 9833 (*pub_key.x().unwrap()).into(), 9834 pub_key.tag() == Tag::CompressedOddY 9835 ) 9836 ),), 9837 extensions: AuthenticatorExtensionOutputStaticState { 9838 cred_protect: CredentialProtectionPolicy::None, 9839 hmac_secret: None, 9840 }, 9841 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 9842 }, 9843 DynamicState { 9844 user_verified: true, 9845 backup: Backup::NotEligible, 9846 sign_count: 0, 9847 authenticator_attachment: AuthenticatorAttachment::None, 9848 }, 9849 )?, 9850 &AuthenticationVerificationOptions::<&str, &str>::default(), 9851 )?); 9852 Ok(()) 9853 } 9854 #[expect( 9855 clippy::panic_in_result_fn, 9856 clippy::unwrap_in_result, 9857 clippy::unwrap_used, 9858 reason = "OK in tests" 9859 )] 9860 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 9861 #[expect(clippy::too_many_lines, reason = "a lot to test")] 9862 #[expect(clippy::many_single_char_names, reason = "fine")] 9863 #[test] 9864 #[cfg(feature = "custom")] 9865 fn rs256_reg() -> Result<(), AggErr> { 9866 let id = UserHandle::from([0]); 9867 let mut opts = CredentialCreationOptions::passkey( 9868 RP_ID, 9869 PublicKeyCredentialUserEntity { 9870 name: "foo".try_into()?, 9871 id: &id, 9872 display_name: DisplayName::Blank, 9873 }, 9874 Vec::new(), 9875 ); 9876 opts.public_key.challenge = Challenge(0); 9877 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 9878 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 9879 let mut attestation_object = Vec::with_capacity(406); 9880 attestation_object.extend_from_slice( 9881 [ 9882 CBOR_MAP | 3, 9883 CBOR_TEXT | 3, 9884 b'f', 9885 b'm', 9886 b't', 9887 CBOR_TEXT | 4, 9888 b'n', 9889 b'o', 9890 b'n', 9891 b'e', 9892 CBOR_TEXT | 7, 9893 b'a', 9894 b't', 9895 b't', 9896 b'S', 9897 b't', 9898 b'm', 9899 b't', 9900 CBOR_MAP, 9901 CBOR_TEXT | 8, 9902 b'a', 9903 b'u', 9904 b't', 9905 b'h', 9906 b'D', 9907 b'a', 9908 b't', 9909 b'a', 9910 CBOR_BYTES | 25, 9911 // Length is 343 as 16-bit big-endian. 9912 1, 9913 87, 9914 // RP ID HASH. 9915 // This will be overwritten later. 9916 0, 9917 0, 9918 0, 9919 0, 9920 0, 9921 0, 9922 0, 9923 0, 9924 0, 9925 0, 9926 0, 9927 0, 9928 0, 9929 0, 9930 0, 9931 0, 9932 0, 9933 0, 9934 0, 9935 0, 9936 0, 9937 0, 9938 0, 9939 0, 9940 0, 9941 0, 9942 0, 9943 0, 9944 0, 9945 0, 9946 0, 9947 0, 9948 // FLAGS. 9949 // UP, UV, and AT (right-to-left). 9950 0b0100_0101, 9951 // COUNTER. 9952 // 0 as 32-bit big-endian. 9953 0, 9954 0, 9955 0, 9956 0, 9957 // AAGUID. 9958 0, 9959 0, 9960 0, 9961 0, 9962 0, 9963 0, 9964 0, 9965 0, 9966 0, 9967 0, 9968 0, 9969 0, 9970 0, 9971 0, 9972 0, 9973 0, 9974 // L. 9975 // CREDENTIAL ID length is 16 as 16-bit big endian. 9976 0, 9977 16, 9978 // CREDENTIAL ID. 9979 0, 9980 0, 9981 0, 9982 0, 9983 0, 9984 0, 9985 0, 9986 0, 9987 0, 9988 0, 9989 0, 9990 0, 9991 0, 9992 0, 9993 0, 9994 0, 9995 CBOR_MAP | 4, 9996 // COSE kty. 9997 CBOR_UINT | 1, 9998 // COSE RSA. 9999 CBOR_UINT | 3, 10000 // COSE alg. 10001 CBOR_UINT | 3, 10002 CBOR_NEG | 25, 10003 // COSE RS256. 10004 1, 10005 0, 10006 // COSE n. 10007 CBOR_NEG, 10008 CBOR_BYTES | 25, 10009 // Length is 256 as 16-bit big-endian. 10010 1, 10011 0, 10012 // N. This will be overwritten later. 10013 0, 10014 0, 10015 0, 10016 0, 10017 0, 10018 0, 10019 0, 10020 0, 10021 0, 10022 0, 10023 0, 10024 0, 10025 0, 10026 0, 10027 0, 10028 0, 10029 0, 10030 0, 10031 0, 10032 0, 10033 0, 10034 0, 10035 0, 10036 0, 10037 0, 10038 0, 10039 0, 10040 0, 10041 0, 10042 0, 10043 0, 10044 0, 10045 0, 10046 0, 10047 0, 10048 0, 10049 0, 10050 0, 10051 0, 10052 0, 10053 0, 10054 0, 10055 0, 10056 0, 10057 0, 10058 0, 10059 0, 10060 0, 10061 0, 10062 0, 10063 0, 10064 0, 10065 0, 10066 0, 10067 0, 10068 0, 10069 0, 10070 0, 10071 0, 10072 0, 10073 0, 10074 0, 10075 0, 10076 0, 10077 0, 10078 0, 10079 0, 10080 0, 10081 0, 10082 0, 10083 0, 10084 0, 10085 0, 10086 0, 10087 0, 10088 0, 10089 0, 10090 0, 10091 0, 10092 0, 10093 0, 10094 0, 10095 0, 10096 0, 10097 0, 10098 0, 10099 0, 10100 0, 10101 0, 10102 0, 10103 0, 10104 0, 10105 0, 10106 0, 10107 0, 10108 0, 10109 0, 10110 0, 10111 0, 10112 0, 10113 0, 10114 0, 10115 0, 10116 0, 10117 0, 10118 0, 10119 0, 10120 0, 10121 0, 10122 0, 10123 0, 10124 0, 10125 0, 10126 0, 10127 0, 10128 0, 10129 0, 10130 0, 10131 0, 10132 0, 10133 0, 10134 0, 10135 0, 10136 0, 10137 0, 10138 0, 10139 0, 10140 0, 10141 0, 10142 0, 10143 0, 10144 0, 10145 0, 10146 0, 10147 0, 10148 0, 10149 0, 10150 0, 10151 0, 10152 0, 10153 0, 10154 0, 10155 0, 10156 0, 10157 0, 10158 0, 10159 0, 10160 0, 10161 0, 10162 0, 10163 0, 10164 0, 10165 0, 10166 0, 10167 0, 10168 0, 10169 0, 10170 0, 10171 0, 10172 0, 10173 0, 10174 0, 10175 0, 10176 0, 10177 0, 10178 0, 10179 0, 10180 0, 10181 0, 10182 0, 10183 0, 10184 0, 10185 0, 10186 0, 10187 0, 10188 0, 10189 0, 10190 0, 10191 0, 10192 0, 10193 0, 10194 0, 10195 0, 10196 0, 10197 0, 10198 0, 10199 0, 10200 0, 10201 0, 10202 0, 10203 0, 10204 0, 10205 0, 10206 0, 10207 0, 10208 0, 10209 0, 10210 0, 10211 0, 10212 0, 10213 0, 10214 0, 10215 0, 10216 0, 10217 0, 10218 0, 10219 0, 10220 0, 10221 0, 10222 0, 10223 0, 10224 0, 10225 0, 10226 0, 10227 0, 10228 0, 10229 0, 10230 0, 10231 0, 10232 0, 10233 0, 10234 0, 10235 0, 10236 0, 10237 0, 10238 0, 10239 0, 10240 0, 10241 0, 10242 0, 10243 0, 10244 0, 10245 0, 10246 0, 10247 0, 10248 0, 10249 0, 10250 0, 10251 0, 10252 0, 10253 0, 10254 0, 10255 0, 10256 0, 10257 0, 10258 0, 10259 0, 10260 0, 10261 0, 10262 0, 10263 0, 10264 0, 10265 0, 10266 0, 10267 0, 10268 0, 10269 // COSE e. 10270 CBOR_NEG | 1, 10271 CBOR_BYTES | 3, 10272 // 65537 as 24-bit big-endian. 10273 1, 10274 0, 10275 1, 10276 ] 10277 .as_slice(), 10278 ); 10279 attestation_object[31..63].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 10280 let n = [ 10281 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 10282 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 10283 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 10284 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 10285 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 10286 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 10287 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 10288 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 10289 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 10290 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 10291 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 10292 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 10293 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 10294 153, 79, 0, 133, 78, 7, 218, 165, 241, 10295 ]; 10296 let e = 0x0001_0001; 10297 let d = [ 10298 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 10299 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 10300 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 10301 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 10302 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 10303 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 10304 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 10305 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 10306 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 10307 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 10308 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 10309 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 10310 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 10311 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 10312 ]; 10313 let p = BoxedUint::from_le_slice_vartime( 10314 [ 10315 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60, 10316 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229, 10317 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161, 10318 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16, 10319 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79, 10320 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191, 10321 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188, 10322 24, 247, 10323 ] 10324 .as_slice(), 10325 ); 10326 let p_2 = BoxedUint::from_le_slice_vartime( 10327 [ 10328 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78, 10329 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177, 10330 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29, 10331 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11, 10332 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252, 10333 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214, 10334 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90, 10335 250, 10336 ] 10337 .as_slice(), 10338 ); 10339 let rsa_key = RsaKey::<Sha256>::new( 10340 RsaPrivateKey::from_components( 10341 BoxedUint::from_le_slice_vartime(n.as_slice()), 10342 e.into(), 10343 BoxedUint::from_le_slice_vartime(d.as_slice()), 10344 vec![p, p_2], 10345 ) 10346 .unwrap(), 10347 ) 10348 .verifying_key(); 10349 let n_other = rsa_key.as_ref().n().to_be_bytes(); 10350 attestation_object[113..369].copy_from_slice(&n_other); 10351 assert!(matches!(opts.start_ceremony()?.0.verify( 10352 RP_ID, 10353 &Registration { 10354 response: AuthenticatorAttestation::new( 10355 client_data_json, 10356 attestation_object, 10357 AuthTransports::NONE, 10358 ), 10359 authenticator_attachment: AuthenticatorAttachment::None, 10360 client_extension_results: ClientExtensionsOutputs { 10361 cred_props: None, 10362 prf: None, 10363 }, 10364 }, 10365 &RegistrationVerificationOptions::<&str, &str>::default(), 10366 )?.static_state.credential_public_key, UncompressedPubKey::Rsa(k) if **k.n() == *n_other && k.e() == e)); 10367 Ok(()) 10368 } 10369 #[expect( 10370 clippy::panic_in_result_fn, 10371 clippy::unwrap_in_result, 10372 clippy::unwrap_used, 10373 reason = "OK in tests" 10374 )] 10375 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 10376 #[expect(clippy::too_many_lines, reason = "a lot to test")] 10377 #[test] 10378 #[cfg(feature = "custom")] 10379 fn rs256_auth() -> Result<(), AggErr> { 10380 let mut opts = DiscoverableCredentialRequestOptions::passkey(RP_ID); 10381 opts.public_key.challenge = Challenge(0); 10382 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 10383 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 10384 let mut authenticator_data = Vec::with_capacity(69); 10385 authenticator_data.extend_from_slice( 10386 [ 10387 // rpIdHash. 10388 // This will be overwritten later. 10389 0, 10390 0, 10391 0, 10392 0, 10393 0, 10394 0, 10395 0, 10396 0, 10397 0, 10398 0, 10399 0, 10400 0, 10401 0, 10402 0, 10403 0, 10404 0, 10405 0, 10406 0, 10407 0, 10408 0, 10409 0, 10410 0, 10411 0, 10412 0, 10413 0, 10414 0, 10415 0, 10416 0, 10417 0, 10418 0, 10419 0, 10420 0, 10421 // flags. 10422 // UP and UV (right-to-left). 10423 0b0000_0101, 10424 // signCount. 10425 // 0 as 32-bit big-endian. 10426 0, 10427 0, 10428 0, 10429 0, 10430 ] 10431 .as_slice(), 10432 ); 10433 authenticator_data[..32].copy_from_slice(&Sha256::digest(RP_ID.as_ref().as_bytes())); 10434 authenticator_data.extend_from_slice(&Sha256::digest(client_data_json.as_slice())); 10435 let n = [ 10436 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 10437 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 10438 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 10439 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 10440 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 10441 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 10442 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 10443 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 10444 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 10445 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 10446 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 10447 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 10448 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 10449 153, 79, 0, 133, 78, 7, 218, 165, 241, 10450 ]; 10451 let e = 0x0001_0001; 10452 let d = [ 10453 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 10454 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 10455 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 10456 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 10457 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 10458 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 10459 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 10460 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 10461 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 10462 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 10463 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 10464 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 10465 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 10466 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 10467 ]; 10468 let p = BoxedUint::from_le_slice_vartime( 10469 [ 10470 215, 166, 5, 21, 11, 179, 41, 77, 198, 92, 165, 48, 77, 162, 42, 41, 206, 141, 60, 10471 69, 47, 164, 19, 92, 46, 72, 100, 238, 100, 53, 214, 197, 163, 185, 6, 140, 229, 10472 250, 195, 77, 8, 12, 5, 236, 178, 173, 86, 201, 43, 213, 165, 51, 108, 101, 161, 10473 99, 76, 240, 14, 234, 76, 197, 137, 53, 198, 168, 135, 205, 212, 198, 120, 29, 16, 10474 82, 98, 233, 236, 177, 12, 171, 141, 100, 107, 146, 33, 176, 125, 202, 172, 79, 10475 147, 179, 30, 62, 247, 206, 169, 19, 168, 114, 26, 73, 108, 178, 105, 84, 89, 191, 10476 168, 253, 228, 214, 54, 16, 212, 199, 111, 72, 3, 41, 247, 227, 165, 244, 32, 188, 10477 24, 247, 10478 ] 10479 .as_slice(), 10480 ); 10481 let p_2 = BoxedUint::from_le_slice_vartime( 10482 [ 10483 41, 25, 198, 240, 134, 206, 121, 57, 11, 5, 134, 192, 212, 77, 229, 197, 14, 78, 10484 85, 212, 190, 114, 179, 188, 21, 171, 174, 12, 104, 74, 15, 164, 136, 173, 62, 177, 10485 141, 213, 93, 102, 147, 83, 59, 124, 146, 59, 175, 213, 55, 27, 25, 248, 154, 29, 10486 39, 85, 50, 235, 134, 60, 203, 106, 186, 195, 190, 185, 71, 169, 142, 236, 92, 11, 10487 250, 187, 198, 8, 201, 184, 120, 178, 227, 87, 63, 243, 89, 227, 234, 184, 28, 252, 10488 112, 211, 193, 69, 23, 92, 5, 72, 93, 53, 69, 159, 73, 160, 105, 244, 249, 94, 214, 10489 173, 9, 236, 4, 255, 129, 11, 224, 140, 252, 168, 57, 143, 176, 241, 60, 219, 90, 10490 250, 10491 ] 10492 .as_slice(), 10493 ); 10494 let rsa_key = RsaKey::<Sha256>::new( 10495 RsaPrivateKey::from_components( 10496 BoxedUint::from_le_slice_vartime(n.as_slice()), 10497 e.into(), 10498 BoxedUint::from_le_slice_vartime(d.as_slice()), 10499 vec![p, p_2], 10500 ) 10501 .unwrap(), 10502 ); 10503 let rsa_pub = rsa_key.verifying_key(); 10504 let sig = rsa_key.sign(authenticator_data.as_slice()).to_vec(); 10505 authenticator_data.truncate(37); 10506 assert!(!opts.start_ceremony()?.0.verify( 10507 RP_ID, 10508 &DiscoverableAuthentication { 10509 raw_id: CredentialId::try_from(vec![0; 16].into_boxed_slice())?, 10510 response: DiscoverableAuthenticatorAssertion::new( 10511 client_data_json, 10512 authenticator_data, 10513 sig, 10514 UserHandle::from([0]), 10515 ), 10516 authenticator_attachment: AuthenticatorAttachment::None, 10517 }, 10518 &mut AuthenticatedCredential::new( 10519 CredentialId::try_from([0; 16].as_slice())?, 10520 &UserHandle::from([0]), 10521 StaticState { 10522 credential_public_key: CompressedPubKeyOwned::Rsa( 10523 RsaPubKey::try_from((rsa_pub.as_ref().n().to_be_bytes(), e)).unwrap(), 10524 ), 10525 extensions: AuthenticatorExtensionOutputStaticState { 10526 cred_protect: CredentialProtectionPolicy::None, 10527 hmac_secret: None, 10528 }, 10529 client_extension_results: ClientExtensionsOutputsStaticState { prf: None } 10530 }, 10531 DynamicState { 10532 user_verified: true, 10533 backup: Backup::NotEligible, 10534 sign_count: 0, 10535 authenticator_attachment: AuthenticatorAttachment::None, 10536 }, 10537 )?, 10538 &AuthenticationVerificationOptions::<&str, &str>::default(), 10539 )?); 10540 Ok(()) 10541 } 10542 }