request.rs (141871B)
1 #[cfg(doc)] 2 use super::{ 3 request::{ 4 auth::{ 5 AllowedCredential, AllowedCredentials, CredentialSpecificExtension, 6 PublicKeyCredentialRequestOptions, 7 }, 8 register::PublicKeyCredentialCreationOptions, 9 }, 10 response::register::ClientExtensionsOutputs, 11 }; 12 use crate::{ 13 request::{ 14 auth::AuthenticationServerState, 15 error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr}, 16 register::{RegistrationServerState, RegistrationVerificationOptions}, 17 }, 18 response::{ 19 AuthData as _, AuthDataContainer, AuthResponse, AuthTransports, Backup, CeremonyErr, 20 CredentialId, Origin, Response, SentChallenge, 21 }, 22 }; 23 #[cfg(any(doc, not(feature = "serializable_server_state")))] 24 use core::hash::{BuildHasher, Hash, Hasher}; 25 use core::{ 26 borrow::Borrow, 27 fmt::{self, Display, Formatter}, 28 num::NonZeroU32, 29 str::FromStr, 30 }; 31 use rsa::sha2::{Digest as _, Sha256}; 32 #[cfg(feature = "serializable_server_state")] 33 use std::time::SystemTime; 34 #[cfg(any(doc, not(feature = "serializable_server_state")))] 35 use std::{collections::HashSet, time::Instant}; 36 use url::Url as Uri; 37 /// Contains functionality for beginning the 38 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony). 39 /// 40 /// # Examples 41 /// 42 /// ``` 43 /// # #[cfg(not(feature = "serializable_server_state"))] 44 /// # use webauthn_rp::request::{FixedCapHashSet, InsertResult}; 45 /// # use webauthn_rp::{ 46 /// # request::{ 47 /// # auth::{AllowedCredentials, PublicKeyCredentialRequestOptions}, 48 /// # register::{UserHandle, USER_HANDLE_MAX_LEN}, 49 /// # AsciiDomain, Credentials, PublicKeyCredentialDescriptor, RpId, 50 /// # }, 51 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 52 /// # AggErr, 53 /// # }; 54 /// # #[cfg(not(feature = "serializable_server_state"))] 55 /// let mut ceremonies = FixedCapHashSet::new(128); 56 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 57 /// let (server, client) = PublicKeyCredentialRequestOptions::passkey(&rp_id).start_ceremony()?; 58 /// # #[cfg(not(feature = "serializable_server_state"))] 59 /// assert!(matches!( 60 /// ceremonies.insert_or_replace_all_expired(server), 61 /// InsertResult::Success 62 /// )); 63 /// # #[cfg(feature = "serde")] 64 /// assert!(serde_json::to_string(&client).is_ok()); 65 /// let user_handle = get_user_handle(); 66 /// # #[cfg(feature = "custom")] 67 /// let creds = get_registered_credentials((&user_handle).into())?; 68 /// # #[cfg(feature = "custom")] 69 /// let (server_2, client_2) = 70 /// PublicKeyCredentialRequestOptions::second_factor(&rp_id, creds)?.start_ceremony()?; 71 /// # #[cfg(all(feature = "custom", not(feature = "serializable_server_state")))] 72 /// assert!(matches!( 73 /// ceremonies.insert_or_replace_all_expired(server_2), 74 /// InsertResult::Success 75 /// )); 76 /// # #[cfg(all(feature = "custom", feature = "serde"))] 77 /// assert!(serde_json::to_string(&client_2).is_ok()); 78 /// /// Extract `UserHandle` from session cookie. 79 /// fn get_user_handle() -> UserHandle<[u8; USER_HANDLE_MAX_LEN]> { 80 /// // ⋮ 81 /// # UserHandle::new_rand() 82 /// } 83 /// # #[cfg(feature = "custom")] 84 /// /// Fetch the `AllowedCredentials` associated with `user`. 85 /// fn get_registered_credentials(user: UserHandle<&[u8]>) -> Result<AllowedCredentials, AggErr> { 86 /// // ⋮ 87 /// # let mut creds = AllowedCredentials::new(); 88 /// # creds.push( 89 /// # PublicKeyCredentialDescriptor { 90 /// # id: CredentialId::try_from(vec![0; CRED_ID_MIN_LEN])?, 91 /// # transports: AuthTransports::NONE, 92 /// # } 93 /// # .into(), 94 /// # ); 95 /// # Ok(creds) 96 /// } 97 /// # Ok::<_, AggErr>(()) 98 /// ``` 99 pub mod auth; 100 /// Contains error types. 101 pub mod error; 102 /// Contains functionality for beginning the 103 /// [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony). 104 /// 105 /// # Examples 106 /// 107 /// ``` 108 /// # #[cfg(not(feature = "serializable_server_state"))] 109 /// # use webauthn_rp::request::{FixedCapHashSet, InsertResult}; 110 /// # use webauthn_rp::{ 111 /// # request::{ 112 /// # register::{ 113 /// # PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MAX_LEN, 114 /// # }, 115 /// # AsciiDomain, PublicKeyCredentialDescriptor, RpId 116 /// # }, 117 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 118 /// # AggErr, 119 /// # }; 120 /// # #[cfg(not(feature = "serializable_server_state"))] 121 /// let mut ceremonies = FixedCapHashSet::new(128); 122 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 123 /// let user_handle = get_user_handle(); 124 /// let handle = (&user_handle).into(); 125 /// let user = get_user_entity(handle)?; 126 /// let creds = get_registered_credentials(handle)?; 127 /// let (server, client) = PublicKeyCredentialCreationOptions::passkey(&rp_id, user.clone(), creds) 128 /// .start_ceremony()?; 129 /// # #[cfg(not(feature = "serializable_server_state"))] 130 /// assert!(matches!( 131 /// ceremonies.insert_or_replace_all_expired(server), 132 /// InsertResult::Success 133 /// )); 134 /// # #[cfg(feature = "serde")] 135 /// assert!(serde_json::to_string(&client).is_ok()); 136 /// let creds_2 = get_registered_credentials(handle)?; 137 /// let (server_2, client_2) = 138 /// PublicKeyCredentialCreationOptions::second_factor(&rp_id, user, creds_2).start_ceremony()?; 139 /// # #[cfg(not(feature = "serializable_server_state"))] 140 /// assert!(matches!( 141 /// ceremonies.insert_or_replace_all_expired(server_2), 142 /// InsertResult::Success 143 /// )); 144 /// # #[cfg(feature = "serde")] 145 /// assert!(serde_json::to_string(&client_2).is_ok()); 146 /// /// Extract `UserHandle` from session cookie if this is not the first credential registered. 147 /// fn get_user_handle() -> UserHandle<[u8; USER_HANDLE_MAX_LEN]> { 148 /// // ⋮ 149 /// # UserHandle::new_rand() 150 /// } 151 /// /// Fetch `PublicKeyCredentialUserEntity` info associated with `user`. 152 /// /// 153 /// /// If this is the first time a credential is being registered, then `PublicKeyCredentialUserEntity` 154 /// /// will need to be constructed with `name` and `display_name` passed from the client and `UserHandle::new` 155 /// /// used for `id`. Once created, this info can be stored such that the entity information 156 /// /// does not need to be requested for subsequent registrations. 157 /// fn get_user_entity(user: UserHandle<&[u8]>) -> Result<PublicKeyCredentialUserEntity<&[u8]>, AggErr> { 158 /// // ⋮ 159 /// # Ok(PublicKeyCredentialUserEntity { 160 /// # name: "foo".try_into()?, 161 /// # id: user, 162 /// # display_name: None, 163 /// # }) 164 /// } 165 /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`. 166 /// /// 167 /// /// This doesn't need to be called when this is the first credential registered for `user`; instead 168 /// /// an empty `Vec` should be passed. 169 /// fn get_registered_credentials( 170 /// user: UserHandle<&[u8]>, 171 /// ) -> Result<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>, AggErr> { 172 /// // ⋮ 173 /// # Ok(Vec::new()) 174 /// } 175 /// # Ok::<_, AggErr>(()) 176 /// ``` 177 pub mod register; 178 /// Contains functionality to serialize data to a client. 179 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 180 #[cfg(feature = "serde")] 181 mod ser; 182 /// Contains functionality to (de)serialize data needed for [`RegistrationServerState`] and 183 /// [`AuthenticationServerState`] to a data store. 184 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 185 #[cfg(feature = "serializable_server_state")] 186 pub(super) mod ser_server_state; 187 // `Challenge` must _never_ be constructable directly or indirectly; thus its tuple field must always be private, 188 // and it must never implement `trait`s (e.g., `Clone`) that would allow indirect creation. It must only ever 189 // be constructed via `Self::new` or `Self::default`. In contrast downstream code must be able to construct 190 // `SentChallenge` since it is used during ceremony validation; thus we must keep `Challenge` and `SentChallenge` 191 // as separate types. 192 /// [Cryptographic challenge](https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges). 193 #[derive(Debug)] 194 pub struct Challenge(u128); 195 impl Challenge { 196 // This won't `panic` since 4/3 of 16 is less than `usize::MAX`. 197 /// The number of bytes a `Challenge` takes to encode in base64url. 198 #[expect(clippy::unwrap_used, reason = "we want to crash when there is a bug")] 199 pub(super) const BASE64_LEN: usize = super::base64url_nopad_len(16).unwrap(); 200 /// Generates a random `Challenge`. 201 /// 202 /// # Examples 203 /// 204 /// ``` 205 /// # use webauthn_rp::request::Challenge; 206 /// // The probability of a `Challenge` being 0 (assuming a good entropy 207 /// // source) is 2^-128 ≈ 2.9 x 10^-39. 208 /// assert_ne!(Challenge::new().into_data(), 0); 209 /// ``` 210 #[inline] 211 #[must_use] 212 pub fn new() -> Self { 213 Self(rand::random()) 214 } 215 /// Returns the contained `u128` consuming `self`. 216 #[inline] 217 #[must_use] 218 pub const fn into_data(self) -> u128 { 219 self.0 220 } 221 /// Returns the contained `u128`. 222 #[inline] 223 #[must_use] 224 pub const fn as_data(&self) -> u128 { 225 self.0 226 } 227 } 228 impl Default for Challenge { 229 /// Same as [`Self::new`]. 230 #[inline] 231 fn default() -> Self { 232 Self::new() 233 } 234 } 235 impl From<Challenge> for u128 { 236 #[inline] 237 fn from(value: Challenge) -> Self { 238 value.0 239 } 240 } 241 impl From<&Challenge> for u128 { 242 #[inline] 243 fn from(value: &Challenge) -> Self { 244 value.0 245 } 246 } 247 /// A [domain](https://url.spec.whatwg.org/#concept-domain) in representation format consisting of only and any 248 /// ASCII. 249 /// 250 /// The only ASCII character disallowed in a label is `'.'` since it is used exclusively as a separator. Every 251 /// label must have length inclusively between 1 and 63, and the total length of the domain must be at most 253 252 /// when a trailing `'.'` does not exist; otherwise the max length is 254. The root domain (i.e., `'.'`) is not 253 /// allowed. 254 #[derive(Clone, Debug, Eq, PartialEq)] 255 pub struct AsciiDomain(String); 256 impl AsciiDomain { 257 /// Removes a trailing `'.'` if it exists. 258 /// 259 /// # Examples 260 /// 261 /// ``` 262 /// # use webauthn_rp::request::{error::AsciiDomainErr, AsciiDomain}; 263 /// let mut dom = AsciiDomain::try_from("example.com.".to_owned())?; 264 /// dom.remove_trailing_dot(); 265 /// assert_eq!(dom.as_ref(), "example.com"); 266 /// # Ok::<_, AsciiDomainErr>(()) 267 /// ``` 268 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 269 #[inline] 270 pub fn remove_trailing_dot(&mut self) { 271 if *self 272 .0 273 .as_bytes() 274 .last() 275 .unwrap_or_else(|| unreachable!("there is a bug in AsciiDomain::try_from")) 276 == b'.' 277 { 278 self.0.pop(); 279 } 280 } 281 } 282 impl AsRef<str> for AsciiDomain { 283 #[inline] 284 fn as_ref(&self) -> &str { 285 self.0.as_str() 286 } 287 } 288 impl Borrow<str> for AsciiDomain { 289 #[inline] 290 fn borrow(&self) -> &str { 291 self.0.as_str() 292 } 293 } 294 impl From<AsciiDomain> for String { 295 #[inline] 296 fn from(value: AsciiDomain) -> Self { 297 value.0 298 } 299 } 300 impl PartialEq<&Self> for AsciiDomain { 301 #[inline] 302 fn eq(&self, other: &&Self) -> bool { 303 *self == **other 304 } 305 } 306 impl PartialEq<AsciiDomain> for &AsciiDomain { 307 #[inline] 308 fn eq(&self, other: &AsciiDomain) -> bool { 309 **self == *other 310 } 311 } 312 impl TryFrom<String> for AsciiDomain { 313 type Error = AsciiDomainErr; 314 /// Verifies `value` is an ASCII domain in representation format converting any uppercase ASCII into 315 /// lowercase. 316 /// 317 /// Note it is _strongly_ encouraged for `value` to only contain letters, numbers, hyphens, and underscores; 318 /// otherwise certain applications may consider it not a domain. If the original domain contains non-ASCII, then 319 /// one must encode it in Punycode _before_ calling this function. Domains that have a trailing `'.'` will be 320 /// considered differently than domains without it; thus one will likely want to trim it if it does exist 321 /// (e.g., [`AsciiDomain::remove_trailing_dot`]). Because this allows any ASCII, one may want to ensure `value` 322 /// is not an IP address. 323 /// 324 /// # Examples 325 /// 326 /// ``` 327 /// # use webauthn_rp::request::{error::AsciiDomainErr, AsciiDomain}; 328 /// // Root `'.'` is not removed if it exists. 329 /// assert_ne!("example.com", AsciiDomain::try_from("example.com.".to_owned())?.as_ref()); 330 /// // Root domain (i.e., `'.'`) is not allowed. 331 /// assert!(AsciiDomain::try_from(".".to_owned()).is_err()); 332 /// // Uppercase is transformed into lowercase. 333 /// assert_eq!("example.com", AsciiDomain::try_from("ExAmPle.CoM".to_owned())?.as_ref()); 334 /// // The only ASCII character not allowed in a domain label is `'.'` as it is used exclusively to delimit 335 /// // labels. 336 /// assert_eq!("\x00", AsciiDomain::try_from("\x00".to_owned())?.as_ref()); 337 /// // Empty labels are not allowed. 338 /// assert!(AsciiDomain::try_from("example..com".to_owned()).is_err()); 339 /// // Labels cannot have length greater than 63. 340 /// let mut long_label = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned(); 341 /// assert_eq!(long_label.len(), 64); 342 /// assert!(AsciiDomain::try_from(long_label.clone()).is_err()); 343 /// long_label.pop(); 344 /// assert_eq!(long_label, AsciiDomain::try_from(long_label.clone())?.as_ref()); 345 /// // The maximum length of a domain is 254 if a trailing `'.'` exists; otherwise the max length is 253. 346 /// let mut long_domain = format!("{long_label}.{long_label}.{long_label}.{long_label}"); 347 /// long_domain.pop(); 348 /// long_domain.push('.'); 349 /// assert_eq!(long_domain.len(), 255); 350 /// assert!(AsciiDomain::try_from(long_domain.clone()).is_err()); 351 /// long_domain.pop(); 352 /// long_domain.pop(); 353 /// long_domain.push('.'); 354 /// assert_eq!(long_domain.len(), 254); 355 /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone())?.as_ref()); 356 /// long_domain.pop(); 357 /// long_domain.push('a'); 358 /// assert_eq!(long_domain.len(), 254); 359 /// assert!(AsciiDomain::try_from(long_domain.clone()).is_err()); 360 /// long_domain.pop(); 361 /// assert_eq!(long_domain.len(), 253); 362 /// assert_eq!(long_domain, AsciiDomain::try_from(long_domain.clone())?.as_ref()); 363 /// // Only ASCII is allowed; thus if a domain needs to be Punycode-encoded, then it must be _before_ calling 364 /// // this function. 365 /// assert!(AsciiDomain::try_from("λ.com".to_owned()).is_err()); 366 /// assert_eq!("xn--wxa.com", AsciiDomain::try_from("xn--wxa.com".to_owned())?.as_ref()); 367 /// # Ok::<_, AsciiDomainErr>(()) 368 /// ``` 369 #[expect( 370 unsafe_code, 371 reason = "need to transform uppercase ASCII into lowercase" 372 )] 373 #[expect( 374 clippy::arithmetic_side_effects, 375 reason = "comment justifies its correctness" 376 )] 377 #[inline] 378 fn try_from(mut value: String) -> Result<Self, Self::Error> { 379 value 380 .as_bytes() 381 .last() 382 .ok_or(AsciiDomainErr::Empty) 383 .and_then(|b| { 384 let len = value.len(); 385 if *b == b'.' { 386 if len == 1 { 387 Err(AsciiDomainErr::RootDomain) 388 } else if len > 254 { 389 Err(AsciiDomainErr::Len) 390 } else { 391 Ok(()) 392 } 393 } else if len > 253 { 394 Err(AsciiDomainErr::Len) 395 } else { 396 Ok(()) 397 } 398 }) 399 .and_then(|()| { 400 // SAFETY: 401 // The only possible mutation we perform is converting uppercase ASCII into lowercase which 402 // is entirely safe since ASCII is a subset of UTF-8, and ASCII characters are always encoded 403 // as a single UTF-8 code unit. 404 let utf8 = unsafe { value.as_bytes_mut() }; 405 utf8.iter_mut() 406 .try_fold(0u8, |label_len, byt| { 407 let b = *byt; 408 if b == b'.' { 409 if label_len == 0 { 410 Err(AsciiDomainErr::EmptyLabel) 411 } else { 412 Ok(0) 413 } 414 } else if label_len == 63 { 415 Err(AsciiDomainErr::LabelLen) 416 } else if b.is_ascii() { 417 *byt = b.to_ascii_lowercase(); 418 // This won't overflow since `label_len < 63`. 419 Ok(label_len + 1) 420 } else { 421 Err(AsciiDomainErr::NotAscii) 422 } 423 }) 424 .map(|_| Self(value)) 425 }) 426 } 427 } 428 /// The output of the [URL serializer](https://url.spec.whatwg.org/#concept-url-serializer). 429 /// 430 /// The returned URL must consist of a [scheme](https://url.spec.whatwg.org/#concept-url-scheme) and 431 /// optional [path](https://url.spec.whatwg.org/#url-path) but nothing else. 432 #[derive(Clone, Debug, Eq, PartialEq)] 433 pub struct Url(String); 434 impl AsRef<str> for Url { 435 #[inline] 436 fn as_ref(&self) -> &str { 437 self.0.as_str() 438 } 439 } 440 impl Borrow<str> for Url { 441 #[inline] 442 fn borrow(&self) -> &str { 443 self.0.as_str() 444 } 445 } 446 impl From<Url> for String { 447 #[inline] 448 fn from(value: Url) -> Self { 449 value.0 450 } 451 } 452 impl PartialEq<&Self> for Url { 453 #[inline] 454 fn eq(&self, other: &&Self) -> bool { 455 *self == **other 456 } 457 } 458 impl PartialEq<Url> for &Url { 459 #[inline] 460 fn eq(&self, other: &Url) -> bool { 461 **self == *other 462 } 463 } 464 impl FromStr for Url { 465 type Err = UrlErr; 466 #[inline] 467 fn from_str(s: &str) -> Result<Self, Self::Err> { 468 Uri::from_str(s).map_err(|_e| UrlErr).and_then(|url| { 469 if url.scheme().is_empty() 470 || url.has_host() 471 || url.query().is_some() 472 || url.fragment().is_some() 473 { 474 Err(UrlErr) 475 } else { 476 Ok(Self(url.into())) 477 } 478 }) 479 } 480 } 481 /// [RP ID](https://w3c.github.io/webauthn/#rp-id). 482 #[derive(Clone, Debug, Eq, PartialEq)] 483 pub enum RpId { 484 /// An ASCII domain. 485 /// 486 /// Note web platforms MUST use this variant; and if possible, non-web platforms should too. Also despite 487 /// the spec currently requiring RP IDs to be 488 /// [valid domain strings](https://url.spec.whatwg.org/#valid-domain-string), this is unnecessarily strict 489 /// and will likely be relaxed in a [future version](https://github.com/w3c/webauthn/issues/2206); thus 490 /// any ASCII domain is allowed. 491 Domain(AsciiDomain), 492 /// A URL with only scheme and path. 493 Url(Url), 494 } 495 impl RpId { 496 /// Validates `hash` is the same as the SHA-256 hash of `self`. 497 fn validate_rp_id_hash<E>(&self, hash: &[u8]) -> Result<(), CeremonyErr<E>> { 498 if hash == Sha256::digest(self.as_ref()).as_slice() { 499 Ok(()) 500 } else { 501 Err(CeremonyErr::RpIdHashMismatch) 502 } 503 } 504 } 505 impl AsRef<str> for RpId { 506 #[inline] 507 fn as_ref(&self) -> &str { 508 match *self { 509 Self::Domain(ref dom) => dom.as_ref(), 510 Self::Url(ref url) => url.as_ref(), 511 } 512 } 513 } 514 impl Borrow<str> for RpId { 515 #[inline] 516 fn borrow(&self) -> &str { 517 match *self { 518 Self::Domain(ref dom) => dom.borrow(), 519 Self::Url(ref url) => url.borrow(), 520 } 521 } 522 } 523 impl From<RpId> for String { 524 #[inline] 525 fn from(value: RpId) -> Self { 526 match value { 527 RpId::Domain(dom) => dom.into(), 528 RpId::Url(url) => url.into(), 529 } 530 } 531 } 532 impl PartialEq<&Self> for RpId { 533 #[inline] 534 fn eq(&self, other: &&Self) -> bool { 535 *self == **other 536 } 537 } 538 impl PartialEq<RpId> for &RpId { 539 #[inline] 540 fn eq(&self, other: &RpId) -> bool { 541 **self == *other 542 } 543 } 544 /// A URI scheme. This can be used to make 545 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient. 546 #[derive(Clone, Copy, Debug, Default)] 547 pub enum Scheme<'a> { 548 /// A scheme must not exist when validating the origin. 549 None, 550 /// Any scheme, or no scheme at all, is allowed to exist when validating the origin. 551 Any, 552 /// The HTTPS scheme must exist when validating the origin. 553 #[default] 554 Https, 555 /// The SSH scheme must exist when validating the origin. 556 Ssh, 557 /// The contained `str` scheme must exist when validating the origin. 558 Other(&'a str), 559 /// [`Self::None`] or [`Self::Https`]. 560 NoneHttps, 561 /// [`Self::None`] or [`Self::Ssh`]. 562 NoneSsh, 563 /// [`Self::None`] or [`Self::Other`]. 564 NoneOther(&'a str), 565 } 566 impl Scheme<'_> { 567 /// `self` is any `Scheme`; however `other` is assumed to only be a `Scheme` from a `DomainOrigin` returned 568 /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Scheme::None`, `Scheme::Https`, 569 /// `Scheme::Ssh`, or `Scheme::Other`; furthermore when `Scheme::Other`, it won't contain a `str` that is 570 /// empty or equal to "https" or "ssh". 571 #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")] 572 fn is_equal_to_origin_scheme(self, other: Self) -> bool { 573 match self { 574 Self::None => matches!(other, Self::None), 575 Self::Any => true, 576 Self::Https => matches!(other, Self::Https), 577 Self::Ssh => matches!(other, Self::Ssh), 578 Self::Other(scheme) => match other { 579 Self::None => false, 580 // We want to crash and burn since there is a bug in code. 581 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 582 unreachable!("there is a bug in DomainOrigin::try_from") 583 } 584 Self::Https => scheme == "https", 585 Self::Ssh => scheme == "ssh", 586 Self::Other(scheme_other) => scheme == scheme_other, 587 }, 588 Self::NoneHttps => match other { 589 Self::None | Self::Https => true, 590 Self::Ssh | Self::Other(_) => false, 591 // We want to crash and burn since there is a bug in code. 592 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 593 unreachable!("there is a bug in DomainOrigin::try_from") 594 } 595 }, 596 Self::NoneSsh => match other { 597 Self::None | Self::Ssh => true, 598 // We want to crash and burn since there is a bug in code. 599 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 600 unreachable!("there is a bug in DomainOrigin::try_from") 601 } 602 Self::Https | Self::Other(_) => false, 603 }, 604 Self::NoneOther(scheme) => match other { 605 Self::None => true, 606 // We want to crash and burn since there is a bug in code. 607 Self::Any | Self::NoneHttps | Self::NoneSsh | Self::NoneOther(_) => { 608 unreachable!("there is a bug in DomainOrigin::try_from") 609 } 610 Self::Https => scheme == "https", 611 Self::Ssh => scheme == "ssh", 612 Self::Other(scheme_other) => scheme == scheme_other, 613 }, 614 } 615 } 616 } 617 impl<'a: 'b, 'b> TryFrom<&'a str> for Scheme<'b> { 618 type Error = SchemeParseErr; 619 /// `"https"` and `"ssh"` get mapped to [`Self::Https`] and [`Self::Ssh`] respectively. All other 620 /// values get mapped to [`Self::Other`]. 621 /// 622 /// # Errors 623 /// 624 /// Errors iff `s` is empty. 625 /// 626 /// # Examples 627 /// 628 /// ``` 629 /// # use webauthn_rp::request::Scheme; 630 /// assert!(matches!(Scheme::try_from("https")?, Scheme::Https)); 631 /// assert!(matches!(Scheme::try_from("https ")?, Scheme::Other(scheme) if scheme == "https ")); 632 /// assert!(matches!(Scheme::try_from("ssh")?, Scheme::Ssh)); 633 /// assert!(matches!(Scheme::try_from("Ssh")?, Scheme::Other(scheme) if scheme == "Ssh")); 634 /// // Even though one can construct an empty `Scheme` via `Scheme::Other` or `Scheme::NoneOther`, 635 /// // one cannot parse one. 636 /// assert!(Scheme::try_from("").is_err()); 637 /// # Ok::<_, webauthn_rp::AggErr>(()) 638 /// ``` 639 #[inline] 640 fn try_from(value: &'a str) -> Result<Self, Self::Error> { 641 match value { 642 "" => Err(SchemeParseErr), 643 "https" => Ok(Self::Https), 644 "ssh" => Ok(Self::Ssh), 645 _ => Ok(Self::Other(value)), 646 } 647 } 648 } 649 /// A TCP/UDP port. This can be used to make 650 /// [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) more convenient. 651 #[derive(Clone, Copy, Debug, Default)] 652 pub enum Port { 653 /// A port must not exist when validating the origin. 654 #[default] 655 None, 656 /// Any port, or no port at all, is allowed to exist when validating the origin. 657 Any, 658 /// The contained `u16` port must exist when validating the origin. 659 Val(u16), 660 /// [`Self::None`] or [`Self::Val`]. 661 NoneVal(u16), 662 } 663 impl Port { 664 /// `self` is any `Port`; however `other` is assumed to only be a `Port` from a `DomainOrigin` returned 665 /// from `DomainOrigin::try_from`. The latter implies that `other` is only `Port::None` or `Port::Val`. 666 #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")] 667 fn is_equal_to_origin_port(self, other: Self) -> bool { 668 match self { 669 Self::None => matches!(other, Self::None), 670 Self::Any => true, 671 Self::Val(port) => match other { 672 Self::None => false, 673 // There is a bug in code so we want to crash and burn. 674 Self::Any | Self::NoneVal(_) => { 675 unreachable!("there is a bug in DomainOrigin::try_from") 676 } 677 Self::Val(port_other) => port == port_other, 678 }, 679 Self::NoneVal(port) => match other { 680 Self::None => true, 681 // There is a bug in code so we want to crash and burn. 682 Self::Any | Self::NoneVal(_) => { 683 unreachable!("there is a bug in DomainOrigin::try_from") 684 } 685 Self::Val(port_other) => port == port_other, 686 }, 687 } 688 } 689 } 690 impl FromStr for Port { 691 type Err = PortParseErr; 692 /// Parses `s` as a 16-bit unsigned integer without leading 0s returning [`Self::Val`] with the contained 693 /// `u16`. 694 /// 695 /// # Errors 696 /// 697 /// Errors iff `s` is not a valid 16-bit unsigned integer in decimal notation without leading 0s. 698 /// 699 /// # Examples 700 /// 701 /// ``` 702 /// # use webauthn_rp::request::{error::PortParseErr, Port}; 703 /// assert!(matches!("443".parse()?, Port::Val(443))); 704 /// // TCP/UDP ports have to be in canonical form: 705 /// assert!("022" 706 /// .parse::<Port>() 707 /// .map_or_else(|err| matches!(err, PortParseErr::NotCanonical), |_| false)); 708 /// # Ok::<_, webauthn_rp::AggErr>(()) 709 /// ``` 710 #[inline] 711 fn from_str(s: &str) -> Result<Self, Self::Err> { 712 s.parse().map_err(PortParseErr::ParseInt).and_then(|port| { 713 if s.len() 714 == match port { 715 ..=9 => 1, 716 10..=99 => 2, 717 100..=999 => 3, 718 1_000..=9_999 => 4, 719 10_000.. => 5, 720 } 721 { 722 Ok(Self::Val(port)) 723 } else { 724 Err(PortParseErr::NotCanonical) 725 } 726 }) 727 } 728 } 729 /// A [`tuple origin`](https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-tuple). 730 /// 731 /// This can be used to make [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) 732 /// more convenient. 733 #[derive(Clone, Copy, Debug)] 734 pub struct DomainOrigin<'a, 'b> { 735 /// The scheme. 736 pub scheme: Scheme<'a>, 737 /// The host. 738 pub host: &'b str, 739 /// The TCP/UDP port. 740 pub port: Port, 741 } 742 impl<'b> DomainOrigin<'_, 'b> { 743 /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and 744 /// [`Self::port`] as [`Port::None`]. 745 /// 746 /// # Examples 747 /// 748 /// ``` 749 /// # extern crate alloc; 750 /// # use alloc::borrow::Cow; 751 /// # use webauthn_rp::{request::DomainOrigin, response::Origin}; 752 /// assert_eq!( 753 /// DomainOrigin::new("www.example.com"), 754 /// Origin(Cow::Borrowed("https://www.example.com")) 755 /// ); 756 /// // `DomainOrigin::new` does not allow _any_ port to exist. 757 /// assert_ne!( 758 /// DomainOrigin::new("www.example.com"), 759 /// Origin(Cow::Borrowed("https://www.example.com:443")) 760 /// ); 761 /// ``` 762 #[must_use] 763 #[inline] 764 pub const fn new<'c: 'b>(host: &'c str) -> Self { 765 Self { 766 scheme: Scheme::Https, 767 host, 768 port: Port::None, 769 } 770 } 771 /// Returns a `DomainOrigin` with [`Self::scheme`] as [`Scheme::Https`], [`Self::host`] as `host`, and 772 /// [`Self::port`] as [`Port::Any`]. 773 /// 774 /// # Examples 775 /// 776 /// ``` 777 /// # extern crate alloc; 778 /// # use alloc::borrow::Cow; 779 /// # use webauthn_rp::{request::DomainOrigin, response::Origin}; 780 /// // Any port is allowed to exist. 781 /// assert_eq!( 782 /// DomainOrigin::new_ignore_port("www.example.com"), 783 /// Origin(Cow::Borrowed("https://www.example.com:1234")) 784 /// ); 785 /// // A port doesn't have to exist at all either. 786 /// assert_eq!( 787 /// DomainOrigin::new_ignore_port("www.example.com"), 788 /// Origin(Cow::Borrowed("https://www.example.com")) 789 /// ); 790 /// ``` 791 #[must_use] 792 #[inline] 793 pub const fn new_ignore_port<'c: 'b>(host: &'c str) -> Self { 794 Self { 795 scheme: Scheme::Https, 796 host, 797 port: Port::Any, 798 } 799 } 800 } 801 impl PartialEq<Origin<'_>> for DomainOrigin<'_, '_> { 802 /// Returns `true` iff [`DomainOrigin::scheme`], [`DomainOrigin::host`], and [`DomainOrigin::port`] are the 803 /// same after calling [`DomainOrigin::try_from`] on `other.0.as_str()`. 804 /// 805 /// Note that [`Scheme`] and [`Port`] need not be the same variant. For example [`Scheme::Https`] and 806 /// [`Scheme::Other`] containing `"https"` will be treated the same. 807 #[inline] 808 fn eq(&self, other: &Origin<'_>) -> bool { 809 DomainOrigin::try_from(other.0.as_ref()).is_ok_and(|dom| { 810 self.scheme.is_equal_to_origin_scheme(dom.scheme) 811 && self.host == dom.host 812 && self.port.is_equal_to_origin_port(dom.port) 813 }) 814 } 815 } 816 impl PartialEq<Origin<'_>> for &DomainOrigin<'_, '_> { 817 #[inline] 818 fn eq(&self, other: &Origin<'_>) -> bool { 819 **self == *other 820 } 821 } 822 impl PartialEq<&Origin<'_>> for DomainOrigin<'_, '_> { 823 #[inline] 824 fn eq(&self, other: &&Origin<'_>) -> bool { 825 *self == **other 826 } 827 } 828 impl PartialEq<DomainOrigin<'_, '_>> for Origin<'_> { 829 #[inline] 830 fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool { 831 *other == *self 832 } 833 } 834 impl PartialEq<DomainOrigin<'_, '_>> for &Origin<'_> { 835 #[inline] 836 fn eq(&self, other: &DomainOrigin<'_, '_>) -> bool { 837 *other == **self 838 } 839 } 840 impl PartialEq<&DomainOrigin<'_, '_>> for Origin<'_> { 841 #[inline] 842 fn eq(&self, other: &&DomainOrigin<'_, '_>) -> bool { 843 **other == *self 844 } 845 } 846 impl<'a: 'b + 'c, 'b, 'c> TryFrom<&'a str> for DomainOrigin<'b, 'c> { 847 type Error = DomainOriginParseErr; 848 /// `value` is parsed according to the following extended regex: 849 /// 850 /// `^([^:]*:\/\/)?[^:]*(:.*)?$` 851 /// 852 /// where the `[^:]*` of the first capturing group is parsed according to [`Scheme::try_from`], and 853 /// the `.*` of the second capturing group is parsed according to [`Port::from_str`]. 854 /// 855 /// # Errors 856 /// 857 /// Errors iff `Scheme::try_from` or `Port::from_str` fail when applicable. 858 /// 859 /// # Examples 860 /// 861 /// ``` 862 /// # use webauthn_rp::request::{DomainOrigin, Port, Scheme}; 863 /// assert!( 864 /// DomainOrigin::try_from("https://www.example.com:443").map_or(false, |dom| matches!( 865 /// dom.scheme, 866 /// Scheme::Https 867 /// ) && dom.host 868 /// == "www.example.com" 869 /// && matches!(dom.port, Port::Val(port) if port == 443)) 870 /// ); 871 /// // Parsing is done in a case sensitive way. 872 /// assert!(DomainOrigin::try_from("Https://www.EXample.com").map_or( 873 /// false, 874 /// |dom| matches!(dom.scheme, Scheme::Other(scheme) if scheme == "Https") 875 /// && dom.host == "www.EXample.com" 876 /// && matches!(dom.port, Port::None) 877 /// )); 878 /// ``` 879 #[inline] 880 fn try_from(value: &'a str) -> Result<Self, Self::Error> { 881 // Any string that contains `':'` is not a [valid domain](https://url.spec.whatwg.org/#valid-domain), and 882 // and `"//"` never exists in a `Port`; thus if `"://"` exists, it's either invalid or delimits the scheme 883 // from the rest of the origin. 884 match value.split_once("://") { 885 None => Ok((Scheme::None, value)), 886 Some((poss_scheme, rem)) => Scheme::try_from(poss_scheme) 887 .map_err(DomainOriginParseErr::Scheme) 888 .map(|scheme| (scheme, rem)), 889 } 890 .and_then(|(scheme, rem)| { 891 // `':'` never exists in a valid domain; thus if it exists, it's either invalid or 892 // separates the domain from the port. 893 rem.split_once(':') 894 .map_or_else( 895 || Ok((rem, Port::None)), 896 |(rem2, poss_port)| { 897 Port::from_str(poss_port) 898 .map_err(DomainOriginParseErr::Port) 899 .map(|port| (rem2, port)) 900 }, 901 ) 902 .map(|(host, port)| Self { scheme, host, port }) 903 }) 904 } 905 } 906 /// [`PublicKeyCredentialDescriptor`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptor) 907 /// associated with a registered credential. 908 #[derive(Debug)] 909 pub struct PublicKeyCredentialDescriptor<T> { 910 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-id). 911 pub id: CredentialId<T>, 912 /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports). 913 pub transports: AuthTransports, 914 } 915 /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement). 916 #[derive(Clone, Copy, Debug)] 917 pub enum UserVerificationRequirement { 918 /// [`required`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-required). 919 Required, 920 /// [`discouraged`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-discouraged). 921 /// 922 /// Note some authenticators always require user verification when registering a credential (e.g., 923 /// [CTAP 2.0](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html) 924 /// authenticators that have had a PIN enabled). 925 Discouraged, 926 /// [`preferred`](https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-preferred). 927 Preferred, 928 } 929 #[cfg(test)] 930 impl PartialEq for UserVerificationRequirement { 931 fn eq(&self, other: &Self) -> bool { 932 match *self { 933 Self::Required => matches!(other, Self::Required), 934 Self::Discouraged => matches!(other, Self::Discouraged), 935 Self::Preferred => matches!(other, Self::Preferred), 936 } 937 } 938 } 939 /// [`PublicKeyCredentialHints`](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint). 940 #[derive(Clone, Copy, Debug, Default)] 941 pub enum Hint { 942 /// No hints. 943 #[default] 944 None, 945 /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-security-key). 946 SecurityKey, 947 /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-client-device). 948 ClientDevice, 949 /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-hybrid). 950 Hybrid, 951 /// [`Self::SecurityKey`] and [`Self::ClientDevice`]. 952 SecurityKeyClientDevice, 953 /// [`Self::ClientDevice`] and [`Self::SecurityKey`]. 954 ClientDeviceSecurityKey, 955 /// [`Self::SecurityKey`] and [`Self::Hybrid`]. 956 SecurityKeyHybrid, 957 /// [`Self::Hybrid`] and [`Self::SecurityKey`]. 958 HybridSecurityKey, 959 /// [`Self::ClientDevice`] and [`Self::Hybrid`]. 960 ClientDeviceHybrid, 961 /// [`Self::Hybrid`] and [`Self::ClientDevice`]. 962 HybridClientDevice, 963 /// [`Self::SecurityKeyClientDevice`] and [`Self::Hybrid`]. 964 SecurityKeyClientDeviceHybrid, 965 /// [`Self::SecurityKeyHybrid`] and [`Self::ClientDevice`]. 966 SecurityKeyHybridClientDevice, 967 /// [`Self::ClientDeviceSecurityKey`] and [`Self::Hybrid`]. 968 ClientDeviceSecurityKeyHybrid, 969 /// [`Self::ClientDeviceHybrid`] and [`Self::SecurityKey`]. 970 ClientDeviceHybridSecurityKey, 971 /// [`Self::HybridSecurityKey`] and [`Self::ClientDevice`]. 972 HybridSecurityKeyClientDevice, 973 /// [`Self::HybridClientDevice`] and [`Self::SecurityKey`]. 974 HybridClientDeviceSecurityKey, 975 } 976 #[cfg(test)] 977 impl PartialEq for Hint { 978 fn eq(&self, other: &Self) -> bool { 979 match *self { 980 Self::None => matches!(other, Self::None), 981 Self::SecurityKey => matches!(other, Self::SecurityKey), 982 Self::ClientDevice => matches!(other, Self::ClientDevice), 983 Self::Hybrid => matches!(other, Self::Hybrid), 984 Self::SecurityKeyClientDevice => matches!(other, Self::SecurityKeyClientDevice), 985 Self::ClientDeviceSecurityKey => matches!(other, Self::ClientDeviceSecurityKey), 986 Self::SecurityKeyHybrid => matches!(other, Self::SecurityKeyHybrid), 987 Self::HybridSecurityKey => matches!(other, Self::HybridSecurityKey), 988 Self::ClientDeviceHybrid => matches!(other, Self::ClientDeviceHybrid), 989 Self::HybridClientDevice => matches!(other, Self::HybridClientDevice), 990 Self::SecurityKeyClientDeviceHybrid => { 991 matches!(other, Self::SecurityKeyClientDeviceHybrid) 992 } 993 Self::SecurityKeyHybridClientDevice => { 994 matches!(other, Self::SecurityKeyHybridClientDevice) 995 } 996 Self::ClientDeviceSecurityKeyHybrid => { 997 matches!(other, Self::ClientDeviceSecurityKeyHybrid) 998 } 999 Self::ClientDeviceHybridSecurityKey => { 1000 matches!(other, Self::ClientDeviceHybridSecurityKey) 1001 } 1002 Self::HybridSecurityKeyClientDevice => { 1003 matches!(other, Self::HybridSecurityKeyClientDevice) 1004 } 1005 Self::HybridClientDeviceSecurityKey => { 1006 matches!(other, Self::HybridClientDeviceSecurityKey) 1007 } 1008 } 1009 } 1010 } 1011 /// Controls if the response to a requested extension is required to be sent back. 1012 /// 1013 /// Note when requiring an extension, the extension must not only be sent back but also 1014 /// contain at least one expected field 1015 /// (e.g., [`ClientExtensionsOutputs::cred_props`] must be 1016 /// `Some(CredentialPropertiesOutput { rk: Some(_) })`. 1017 /// 1018 /// If one wants to additionally control the values of an extension, use [`ExtensionInfo`]. 1019 #[derive(Clone, Copy, Debug)] 1020 pub enum ExtensionReq { 1021 /// The response to a requested extension is required to be sent back. 1022 Require, 1023 /// The response to a requested extension is allowed, but not required, to be sent back. 1024 Allow, 1025 } 1026 #[cfg(test)] 1027 impl PartialEq for ExtensionReq { 1028 fn eq(&self, other: &Self) -> bool { 1029 match *self { 1030 Self::Require => matches!(other, Self::Require), 1031 Self::Allow => matches!(other, Self::Allow), 1032 } 1033 } 1034 } 1035 /// Dictates how an extension should be processed. 1036 /// 1037 /// If one wants to only control if the extension should be returned, use [`ExtensionReq`]. 1038 #[derive(Clone, Copy, Debug)] 1039 pub enum ExtensionInfo { 1040 /// Require the associated extension and enforce its value. 1041 RequireEnforceValue, 1042 /// Require the associated extension but don't enforce its value. 1043 RequireDontEnforceValue, 1044 /// Allow the associated extension to exist and enforce its value when it does exist. 1045 AllowEnforceValue, 1046 /// Allow the associated extension to exist but don't enforce its value. 1047 AllowDontEnforceValue, 1048 } 1049 #[cfg(test)] 1050 impl PartialEq for ExtensionInfo { 1051 fn eq(&self, other: &Self) -> bool { 1052 match *self { 1053 Self::RequireEnforceValue => matches!(other, Self::RequireEnforceValue), 1054 Self::RequireDontEnforceValue => matches!(other, Self::RequireDontEnforceValue), 1055 Self::AllowEnforceValue => matches!(other, Self::AllowEnforceValue), 1056 Self::AllowDontEnforceValue => matches!(other, Self::AllowDontEnforceValue), 1057 } 1058 } 1059 } 1060 impl Display for ExtensionInfo { 1061 #[inline] 1062 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1063 f.write_str(match *self { 1064 Self::RequireEnforceValue => "require the corresponding extension response and enforce its value", 1065 Self::RequireDontEnforceValue => "require the corresponding extension response but don't enforce its value", 1066 Self::AllowEnforceValue => "don't require the corresponding extension response; but if sent, enforce its value", 1067 Self::AllowDontEnforceValue => "don't require the corresponding extension response; and if sent, don't enforce its value", 1068 }) 1069 } 1070 } 1071 /// [`CredentialMediationRequirement`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement). 1072 #[derive(Clone, Copy, Debug)] 1073 pub enum CredentialMediationRequirement { 1074 /// [`silent`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-silent). 1075 Silent, 1076 /// [`optional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-optional) 1077 Optional, 1078 /// [`conditional`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-conditional) 1079 /// 1080 /// Note that when registering a new credential with [`PublicKeyCredentialCreationOptions::mediation`] set to 1081 /// `Self::Conditional`, [`UserVerificationRequirement::Discouraged`] MUST be used unless user verification 1082 /// can be explicitly performed during the ceremony. 1083 Conditional, 1084 /// [`required`](https://www.w3.org/TR/credential-management-1/#dom-credentialmediationrequirement-required) 1085 Required, 1086 } 1087 #[cfg(test)] 1088 impl PartialEq for CredentialMediationRequirement { 1089 fn eq(&self, other: &Self) -> bool { 1090 match *self { 1091 Self::Silent => matches!(other, Self::Silent), 1092 Self::Optional => matches!(other, Self::Optional), 1093 Self::Conditional => matches!(other, Self::Conditional), 1094 Self::Required => matches!(other, Self::Required), 1095 } 1096 } 1097 } 1098 /// Backup requirements for the credential. 1099 #[derive(Clone, Copy, Debug, Default)] 1100 pub enum BackupReq { 1101 #[default] 1102 /// No requirements (i.e., any [`Backup`] is allowed). 1103 None, 1104 /// Credential must not be eligible for backup. 1105 NotEligible, 1106 /// Credential must be eligible for backup. 1107 /// 1108 /// Note the existence of a backup is ignored. If a backup must exist, then use [`Self::Exists`]. 1109 Eligible, 1110 /// Credential must be backed up. 1111 Exists, 1112 } 1113 impl From<Backup> for BackupReq { 1114 /// One may want to create `BackupReq` based on the previous `Backup` such that the subsequent `Backup` is 1115 /// essentially unchanged. 1116 /// 1117 /// Specifically this transforms [`Backup::NotEligible`] to [`Self::NotEligible`] and [`Backup::Eligible`] and 1118 /// [`Backup::Exists`] to [`Self::Eligible`]. Note this means that a credential that 1119 /// is eligible to be backed up but currently does not have a backup will be allowed to change such that it 1120 /// is backed up. Similarly, a credential that is backed up is allowed to change such that a backup no longer 1121 /// exists. 1122 #[inline] 1123 fn from(value: Backup) -> Self { 1124 if matches!(value, Backup::NotEligible) { 1125 Self::NotEligible 1126 } else { 1127 Self::Eligible 1128 } 1129 } 1130 } 1131 /// A container of "credentials". 1132 /// 1133 /// This is mainly a way to unify [`Vec`] of [`PublicKeyCredentialDescriptor`] 1134 /// and [`AllowedCredentials`]. This can be useful in situations when one only 1135 /// deals with [`AllowedCredential`]s with empty [`CredentialSpecificExtension`]s 1136 /// essentially making them the same as [`PublicKeyCredentialDescriptor`]s. 1137 /// 1138 /// # Examples 1139 /// 1140 /// ``` 1141 /// # use webauthn_rp::{ 1142 /// # request::{ 1143 /// # auth::AllowedCredentials, register::UserHandle, Credentials, PublicKeyCredentialDescriptor, 1144 /// # }, 1145 /// # response::{AuthTransports, CredentialId}, 1146 /// # }; 1147 /// /// Fetches all credentials under `user_handle` to be allowed during authentication for non-discoverable 1148 /// /// requests. 1149 /// # #[cfg(feature = "custom")] 1150 /// fn get_allowed_credentials(user_handle: UserHandle<&[u8]>) -> AllowedCredentials { 1151 /// get_credentials(user_handle) 1152 /// } 1153 /// /// Fetches all credentials under `user_handle` to be excluded during registration. 1154 /// # #[cfg(feature = "custom")] 1155 /// fn get_excluded_credentials( 1156 /// user_handle: UserHandle<&[u8]>, 1157 /// ) -> Vec<PublicKeyCredentialDescriptor<Vec<u8>>> { 1158 /// get_credentials(user_handle) 1159 /// } 1160 /// /// Used to fetch the excluded `PublicKeyCredentialDescriptor`s associated with `user_handle` during 1161 /// /// registration as well as the `AllowedCredentials` containing `AllowedCredential`s with no credential-specific 1162 /// /// extensions which is used for non-discoverable requests. 1163 /// # #[cfg(feature = "custom")] 1164 /// fn get_credentials<T>(user_handle: UserHandle<&[u8]>) -> T 1165 /// where 1166 /// T: Credentials, 1167 /// PublicKeyCredentialDescriptor<Vec<u8>>: Into<T::Credential>, 1168 /// { 1169 /// let iter = get_cred_parts(user_handle); 1170 /// let len = iter.size_hint().0; 1171 /// iter.fold(T::with_capacity(len), |mut creds, parts| { 1172 /// creds.push( 1173 /// PublicKeyCredentialDescriptor { 1174 /// id: parts.0, 1175 /// transports: parts.1, 1176 /// } 1177 /// .into(), 1178 /// ); 1179 /// creds 1180 /// }) 1181 /// } 1182 /// /// Fetches all `CredentialId`s and associated `AuthTransports` under `user_handle` 1183 /// /// from the database. 1184 /// # #[cfg(feature = "custom")] 1185 /// fn get_cred_parts( 1186 /// user_handle: UserHandle<&[u8]>, 1187 /// ) -> impl Iterator<Item = (CredentialId<Vec<u8>>, AuthTransports)> { 1188 /// // ⋮ 1189 /// # [( 1190 /// # CredentialId::try_from(vec![0; 16]).unwrap(), 1191 /// # AuthTransports::NONE, 1192 /// # )] 1193 /// # .into_iter() 1194 /// } 1195 /// ``` 1196 pub trait Credentials: Sized { 1197 /// The "credential"s that make up `Self`. 1198 type Credential; 1199 /// Returns `Self`. 1200 #[inline] 1201 #[must_use] 1202 fn new() -> Self { 1203 Self::with_capacity(0) 1204 } 1205 /// Returns `Self` with at least `capacity` allocated. 1206 fn with_capacity(capacity: usize) -> Self; 1207 /// Adds `cred` to `self`. 1208 /// 1209 /// Returns `true` iff `cred` was added. 1210 fn push(&mut self, cred: Self::Credential) -> bool; 1211 /// Returns the number of [`Self::Credential`]s in `Self`. 1212 fn len(&self) -> usize; 1213 /// Returns `true` iff [`Self::len`] is `0`. 1214 #[inline] 1215 fn is_empty(&self) -> bool { 1216 self.len() == 0 1217 } 1218 } 1219 impl<T> Credentials for Vec<T> { 1220 type Credential = T; 1221 #[inline] 1222 fn with_capacity(capacity: usize) -> Self { 1223 Self::with_capacity(capacity) 1224 } 1225 #[inline] 1226 fn push(&mut self, cred: Self::Credential) -> bool { 1227 self.push(cred); 1228 true 1229 } 1230 #[inline] 1231 fn len(&self) -> usize { 1232 self.len() 1233 } 1234 } 1235 /// Additional options that control how [`Ceremony::partial_validate`] works. 1236 struct CeremonyOptions<'origins, 'top_origins, O, T> { 1237 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1238 /// 1239 /// When this is empty, the origin that will be used will be based on 1240 /// the [`RpId`] passed to [`RegistrationServerState::verify`]. If [`RpId::Domain`], then the [`DomainOrigin`] returned from 1241 /// passing [`AsciiDomain::as_ref`] to [`DomainOrigin::new`] will be used; otherwise the [`Url`] in 1242 /// [`RpId::Url`] will be used. 1243 allowed_origins: &'origins [O], 1244 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 1245 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1246 /// 1247 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 1248 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 1249 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 1250 allowed_top_origins: Option<&'top_origins [T]>, 1251 /// The required [`Backup`] state of the credential. 1252 backup_requirement: BackupReq, 1253 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 1254 #[cfg(feature = "serde_relaxed")] 1255 client_data_json_relaxed: bool, 1256 } 1257 impl<'o, 't, O, T> From<&RegistrationVerificationOptions<'o, 't, O, T>> 1258 for CeremonyOptions<'o, 't, O, T> 1259 { 1260 fn from(value: &RegistrationVerificationOptions<'o, 't, O, T>) -> Self { 1261 Self { 1262 allowed_origins: value.allowed_origins, 1263 allowed_top_origins: value.allowed_top_origins, 1264 backup_requirement: value.backup_requirement, 1265 #[cfg(feature = "serde_relaxed")] 1266 client_data_json_relaxed: value.client_data_json_relaxed, 1267 } 1268 } 1269 } 1270 /// Functionality common to both registration and authentication ceremonies. 1271 /// 1272 /// Designed to be implemented on the _request_ side. 1273 trait Ceremony<U> { 1274 /// The type of response that is associated with the ceremony. 1275 type R: Response; 1276 /// Challenge. 1277 fn rand_challenge(&self) -> SentChallenge; 1278 /// `Instant` the ceremony was expires. 1279 #[cfg(not(feature = "serializable_server_state"))] 1280 fn expiry(&self) -> Instant; 1281 /// `Instant` the ceremony was expires. 1282 #[cfg(feature = "serializable_server_state")] 1283 fn expiry(&self) -> SystemTime; 1284 /// User verification requirement. 1285 fn user_verification(&self) -> UserVerificationRequirement; 1286 /// Performs validation of ceremony criteria common to both ceremony types. 1287 #[expect( 1288 clippy::type_complexity, 1289 reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable" 1290 )] 1291 #[expect(clippy::too_many_lines, reason = "102 lines is fine")] 1292 fn partial_validate<'a, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>>( 1293 &self, 1294 rp_id: &RpId, 1295 resp: &'a Self::R, 1296 key: <<Self::R as Response>::Auth as AuthResponse>::CredKey<'_>, 1297 options: &CeremonyOptions<'_, '_, O, T>, 1298 ) -> Result< 1299 <<Self::R as Response>::Auth as AuthResponse>::Auth<'a>, 1300 CeremonyErr< 1301 <<<Self::R as Response>::Auth as AuthResponse>::Auth<'a> as AuthDataContainer<'a>>::Err, 1302 >, 1303 > { 1304 // [Registration ceremony](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential) 1305 // is handled by: 1306 // 1307 // 1. Calling code. 1308 // 2. Client code and the construction of `resp` (hopefully via [`Registration::deserialize`]). 1309 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAttestation::deserialize`]). 1310 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1311 // 5. Below via [`CollectedClientData::from_client_data_json_relaxed`]. 1312 // 6. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1313 // 7. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1314 // 8. Below. 1315 // 9. Below. 1316 // 10. Below. 1317 // 11. Below. 1318 // 12. Below via [`AuthenticatorAttestation::new`]. 1319 // 13. Below via [`AttestationObject::parse_data`]. 1320 // 14. Below. 1321 // 15. [`RegistrationServerState::verify`]. 1322 // 16. Below. 1323 // 17. Below via [`AuthenticatorData::from_cbor`]. 1324 // 18. Below. 1325 // 19. Below. 1326 // 20. [`RegistrationServerState::verify`]. 1327 // 21. Below via [`AttestationObject::parse_data`]. 1328 // 22. Below via [`AttestationObject::parse_data`]. 1329 // 23. N/A since only none and self attestations are supported. 1330 // 24. Always satisfied since only none and self attestations are supported (Item 3 is N/A). 1331 // 25. Below via [`AttestedCredentialData::from_cbor`]. 1332 // 26. Calling code. 1333 // 27. [`RegistrationServerState::verify`]. 1334 // 28. N/A since only none and self attestations are supported. 1335 // 29. [`RegistrationServerState::verify`]. 1336 // 1337 // 1338 // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) 1339 // is handled by: 1340 // 1341 // 1. Calling code. 1342 // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]). 1343 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]). 1344 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1345 // 5. [`AuthenticationServerState::verify`]. 1346 // 6. [`AuthenticationServerState::verify`]. 1347 // 7. Informative only in that it defines variables. 1348 // 8. Below via [`CollectedClientData::from_client_data_json_relaxed`]. 1349 // 9. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1350 // 10. Below via [`CollectedClientData::from_client_data_json_relaxed`] or [`CollectedClientData::from_client_data_json_relaxed`]. 1351 // 11. Below. 1352 // 12. Below. 1353 // 13. Below. 1354 // 14. Below. 1355 // 15. Below. 1356 // 16. Below via [`AuthenticatorData::from_cbor`]. 1357 // 17. Below. 1358 // 18. Below via [`AuthenticatorData::from_cbor`]. 1359 // 19. Below. 1360 // 20. Below via [`AuthenticatorAssertion::new`]. 1361 // 21. Below. 1362 // 22. [`AuthenticationServerState::verify`]. 1363 // 23. [`AuthenticationServerState::verify`]. 1364 // 24. [`AuthenticationServerState::verify`]. 1365 // 25. [`AuthenticationServerState::verify`]. 1366 1367 // Enforce timeout. 1368 #[cfg(not(feature = "serializable_server_state"))] 1369 let active = self.expiry() >= Instant::now(); 1370 #[cfg(feature = "serializable_server_state")] 1371 let active = self.expiry() >= SystemTime::now(); 1372 if active { 1373 #[cfg(feature = "serde_relaxed")] 1374 let relaxed = options.client_data_json_relaxed; 1375 #[cfg(not(feature = "serde_relaxed"))] 1376 let relaxed = false; 1377 resp.auth() 1378 // Steps 5–7, 12–13, 17, 21–22, and 25 of the registration ceremony. 1379 // Steps 8–10, 16, 18, and 20–21 of the authentication ceremony. 1380 .parse_data_and_verify_sig(key, relaxed) 1381 .map_err(CeremonyErr::AuthResp) 1382 .and_then(|(client_data_json, auth_response)| { 1383 if options.allowed_origins.is_empty() { 1384 if match *rp_id { 1385 RpId::Domain(ref dom) => { 1386 // Steps 9 and 12 of the registration and authentication ceremonies 1387 // respectively. 1388 DomainOrigin::new(dom.as_ref()) == client_data_json.origin 1389 } 1390 // Steps 9 and 12 of the registration and authentication ceremonies 1391 // respectively. 1392 RpId::Url(ref url) => url == client_data_json.origin, 1393 } { 1394 Ok(()) 1395 } else { 1396 Err(CeremonyErr::OriginMismatch) 1397 } 1398 } else { 1399 options 1400 .allowed_origins 1401 .iter() 1402 // Steps 9 and 12 of the registration and authentication ceremonies 1403 // respectively. 1404 .find(|o| **o == client_data_json.origin) 1405 .ok_or(CeremonyErr::OriginMismatch) 1406 .map(|_| ()) 1407 } 1408 .and_then(|()| { 1409 // Steps 10–11 of the registration ceremony. 1410 // Steps 13–14 of the authentication ceremony. 1411 match options.allowed_top_origins { 1412 None => { 1413 if client_data_json.cross_origin { 1414 Err(CeremonyErr::CrossOrigin) 1415 } else if client_data_json.top_origin.is_some() { 1416 Err(CeremonyErr::TopOriginMismatch) 1417 } else { 1418 Ok(()) 1419 } 1420 } 1421 Some(top_origins) => client_data_json.top_origin.map_or(Ok(()), |t| { 1422 top_origins 1423 .iter() 1424 .find(|top| **top == t) 1425 .ok_or(CeremonyErr::TopOriginMismatch) 1426 .map(|_| ()) 1427 }), 1428 } 1429 .and_then(|()| { 1430 // Steps 8 and 11 of the registration and authentication ceremonies 1431 // respectively. 1432 if self.rand_challenge() == client_data_json.challenge { 1433 let auth_data = auth_response.authenticator_data(); 1434 rp_id 1435 // Steps 14 and 15 of the registration and authentication ceremonies 1436 // respectively. 1437 .validate_rp_id_hash(auth_data.rp_hash()) 1438 .and_then(|()| { 1439 let flag = auth_data.flag(); 1440 // Steps 16 and 17 of the registration and authentication ceremonies 1441 // respectively. 1442 if flag.user_verified 1443 || !matches!( 1444 self.user_verification(), 1445 UserVerificationRequirement::Required 1446 ) 1447 { 1448 // Steps 18–19 of the registration ceremony. 1449 // Step 19 of the authentication ceremony. 1450 match options.backup_requirement { 1451 BackupReq::None => Ok(()), 1452 BackupReq::NotEligible => { 1453 if matches!(flag.backup, Backup::NotEligible) { 1454 Ok(()) 1455 } else { 1456 Err(CeremonyErr::BackupEligible) 1457 } 1458 } 1459 BackupReq::Eligible => { 1460 if matches!(flag.backup, Backup::NotEligible) { 1461 Err(CeremonyErr::BackupNotEligible) 1462 } else { 1463 Ok(()) 1464 } 1465 } 1466 BackupReq::Exists => { 1467 if matches!(flag.backup, Backup::Exists) { 1468 Ok(()) 1469 } else { 1470 Err(CeremonyErr::BackupDoesNotExist) 1471 } 1472 } 1473 } 1474 } else { 1475 Err(CeremonyErr::UserNotVerified) 1476 } 1477 }) 1478 .map(|()| auth_response) 1479 } else { 1480 Err(CeremonyErr::ChallengeMismatch) 1481 } 1482 }) 1483 }) 1484 }) 1485 } else { 1486 Err(CeremonyErr::Timeout) 1487 } 1488 } 1489 } 1490 /// `300_000` milliseconds is equal to five minutes. 1491 #[expect( 1492 clippy::unwrap_used, 1493 reason = "clearly correct, Option::unwrap is const, and better than unsafe" 1494 )] 1495 pub(super) const THREE_HUNDRED_THOUSAND: NonZeroU32 = NonZeroU32::new(300_000).unwrap(); 1496 1497 /// [`Hasher`] whose `write_*` methods simply store up to 64 bits of the passed argument _as is_ overwriting 1498 /// any previous state. 1499 /// 1500 /// This is designed to only be used indirectly via a hash map whose keys are randomly generated on the server 1501 /// based on at least 64 bits—the size of the integer returned from [`Self::finish`]—of entropy. 1502 /// This makes this `Hasher` usable (and ideal) in only the most niche circumstances. 1503 /// 1504 /// [`RegistrationServerState`] and [`AuthenticationServerState`] both implement [`Hash`] by simply writing the 1505 /// contained [`Challenge`]; thus when they are stored in a hashed collection (e.g., [`FixedCapHashSet`]), one can 1506 /// optimize without fear by using this `Hasher` since `Challenge`s are immutable and can only ever be created on 1507 /// the server via [`Challenge::new`] (and equivalently [`Challenge::default`]). `RegistrationServerState` and 1508 /// `AuthenticationServerState` are also immutable and only constructable via 1509 /// [`PublicKeyCredentialCreationOptions::start_ceremony`] and 1510 /// [`PublicKeyCredentialRequestOptions::start_ceremony`] respectively. Since `Challenge` is already based on 1511 /// a random `u128`, other `Hasher`s will be slower and likely produce lower-quality hashes (and never 1512 /// higher quality). 1513 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1514 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1515 #[derive(Debug)] 1516 pub struct IdentityHasher(u64); 1517 // Note it is _not_ required for `write_*` methods to do the same thing as other `write_*` methods 1518 // (e.g., `Self::write_u64` may not be the same thing as 8 calls to `Self::write_u8`). 1519 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1520 impl Hasher for IdentityHasher { 1521 /// Returns `0` if no `write_*` calls have been made; otherwise returns the result of the most recent 1522 /// `write_*` call. 1523 #[inline] 1524 fn finish(&self) -> u64 { 1525 self.0 1526 } 1527 /// Writes `i` to `self`. 1528 #[inline] 1529 fn write_u64(&mut self, i: u64) { 1530 self.0 = i; 1531 } 1532 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1533 #[expect( 1534 clippy::as_conversions, 1535 clippy::cast_sign_loss, 1536 reason = "we simply need to convert into a u64 in a deterministic way" 1537 )] 1538 #[inline] 1539 fn write_i8(&mut self, i: i8) { 1540 self.write_u64(i as u64); 1541 } 1542 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1543 #[expect( 1544 clippy::as_conversions, 1545 clippy::cast_sign_loss, 1546 reason = "we simply need to convert into a u64 in a deterministic way" 1547 )] 1548 #[inline] 1549 fn write_i16(&mut self, i: i16) { 1550 self.write_u64(i as u64); 1551 } 1552 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1553 #[expect( 1554 clippy::as_conversions, 1555 clippy::cast_sign_loss, 1556 reason = "we simply need to convert into a u64 in a deterministic way" 1557 )] 1558 #[inline] 1559 fn write_i32(&mut self, i: i32) { 1560 self.write_u64(i as u64); 1561 } 1562 /// Redirects to [`Self::write_u64`]. 1563 #[expect( 1564 clippy::as_conversions, 1565 clippy::cast_sign_loss, 1566 reason = "we simply need to convert into a u64 in a deterministic way" 1567 )] 1568 #[inline] 1569 fn write_i64(&mut self, i: i64) { 1570 self.write_u64(i as u64); 1571 } 1572 /// Truncates `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1573 #[expect( 1574 clippy::as_conversions, 1575 clippy::cast_possible_truncation, 1576 clippy::cast_sign_loss, 1577 reason = "we simply need to convert into a u64 in a deterministic way" 1578 )] 1579 #[inline] 1580 fn write_i128(&mut self, i: i128) { 1581 self.write_u64(i as u64); 1582 } 1583 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1584 #[inline] 1585 fn write_u8(&mut self, i: u8) { 1586 self.write_u64(u64::from(i)); 1587 } 1588 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1589 #[inline] 1590 fn write_u16(&mut self, i: u16) { 1591 self.write_u64(u64::from(i)); 1592 } 1593 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1594 #[inline] 1595 fn write_u32(&mut self, i: u32) { 1596 self.write_u64(u64::from(i)); 1597 } 1598 /// Truncates `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1599 #[expect( 1600 clippy::as_conversions, 1601 clippy::cast_possible_truncation, 1602 reason = "we simply need to convert into a u64 in a deterministic way" 1603 )] 1604 #[inline] 1605 fn write_u128(&mut self, i: u128) { 1606 self.write_u64(i as u64); 1607 } 1608 /// This does nothing iff `bytes.len() < 8`; otherwise the first 8 bytes are converted 1609 /// to a [`u64`] that is written via [`Self::write_u64`]; 1610 #[expect(clippy::host_endian_bytes, reason = "endianness does not matter")] 1611 #[inline] 1612 fn write(&mut self, bytes: &[u8]) { 1613 if let Some(data) = bytes.get(..8) { 1614 let mut val = [0; 8]; 1615 val.copy_from_slice(data); 1616 self.write_u64(u64::from_ne_bytes(val)); 1617 } 1618 } 1619 } 1620 /// [`BuildHasher`] of an [`IdentityHasher`]. 1621 /// 1622 /// This MUST only be used with hash maps with keys that are randomly generated on the server based on at least 64 1623 /// bits of entropy. 1624 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1625 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1626 #[derive(Clone, Copy, Debug)] 1627 pub struct BuildIdentityHasher; 1628 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1629 #[cfg(not(feature = "serializable_server_state"))] 1630 impl BuildHasher for BuildIdentityHasher { 1631 type Hasher = IdentityHasher; 1632 #[inline] 1633 fn build_hasher(&self) -> Self::Hasher { 1634 IdentityHasher(0) 1635 } 1636 } 1637 /// Prevent users from implementing [`ServerState`] and [`super::register::User`]. 1638 mod private { 1639 /// Marker trait used as a supertrait of `ServerState` and `User`. 1640 pub trait Sealed {} 1641 impl Sealed for super::AuthenticationServerState {} 1642 impl Sealed for super::RegistrationServerState {} 1643 impl<T> Sealed for super::register::UserHandle<T> {} 1644 impl<T> Sealed for Option<super::register::UserHandle<T>> {} 1645 } 1646 /// Subset of data shared by both [`RegistrationServerState`] and [`AuthenticationServerState`]. 1647 /// 1648 /// This `trait` is sealed and cannot be implemented for types outside of `webauthn_rp`. 1649 pub trait ServerState: private::Sealed { 1650 /// Returns the `Instant` the ceremony expires. 1651 /// 1652 /// Note when `serializable_server_state` is enabled, [`SystemTime`] is returned instead. 1653 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1654 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1655 fn expiration(&self) -> Instant; 1656 /// Returns the `SystemTime` the ceremony expires. 1657 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1658 fn expiration(&self) -> SystemTime; 1659 /// Returns the `SentChallenge` associated with the ceremony. 1660 fn sent_challenge(&self) -> SentChallenge; 1661 } 1662 /// Fixed-capacity hash set that only inserts items when there is available capacity. 1663 /// 1664 /// This should only be used if _both_ of the following conditions are met: 1665 /// 1666 /// * Application is already protected from memory exhaustion attacks (e.g., users must be connected 1667 /// via VPN). 1668 /// * There are legitimate reasons for an in-memory collection to grow unbounded. 1669 /// 1670 /// The first point is necessary; otherwise an attacker could trivially slow down the application 1671 /// by causing repeated inserts forcing repeated calls to functions like [`Self::retain`]. The second 1672 /// point is necessary; otherwise any in-memory collection would suffice. 1673 /// 1674 /// When `T` is a [`ServerState`], there are legitimate reasons why a ceremony will never finish (e.g., 1675 /// an outage could kill a user's connection after starting a ceremony). The longer the application 1676 /// runs the more such instances occur to the point where the in-memory collection is full of expired 1677 /// ceremonies. Since this should rarely occur and as long as [`Self::capacity`] is appropriate, 1678 /// [`Self::insert`] should almost always succeed; however very rarely there will be a point when 1679 /// one will have to [`Self::remove_expired_ceremonies`]. A vast majority of the time a user 1680 /// will complete the ceremony which requires ownership of the `ServerState` which in turn requires 1681 /// [`Self::take`] which will add an available slot. 1682 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1683 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1684 #[derive(Debug)] 1685 pub struct FixedCapHashSet<T, S = BuildIdentityHasher>(HashSet<T, S>); 1686 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1687 impl<T> FixedCapHashSet<T, BuildIdentityHasher> { 1688 /// Creates an empty `FixedCapHashSet` with at least the specified capacity. 1689 /// 1690 /// The hash set will be able to hold at least `capacity` elements without reallocating. This method is allowed 1691 /// to allocate for more elements than `capacity`. 1692 #[inline] 1693 #[must_use] 1694 pub fn new(capacity: usize) -> Self { 1695 Self(HashSet::with_capacity_and_hasher( 1696 capacity, 1697 BuildIdentityHasher, 1698 )) 1699 } 1700 } 1701 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1702 impl<T, S> FixedCapHashSet<T, S> { 1703 /// Creates an empty `FixedCapHashSet` with at least the specified capacity, using `hasher` to hash the keys. 1704 /// 1705 /// The hash set will be able to hold at least `capacity` elements without reallocating. This method is allowed 1706 /// to allocate for more elements than `capacity`. 1707 #[inline] 1708 #[must_use] 1709 pub fn new_with_hasher(capacity: usize, hasher: S) -> Self { 1710 Self(HashSet::with_capacity_and_hasher(capacity, hasher)) 1711 } 1712 /// Returns the immutable capacity. 1713 /// 1714 /// This number is a lower bound; the `FixedCapHashSet` might be able to hold more, but is guaranteed to be 1715 /// able to hold at least this many. 1716 #[inline] 1717 #[must_use] 1718 pub fn capacity(&self) -> usize { 1719 self.0.capacity() 1720 } 1721 /// Clears the set, removing all values. 1722 #[inline] 1723 pub fn clear(&mut self) { 1724 self.0.clear(); 1725 } 1726 /// Returns the number of elements in the set. 1727 #[inline] 1728 #[must_use] 1729 pub fn len(&self) -> usize { 1730 self.0.len() 1731 } 1732 /// Returns `true` iff the set contains no elements. 1733 #[inline] 1734 #[must_use] 1735 pub fn is_empty(&self) -> bool { 1736 self.0.is_empty() 1737 } 1738 /// Retains only the elements specified by the predicate. 1739 /// 1740 /// In other words, remove all elements `e` for which `f(&e)` returns `false`. The elements are visited in 1741 /// unsorted (and unspecified) order. 1742 #[inline] 1743 pub fn retain<F>(&mut self, f: F) 1744 where 1745 F: FnMut(&T) -> bool, 1746 { 1747 self.0.retain(f); 1748 } 1749 } 1750 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1751 impl<T: ServerState, S> FixedCapHashSet<T, S> { 1752 /// Removes all expired ceremonies. 1753 #[inline] 1754 pub fn remove_expired_ceremonies(&mut self) { 1755 // Even though it's more accurate to check the current `Instant` for each ceremony, we elect to capture 1756 // the `Instant` we begin iteration for performance reasons. It's unlikely an appreciable amount of 1757 // additional ceremonies would be removed. 1758 let now = Instant::now(); 1759 self.retain(|v| v.expiration() >= now); 1760 } 1761 } 1762 /// Result returned from [`FixedCapHashSet::insert`], [`FixedCapHashSet::insert_or_replace_expired`], and 1763 /// [`FixedCapHashSet::insert_or_replace_all_expired`]. 1764 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1765 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1766 #[derive(Clone, Copy, Debug)] 1767 pub enum InsertResult { 1768 /// Value was successfully inserted. 1769 Success, 1770 /// Value was not inserted since the capacity was full. 1771 CapacityFull, 1772 /// Value was not inserted since the value already existed. 1773 /// 1774 /// When the keys are based on [`Challenge`]s, this should almost never occur since `Challenge`s 1775 /// are 16 bytes of random data. 1776 Duplicate, 1777 } 1778 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1779 impl<T: Eq + Hash, S: BuildHasher> FixedCapHashSet<T, S> { 1780 /// Returns `true` iff the set contains a value. 1781 /// 1782 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1783 /// match those for the value type. 1784 #[inline] 1785 #[must_use] 1786 pub fn contains<Q>(&self, value: &Q) -> bool 1787 where 1788 T: Borrow<Q>, 1789 Q: Eq + Hash + ?Sized, 1790 { 1791 self.0.contains(value) 1792 } 1793 /// Returns a reference to the value in the set, if any, that is equal to the given value. 1794 /// 1795 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1796 /// match those for the value type. 1797 #[inline] 1798 #[must_use] 1799 pub fn get<Q>(&self, value: &Q) -> Option<&T> 1800 where 1801 T: Borrow<Q>, 1802 Q: Eq + Hash + ?Sized, 1803 { 1804 self.0.get(value) 1805 } 1806 /// Removes a value from the set. Returns whether the value was present in the set. 1807 /// 1808 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1809 /// match those for the value type. 1810 #[inline] 1811 pub fn remove<Q>(&mut self, value: &Q) -> bool 1812 where 1813 T: Borrow<Q>, 1814 Q: Eq + Hash + ?Sized, 1815 { 1816 self.0.remove(value) 1817 } 1818 /// Removes and returns the value in the set, if any, that is equal to the given one. 1819 /// 1820 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1821 /// match those for the value type. 1822 #[inline] 1823 pub fn take<Q>(&mut self, value: &Q) -> Option<T> 1824 where 1825 T: Borrow<Q>, 1826 Q: Eq + Hash + ?Sized, 1827 { 1828 self.0.take(value) 1829 } 1830 /// Adds `value` to the set iff [`Self::capacity`] `>` [`Self::len`]. 1831 #[inline] 1832 pub fn insert(&mut self, value: T) -> InsertResult { 1833 if self.len() == self.capacity() { 1834 InsertResult::CapacityFull 1835 } else if self.0.insert(value) { 1836 InsertResult::Success 1837 } else { 1838 InsertResult::Duplicate 1839 } 1840 } 1841 } 1842 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1843 impl<T: Borrow<SentChallenge> + Eq + Hash + ServerState, S: BuildHasher> FixedCapHashSet<T, S> { 1844 /// Adds a ceremony to the set. 1845 /// 1846 /// This will only insert `value` iff [`Self::capacity`] `>` [`Self::len`]. When [`Self::len`] `==` 1847 /// [`Self::capacity`], this will iterate items in the set until an expired ceremony is encountered; at which 1848 /// point, the expired ceremony will be removed before `value` is inserted. In the event no expired ceremonies 1849 /// exist, [`InsertResult::CapacityFull`] will be returned. 1850 /// 1851 /// If one wants to avoid the potentially expensive operation of iterating the set for an expired ceremony, 1852 /// call [`Self::insert`]. Alternatively one can call [`Self::insert_or_replace_all_expired`] to avoid 1853 /// repeatedly iterating the hash set once its capacity is full. 1854 #[inline] 1855 pub fn insert_or_replace_expired(&mut self, value: T) -> InsertResult { 1856 if self.len() == self.capacity() { 1857 let now = Instant::now(); 1858 self.0 1859 .iter() 1860 .try_fold((), |(), v| { 1861 if v.expiration() < now { 1862 Err(v.sent_challenge()) 1863 } else { 1864 Ok(()) 1865 } 1866 }) 1867 .map_or_else( 1868 |chall| { 1869 self.remove(&chall); 1870 if self.0.insert(value) { 1871 InsertResult::Success 1872 } else { 1873 InsertResult::Duplicate 1874 } 1875 }, 1876 |()| InsertResult::CapacityFull, 1877 ) 1878 } else if self.0.insert(value) { 1879 InsertResult::Success 1880 } else { 1881 InsertResult::Duplicate 1882 } 1883 } 1884 /// Adds a ceremony to the set. 1885 /// 1886 /// This will only insert `value` iff [`Self::capacity`] `>` [`Self::len`]. When [`Self::len`] `==` 1887 /// [`Self::capacity`], this will [`Self::remove_expired_ceremonies`] before `value` is inserted. In the event 1888 /// no expired ceremonies exist, [`InsertResult::CapacityFull`] will be returned. 1889 #[inline] 1890 pub fn insert_or_replace_all_expired(&mut self, value: T) -> InsertResult { 1891 if self.len() == self.capacity() { 1892 self.remove_expired_ceremonies(); 1893 } 1894 if self.len() == self.capacity() { 1895 InsertResult::CapacityFull 1896 } else if self.0.insert(value) { 1897 InsertResult::Success 1898 } else { 1899 InsertResult::Duplicate 1900 } 1901 } 1902 } 1903 #[cfg(test)] 1904 mod tests { 1905 #[cfg(feature = "custom")] 1906 use super::{ 1907 super::{ 1908 AggErr, AuthenticatedCredential, 1909 response::{ 1910 AuthTransports, AuthenticatorAttachment, Backup, CredentialId, 1911 auth::{Authentication, AuthenticatorAssertion}, 1912 register::{ 1913 AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation, 1914 AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputs, 1915 CompressedP256PubKey, CompressedP384PubKey, CompressedPubKey, 1916 CredentialProtectionPolicy, DynamicState, Ed25519PubKey, Registration, 1917 RsaPubKey, StaticState, UncompressedPubKey, 1918 }, 1919 }, 1920 }, 1921 AsciiDomain, Challenge, Credentials, ExtensionInfo, ExtensionReq, 1922 PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, 1923 auth::{ 1924 AllowedCredential, AllowedCredentials, AuthenticationVerificationOptions, 1925 CredentialSpecificExtension, Extension as AuthExt, PrfInputOwned, 1926 PublicKeyCredentialRequestOptions, 1927 }, 1928 register::{ 1929 CredProtect, Extension as RegExt, PublicKeyCredentialCreationOptions, 1930 PublicKeyCredentialUserEntity, RegistrationVerificationOptions, UserHandle, 1931 }, 1932 }; 1933 #[cfg(feature = "custom")] 1934 use ed25519_dalek::{Signer, SigningKey}; 1935 #[cfg(feature = "custom")] 1936 use p256::{ 1937 ecdsa::{DerSignature as P256DerSig, SigningKey as P256Key}, 1938 elliptic_curve::sec1::Tag, 1939 }; 1940 #[cfg(feature = "custom")] 1941 use p384::ecdsa::{DerSignature as P384DerSig, SigningKey as P384Key}; 1942 #[cfg(feature = "custom")] 1943 use rsa::{ 1944 BigUint, RsaPrivateKey, 1945 pkcs1v15::SigningKey as RsaKey, 1946 sha2::{Digest, Sha256}, 1947 signature::{Keypair, SignatureEncoding}, 1948 traits::PublicKeyParts, 1949 }; 1950 #[cfg(feature = "custom")] 1951 const CBOR_UINT: u8 = 0b000_00000; 1952 #[cfg(feature = "custom")] 1953 const CBOR_NEG: u8 = 0b001_00000; 1954 #[cfg(feature = "custom")] 1955 const CBOR_BYTES: u8 = 0b010_00000; 1956 #[cfg(feature = "custom")] 1957 const CBOR_TEXT: u8 = 0b011_00000; 1958 #[cfg(feature = "custom")] 1959 const CBOR_MAP: u8 = 0b101_00000; 1960 #[cfg(feature = "custom")] 1961 const CBOR_SIMPLE: u8 = 0b111_00000; 1962 #[cfg(feature = "custom")] 1963 const CBOR_TRUE: u8 = CBOR_SIMPLE | 21; 1964 #[test] 1965 #[cfg(feature = "custom")] 1966 fn ed25519_reg() -> Result<(), AggErr> { 1967 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 1968 let id = UserHandle::try_from([0; 1].as_slice())?; 1969 let mut opts = PublicKeyCredentialCreationOptions::passkey( 1970 &rp_id, 1971 PublicKeyCredentialUserEntity { 1972 name: "foo".try_into()?, 1973 id, 1974 display_name: None, 1975 }, 1976 Vec::new(), 1977 ); 1978 opts.challenge = Challenge(0); 1979 opts.extensions = RegExt { 1980 cred_props: None, 1981 cred_protect: CredProtect::UserVerificationRequired(ExtensionInfo::RequireEnforceValue), 1982 min_pin_length: Some((10, ExtensionInfo::RequireEnforceValue)), 1983 prf: Some(ExtensionInfo::RequireEnforceValue), 1984 }; 1985 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 1986 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 1987 let mut attestation_object = Vec::new(); 1988 attestation_object.extend_from_slice( 1989 [ 1990 CBOR_MAP | 3, 1991 CBOR_TEXT | 3, 1992 b'f', 1993 b'm', 1994 b't', 1995 CBOR_TEXT | 6, 1996 b'p', 1997 b'a', 1998 b'c', 1999 b'k', 2000 b'e', 2001 b'd', 2002 CBOR_TEXT | 7, 2003 b'a', 2004 b't', 2005 b't', 2006 b'S', 2007 b't', 2008 b'm', 2009 b't', 2010 CBOR_MAP | 2, 2011 CBOR_TEXT | 3, 2012 b'a', 2013 b'l', 2014 b'g', 2015 // COSE EdDSA. 2016 CBOR_NEG | 7, 2017 CBOR_TEXT | 3, 2018 b's', 2019 b'i', 2020 b'g', 2021 CBOR_BYTES | 24, 2022 64, 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 0, 2040 0, 2041 0, 2042 0, 2043 0, 2044 0, 2045 0, 2046 0, 2047 0, 2048 0, 2049 0, 2050 0, 2051 0, 2052 0, 2053 0, 2054 0, 2055 0, 2056 0, 2057 0, 2058 0, 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 CBOR_TEXT | 8, 2088 b'a', 2089 b'u', 2090 b't', 2091 b'h', 2092 b'D', 2093 b'a', 2094 b't', 2095 b'a', 2096 CBOR_BYTES | 24, 2097 // Length is 154. 2098 154, 2099 // RP ID HASH. 2100 // This will be overwritten later. 2101 0, 2102 0, 2103 0, 2104 0, 2105 0, 2106 0, 2107 0, 2108 0, 2109 0, 2110 0, 2111 0, 2112 0, 2113 0, 2114 0, 2115 0, 2116 0, 2117 0, 2118 0, 2119 0, 2120 0, 2121 0, 2122 0, 2123 0, 2124 0, 2125 0, 2126 0, 2127 0, 2128 0, 2129 0, 2130 0, 2131 0, 2132 0, 2133 // FLAGS. 2134 // UP, UV, AT, and ED (right-to-left). 2135 0b1100_0101, 2136 // COUNTER. 2137 // 0 as 32-bit big endian. 2138 0, 2139 0, 2140 0, 2141 0, 2142 // AAGUID. 2143 0, 2144 0, 2145 0, 2146 0, 2147 0, 2148 0, 2149 0, 2150 0, 2151 0, 2152 0, 2153 0, 2154 0, 2155 0, 2156 0, 2157 0, 2158 0, 2159 // L. 2160 // CREDENTIAL ID length is 16 as 16-bit big endian. 2161 0, 2162 16, 2163 // CREDENTIAL ID. 2164 0, 2165 0, 2166 0, 2167 0, 2168 0, 2169 0, 2170 0, 2171 0, 2172 0, 2173 0, 2174 0, 2175 0, 2176 0, 2177 0, 2178 0, 2179 0, 2180 CBOR_MAP | 4, 2181 // COSE kty. 2182 CBOR_UINT | 1, 2183 // COSE OKP. 2184 CBOR_UINT | 1, 2185 // COSE alg. 2186 CBOR_UINT | 3, 2187 // COSE EdDSA. 2188 CBOR_NEG | 7, 2189 // COSE OKP crv. 2190 CBOR_NEG, 2191 // COSE Ed25519. 2192 CBOR_UINT | 6, 2193 // COSE OKP x. 2194 CBOR_NEG | 1, 2195 CBOR_BYTES | 24, 2196 // Length is 32. 2197 32, 2198 // Compressed-y coordinate. 2199 // This will be overwritten later. 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 0, 2228 0, 2229 0, 2230 0, 2231 0, 2232 CBOR_MAP | 3, 2233 CBOR_TEXT | 11, 2234 b'c', 2235 b'r', 2236 b'e', 2237 b'd', 2238 b'P', 2239 b'r', 2240 b'o', 2241 b't', 2242 b'e', 2243 b'c', 2244 b't', 2245 // userVerificationRequired. 2246 CBOR_UINT | 3, 2247 // CBOR text of length 11. 2248 CBOR_TEXT | 11, 2249 b'h', 2250 b'm', 2251 b'a', 2252 b'c', 2253 b'-', 2254 b's', 2255 b'e', 2256 b'c', 2257 b'r', 2258 b'e', 2259 b't', 2260 CBOR_TRUE, 2261 CBOR_TEXT | 12, 2262 b'm', 2263 b'i', 2264 b'n', 2265 b'P', 2266 b'i', 2267 b'n', 2268 b'L', 2269 b'e', 2270 b'n', 2271 b'g', 2272 b't', 2273 b'h', 2274 CBOR_UINT | 16, 2275 ] 2276 .as_slice(), 2277 ); 2278 attestation_object 2279 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2280 let sig_key = SigningKey::from_bytes(&[0; 32]); 2281 let ver_key = sig_key.verifying_key(); 2282 let pub_key = ver_key.as_bytes(); 2283 attestation_object[107..139] 2284 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2285 attestation_object[188..220].copy_from_slice(pub_key); 2286 let sig = sig_key.sign(&attestation_object[107..]); 2287 attestation_object[32..96].copy_from_slice(sig.to_bytes().as_slice()); 2288 attestation_object.truncate(261); 2289 assert!(matches!(opts.start_ceremony()?.0.verify( 2290 &rp_id, 2291 id, 2292 &Registration { 2293 response: AuthenticatorAttestation::new( 2294 client_data_json, 2295 attestation_object, 2296 AuthTransports::NONE, 2297 ), 2298 authenticator_attachment: AuthenticatorAttachment::None, 2299 client_extension_results: ClientExtensionsOutputs { 2300 cred_props: None, 2301 prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true, }), 2302 }, 2303 }, 2304 &RegistrationVerificationOptions::<&str, &str>::default(), 2305 )?.static_state.credential_public_key, UncompressedPubKey::Ed25519(k) if k.into_inner() == pub_key)); 2306 Ok(()) 2307 } 2308 #[test] 2309 #[cfg(feature = "custom")] 2310 fn ed25519_auth() -> Result<(), AggErr> { 2311 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2312 let mut creds = AllowedCredentials::with_capacity(1); 2313 creds.push(AllowedCredential { 2314 credential: PublicKeyCredentialDescriptor { 2315 id: CredentialId::try_from(vec![0; 16])?, 2316 transports: AuthTransports::NONE, 2317 }, 2318 extension: CredentialSpecificExtension { 2319 prf: Some(PrfInputOwned { 2320 first: Vec::new(), 2321 second: Some(Vec::new()), 2322 ext_info: ExtensionReq::Require, 2323 }), 2324 }, 2325 }); 2326 let mut opts = PublicKeyCredentialRequestOptions::second_factor(&rp_id, creds)?; 2327 opts.user_verification = UserVerificationRequirement::Required; 2328 opts.challenge = Challenge(0); 2329 opts.extensions = AuthExt { prf: None }; 2330 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2331 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 2332 let mut authenticator_data = Vec::with_capacity(164); 2333 authenticator_data.extend_from_slice( 2334 [ 2335 // rpIdHash. 2336 // This will be overwritten later. 2337 0, 2338 0, 2339 0, 2340 0, 2341 0, 2342 0, 2343 0, 2344 0, 2345 0, 2346 0, 2347 0, 2348 0, 2349 0, 2350 0, 2351 0, 2352 0, 2353 0, 2354 0, 2355 0, 2356 0, 2357 0, 2358 0, 2359 0, 2360 0, 2361 0, 2362 0, 2363 0, 2364 0, 2365 0, 2366 0, 2367 0, 2368 0, 2369 // flags. 2370 // UP, UV, and ED (right-to-left). 2371 0b1000_0101, 2372 // signCount. 2373 // 0 as 32-bit big endian. 2374 0, 2375 0, 2376 0, 2377 0, 2378 CBOR_MAP | 1, 2379 CBOR_TEXT | 11, 2380 b'h', 2381 b'm', 2382 b'a', 2383 b'c', 2384 b'-', 2385 b's', 2386 b'e', 2387 b'c', 2388 b'r', 2389 b'e', 2390 b't', 2391 CBOR_BYTES | 24, 2392 // Length is 80. 2393 80, 2394 // Two HMAC outputs concatenated and encrypted. 2395 0, 2396 0, 2397 0, 2398 0, 2399 0, 2400 0, 2401 0, 2402 0, 2403 0, 2404 0, 2405 0, 2406 0, 2407 0, 2408 0, 2409 0, 2410 0, 2411 0, 2412 0, 2413 0, 2414 0, 2415 0, 2416 0, 2417 0, 2418 0, 2419 0, 2420 0, 2421 0, 2422 0, 2423 0, 2424 0, 2425 0, 2426 0, 2427 0, 2428 0, 2429 0, 2430 0, 2431 0, 2432 0, 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 0, 2466 0, 2467 0, 2468 0, 2469 0, 2470 0, 2471 0, 2472 0, 2473 0, 2474 0, 2475 ] 2476 .as_slice(), 2477 ); 2478 authenticator_data[..32] 2479 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2480 authenticator_data 2481 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2482 let ed_priv = SigningKey::from([0; 32]); 2483 let sig = ed_priv.sign(authenticator_data.as_slice()).to_vec(); 2484 authenticator_data.truncate(132); 2485 assert!(!opts.start_ceremony()?.0.verify( 2486 &rp_id, 2487 &Authentication { 2488 raw_id: CredentialId::try_from(vec![0; 16])?, 2489 response: AuthenticatorAssertion::with_user( 2490 client_data_json, 2491 authenticator_data, 2492 sig, 2493 UserHandle::from([0]), 2494 ), 2495 authenticator_attachment: AuthenticatorAttachment::None, 2496 }, 2497 &mut AuthenticatedCredential::new( 2498 CredentialId::try_from([0; 16].as_slice())?, 2499 UserHandle::try_from([0].as_slice())?, 2500 StaticState { 2501 credential_public_key: CompressedPubKey::<_, &[u8], &[u8], &[u8]>::Ed25519( 2502 Ed25519PubKey::from(ed_priv.verifying_key().to_bytes()), 2503 ), 2504 extensions: AuthenticatorExtensionOutputStaticState { 2505 cred_protect: CredentialProtectionPolicy::None, 2506 hmac_secret: Some(true), 2507 }, 2508 }, 2509 DynamicState { 2510 user_verified: true, 2511 backup: Backup::NotEligible, 2512 sign_count: 0, 2513 authenticator_attachment: AuthenticatorAttachment::None, 2514 }, 2515 )?, 2516 &AuthenticationVerificationOptions::<&str, &str>::default(), 2517 )?); 2518 Ok(()) 2519 } 2520 #[test] 2521 #[cfg(feature = "custom")] 2522 fn p256_reg() -> Result<(), AggErr> { 2523 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2524 let id = UserHandle::try_from([0; 1].as_slice())?; 2525 let mut opts = PublicKeyCredentialCreationOptions::passkey( 2526 &rp_id, 2527 PublicKeyCredentialUserEntity { 2528 name: "foo".try_into()?, 2529 id, 2530 display_name: None, 2531 }, 2532 Vec::new(), 2533 ); 2534 opts.challenge = Challenge(0); 2535 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2536 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 2537 let mut attestation_object = Vec::with_capacity(210); 2538 attestation_object.extend_from_slice( 2539 [ 2540 CBOR_MAP | 3, 2541 CBOR_TEXT | 3, 2542 b'f', 2543 b'm', 2544 b't', 2545 CBOR_TEXT | 4, 2546 b'n', 2547 b'o', 2548 b'n', 2549 b'e', 2550 CBOR_TEXT | 7, 2551 b'a', 2552 b't', 2553 b't', 2554 b'S', 2555 b't', 2556 b'm', 2557 b't', 2558 CBOR_MAP, 2559 CBOR_TEXT | 8, 2560 b'a', 2561 b'u', 2562 b't', 2563 b'h', 2564 b'D', 2565 b'a', 2566 b't', 2567 b'a', 2568 CBOR_BYTES | 24, 2569 // Length is 148. 2570 148, 2571 // RP ID HASH. 2572 // This will be overwritten later. 2573 0, 2574 0, 2575 0, 2576 0, 2577 0, 2578 0, 2579 0, 2580 0, 2581 0, 2582 0, 2583 0, 2584 0, 2585 0, 2586 0, 2587 0, 2588 0, 2589 0, 2590 0, 2591 0, 2592 0, 2593 0, 2594 0, 2595 0, 2596 0, 2597 0, 2598 0, 2599 0, 2600 0, 2601 0, 2602 0, 2603 0, 2604 0, 2605 // FLAGS. 2606 // UP, UV, and AT (right-to-left). 2607 0b0100_0101, 2608 // COUNTER. 2609 // 0 as 32-bit big endian. 2610 0, 2611 0, 2612 0, 2613 0, 2614 // AAGUID. 2615 0, 2616 0, 2617 0, 2618 0, 2619 0, 2620 0, 2621 0, 2622 0, 2623 0, 2624 0, 2625 0, 2626 0, 2627 0, 2628 0, 2629 0, 2630 0, 2631 // L. 2632 // CREDENTIAL ID length is 16 as 16-bit big endian. 2633 0, 2634 16, 2635 // CREDENTIAL ID. 2636 0, 2637 0, 2638 0, 2639 0, 2640 0, 2641 0, 2642 0, 2643 0, 2644 0, 2645 0, 2646 0, 2647 0, 2648 0, 2649 0, 2650 0, 2651 0, 2652 CBOR_MAP | 5, 2653 // COSE kty. 2654 CBOR_UINT | 1, 2655 // COSE EC2. 2656 CBOR_UINT | 2, 2657 // COSE alg. 2658 CBOR_UINT | 3, 2659 // COSE ES256. 2660 CBOR_NEG | 6, 2661 // COSE EC2 crv. 2662 CBOR_NEG, 2663 // COSE P-256. 2664 CBOR_UINT | 1, 2665 // COSE EC2 x. 2666 CBOR_NEG | 1, 2667 CBOR_BYTES | 24, 2668 // Length is 32. 2669 32, 2670 // X-coordinate. This will be overwritten later. 2671 0, 2672 0, 2673 0, 2674 0, 2675 0, 2676 0, 2677 0, 2678 0, 2679 0, 2680 0, 2681 0, 2682 0, 2683 0, 2684 0, 2685 0, 2686 0, 2687 0, 2688 0, 2689 0, 2690 0, 2691 0, 2692 0, 2693 0, 2694 0, 2695 0, 2696 0, 2697 0, 2698 0, 2699 0, 2700 0, 2701 0, 2702 0, 2703 // COSE EC2 y. 2704 CBOR_NEG | 2, 2705 CBOR_BYTES | 24, 2706 // Length is 32. 2707 32, 2708 // Y-coordinate. This will be overwritten later. 2709 0, 2710 0, 2711 0, 2712 0, 2713 0, 2714 0, 2715 0, 2716 0, 2717 0, 2718 0, 2719 0, 2720 0, 2721 0, 2722 0, 2723 0, 2724 0, 2725 0, 2726 0, 2727 0, 2728 0, 2729 0, 2730 0, 2731 0, 2732 0, 2733 0, 2734 0, 2735 0, 2736 0, 2737 0, 2738 0, 2739 0, 2740 0, 2741 ] 2742 .as_slice(), 2743 ); 2744 attestation_object[30..62] 2745 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2746 let p256_key = P256Key::from_bytes( 2747 &[ 2748 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 2749 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 2750 ] 2751 .into(), 2752 ) 2753 .unwrap() 2754 .verifying_key() 2755 .to_encoded_point(false); 2756 let x = p256_key.x().unwrap(); 2757 let y = p256_key.y().unwrap(); 2758 attestation_object[111..143].copy_from_slice(x); 2759 attestation_object[146..].copy_from_slice(y); 2760 assert!(matches!(opts.start_ceremony()?.0.verify( 2761 &rp_id, 2762 id, 2763 &Registration { 2764 response: AuthenticatorAttestation::new( 2765 client_data_json, 2766 attestation_object, 2767 AuthTransports::NONE, 2768 ), 2769 authenticator_attachment: AuthenticatorAttachment::None, 2770 client_extension_results: ClientExtensionsOutputs { 2771 cred_props: None, 2772 prf: None, 2773 }, 2774 }, 2775 &RegistrationVerificationOptions::<&str, &str>::default(), 2776 )?.static_state.credential_public_key, UncompressedPubKey::P256(k) if k.x() == x.as_slice() && k.y() == y.as_slice())); 2777 Ok(()) 2778 } 2779 #[test] 2780 #[cfg(feature = "custom")] 2781 fn p256_auth() -> Result<(), AggErr> { 2782 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2783 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 2784 opts.challenge = Challenge(0); 2785 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2786 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 2787 let mut authenticator_data = Vec::with_capacity(69); 2788 authenticator_data.extend_from_slice( 2789 [ 2790 // rpIdHash. 2791 // This will be overwritten later. 2792 0, 2793 0, 2794 0, 2795 0, 2796 0, 2797 0, 2798 0, 2799 0, 2800 0, 2801 0, 2802 0, 2803 0, 2804 0, 2805 0, 2806 0, 2807 0, 2808 0, 2809 0, 2810 0, 2811 0, 2812 0, 2813 0, 2814 0, 2815 0, 2816 0, 2817 0, 2818 0, 2819 0, 2820 0, 2821 0, 2822 0, 2823 0, 2824 // flags. 2825 // UP and UV (right-to-left). 2826 0b0000_0101, 2827 // signCount. 2828 // 0 as 32-bit big endian. 2829 0, 2830 0, 2831 0, 2832 0, 2833 ] 2834 .as_slice(), 2835 ); 2836 authenticator_data[..32] 2837 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2838 authenticator_data 2839 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2840 let p256_key = P256Key::from_bytes( 2841 &[ 2842 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 2843 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 2844 ] 2845 .into(), 2846 ) 2847 .unwrap(); 2848 let der_sig: P256DerSig = p256_key.sign(authenticator_data.as_slice()); 2849 let pub_key = p256_key.verifying_key().to_encoded_point(true); 2850 authenticator_data.truncate(37); 2851 assert!(!opts.start_ceremony()?.0.verify( 2852 &rp_id, 2853 &Authentication { 2854 raw_id: CredentialId::try_from(vec![0; 16])?, 2855 response: AuthenticatorAssertion::with_user( 2856 client_data_json, 2857 authenticator_data, 2858 der_sig.as_bytes().into(), 2859 UserHandle::from([0]), 2860 ), 2861 authenticator_attachment: AuthenticatorAttachment::None, 2862 }, 2863 &mut AuthenticatedCredential::new( 2864 CredentialId::try_from([0; 16].as_slice())?, 2865 UserHandle::try_from([0].as_slice())?, 2866 StaticState { 2867 credential_public_key: CompressedPubKey::<&[u8], _, &[u8], &[u8]>::P256( 2868 CompressedP256PubKey::from(( 2869 (*pub_key.x().unwrap()).into(), 2870 pub_key.tag() == Tag::CompressedOddY 2871 )), 2872 ), 2873 extensions: AuthenticatorExtensionOutputStaticState { 2874 cred_protect: CredentialProtectionPolicy::None, 2875 hmac_secret: None, 2876 }, 2877 }, 2878 DynamicState { 2879 user_verified: true, 2880 backup: Backup::NotEligible, 2881 sign_count: 0, 2882 authenticator_attachment: AuthenticatorAttachment::None, 2883 }, 2884 )?, 2885 &AuthenticationVerificationOptions::<&str, &str>::default(), 2886 )?); 2887 Ok(()) 2888 } 2889 #[test] 2890 #[cfg(feature = "custom")] 2891 fn p384_reg() -> Result<(), AggErr> { 2892 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2893 let id = UserHandle::try_from([0; 1].as_slice())?; 2894 let mut opts = PublicKeyCredentialCreationOptions::passkey( 2895 &rp_id, 2896 PublicKeyCredentialUserEntity { 2897 name: "foo".try_into()?, 2898 id, 2899 display_name: None, 2900 }, 2901 Vec::new(), 2902 ); 2903 opts.challenge = Challenge(0); 2904 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2905 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 2906 let mut attestation_object = Vec::with_capacity(243); 2907 attestation_object.extend_from_slice( 2908 [ 2909 CBOR_MAP | 3, 2910 CBOR_TEXT | 3, 2911 b'f', 2912 b'm', 2913 b't', 2914 CBOR_TEXT | 4, 2915 b'n', 2916 b'o', 2917 b'n', 2918 b'e', 2919 // CBOR text of length 7. 2920 CBOR_TEXT | 7, 2921 b'a', 2922 b't', 2923 b't', 2924 b'S', 2925 b't', 2926 b'm', 2927 b't', 2928 CBOR_MAP, 2929 CBOR_TEXT | 8, 2930 b'a', 2931 b'u', 2932 b't', 2933 b'h', 2934 b'D', 2935 b'a', 2936 b't', 2937 b'a', 2938 CBOR_BYTES | 24, 2939 // Length is 181. 2940 181, 2941 // RP ID HASH. 2942 // This will be overwritten later. 2943 0, 2944 0, 2945 0, 2946 0, 2947 0, 2948 0, 2949 0, 2950 0, 2951 0, 2952 0, 2953 0, 2954 0, 2955 0, 2956 0, 2957 0, 2958 0, 2959 0, 2960 0, 2961 0, 2962 0, 2963 0, 2964 0, 2965 0, 2966 0, 2967 0, 2968 0, 2969 0, 2970 0, 2971 0, 2972 0, 2973 0, 2974 0, 2975 // FLAGS. 2976 // UP, UV, and AT (right-to-left). 2977 0b0100_0101, 2978 // COUNTER. 2979 // 0 as 32-bit big-endian. 2980 0, 2981 0, 2982 0, 2983 0, 2984 // AAGUID. 2985 0, 2986 0, 2987 0, 2988 0, 2989 0, 2990 0, 2991 0, 2992 0, 2993 0, 2994 0, 2995 0, 2996 0, 2997 0, 2998 0, 2999 0, 3000 0, 3001 // L. 3002 // CREDENTIAL ID length is 16 as 16-bit big endian. 3003 0, 3004 16, 3005 // CREDENTIAL ID. 3006 0, 3007 0, 3008 0, 3009 0, 3010 0, 3011 0, 3012 0, 3013 0, 3014 0, 3015 0, 3016 0, 3017 0, 3018 0, 3019 0, 3020 0, 3021 0, 3022 CBOR_MAP | 5, 3023 // COSE kty. 3024 CBOR_UINT | 1, 3025 // COSE EC2. 3026 CBOR_UINT | 2, 3027 // COSE alg. 3028 CBOR_UINT | 3, 3029 CBOR_NEG | 24, 3030 // COSE ES384. 3031 34, 3032 // COSE EC2 crv. 3033 CBOR_NEG, 3034 // COSE P-384. 3035 CBOR_UINT | 2, 3036 // COSE EC2 x. 3037 CBOR_NEG | 1, 3038 CBOR_BYTES | 24, 3039 // Length is 48. 3040 48, 3041 // X-coordinate. This will be overwritten later. 3042 0, 3043 0, 3044 0, 3045 0, 3046 0, 3047 0, 3048 0, 3049 0, 3050 0, 3051 0, 3052 0, 3053 0, 3054 0, 3055 0, 3056 0, 3057 0, 3058 0, 3059 0, 3060 0, 3061 0, 3062 0, 3063 0, 3064 0, 3065 0, 3066 0, 3067 0, 3068 0, 3069 0, 3070 0, 3071 0, 3072 0, 3073 0, 3074 0, 3075 0, 3076 0, 3077 0, 3078 0, 3079 0, 3080 0, 3081 0, 3082 0, 3083 0, 3084 0, 3085 0, 3086 0, 3087 0, 3088 0, 3089 0, 3090 // COSE EC2 y. 3091 CBOR_NEG | 2, 3092 CBOR_BYTES | 24, 3093 // Length is 48. 3094 48, 3095 // Y-coordinate. This will be overwritten later. 3096 0, 3097 0, 3098 0, 3099 0, 3100 0, 3101 0, 3102 0, 3103 0, 3104 0, 3105 0, 3106 0, 3107 0, 3108 0, 3109 0, 3110 0, 3111 0, 3112 0, 3113 0, 3114 0, 3115 0, 3116 0, 3117 0, 3118 0, 3119 0, 3120 0, 3121 0, 3122 0, 3123 0, 3124 0, 3125 0, 3126 0, 3127 0, 3128 0, 3129 0, 3130 0, 3131 0, 3132 0, 3133 0, 3134 0, 3135 0, 3136 0, 3137 0, 3138 0, 3139 0, 3140 0, 3141 0, 3142 0, 3143 0, 3144 ] 3145 .as_slice(), 3146 ); 3147 attestation_object[30..62] 3148 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3149 let p384_key = P384Key::from_bytes( 3150 &[ 3151 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 3152 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 3153 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 3154 ] 3155 .into(), 3156 ) 3157 .unwrap() 3158 .verifying_key() 3159 .to_encoded_point(false); 3160 let x = p384_key.x().unwrap(); 3161 let y = p384_key.y().unwrap(); 3162 attestation_object[112..160].copy_from_slice(x); 3163 attestation_object[163..].copy_from_slice(y); 3164 assert!(matches!(opts.start_ceremony()?.0.verify( 3165 &rp_id, 3166 id, 3167 &Registration { 3168 response: AuthenticatorAttestation::new( 3169 client_data_json, 3170 attestation_object, 3171 AuthTransports::NONE, 3172 ), 3173 authenticator_attachment: AuthenticatorAttachment::None, 3174 client_extension_results: ClientExtensionsOutputs { 3175 cred_props: None, 3176 prf: None, 3177 }, 3178 }, 3179 &RegistrationVerificationOptions::<&str, &str>::default(), 3180 )?.static_state.credential_public_key, UncompressedPubKey::P384(k) if k.x() == x.as_slice() && k.y() == y.as_slice())); 3181 Ok(()) 3182 } 3183 #[test] 3184 #[cfg(feature = "custom")] 3185 fn p384_auth() -> Result<(), AggErr> { 3186 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3187 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 3188 opts.challenge = Challenge(0); 3189 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3190 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 3191 let mut authenticator_data = Vec::with_capacity(69); 3192 authenticator_data.extend_from_slice( 3193 [ 3194 // rpIdHash. 3195 // This will be overwritten later. 3196 0, 3197 0, 3198 0, 3199 0, 3200 0, 3201 0, 3202 0, 3203 0, 3204 0, 3205 0, 3206 0, 3207 0, 3208 0, 3209 0, 3210 0, 3211 0, 3212 0, 3213 0, 3214 0, 3215 0, 3216 0, 3217 0, 3218 0, 3219 0, 3220 0, 3221 0, 3222 0, 3223 0, 3224 0, 3225 0, 3226 0, 3227 0, 3228 // flags. 3229 // UP and UV (right-to-left). 3230 0b0000_0101, 3231 // signCount. 3232 // 0 as 32-bit big-endian. 3233 0, 3234 0, 3235 0, 3236 0, 3237 ] 3238 .as_slice(), 3239 ); 3240 authenticator_data[..32] 3241 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3242 authenticator_data 3243 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 3244 let p384_key = P384Key::from_bytes( 3245 &[ 3246 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 3247 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 3248 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 3249 ] 3250 .into(), 3251 ) 3252 .unwrap(); 3253 let der_sig: P384DerSig = p384_key.sign(authenticator_data.as_slice()); 3254 let pub_key = p384_key.verifying_key().to_encoded_point(true); 3255 authenticator_data.truncate(37); 3256 assert!(!opts.start_ceremony()?.0.verify( 3257 &rp_id, 3258 &Authentication { 3259 raw_id: CredentialId::try_from(vec![0; 16])?, 3260 response: AuthenticatorAssertion::with_user( 3261 client_data_json, 3262 authenticator_data, 3263 der_sig.as_bytes().into(), 3264 UserHandle::from([0]), 3265 ), 3266 authenticator_attachment: AuthenticatorAttachment::None, 3267 }, 3268 &mut AuthenticatedCredential::new( 3269 CredentialId::try_from([0; 16].as_slice())?, 3270 UserHandle::try_from([0].as_slice())?, 3271 StaticState { 3272 credential_public_key: CompressedPubKey::<&[u8], &[u8], _, &[u8]>::P384( 3273 CompressedP384PubKey::from(( 3274 (*pub_key.x().unwrap()).into(), 3275 pub_key.tag() == Tag::CompressedOddY 3276 )), 3277 ), 3278 extensions: AuthenticatorExtensionOutputStaticState { 3279 cred_protect: CredentialProtectionPolicy::None, 3280 hmac_secret: None, 3281 }, 3282 }, 3283 DynamicState { 3284 user_verified: true, 3285 backup: Backup::NotEligible, 3286 sign_count: 0, 3287 authenticator_attachment: AuthenticatorAttachment::None, 3288 }, 3289 )?, 3290 &AuthenticationVerificationOptions::<&str, &str>::default(), 3291 )?); 3292 Ok(()) 3293 } 3294 #[test] 3295 #[cfg(feature = "custom")] 3296 fn rsa_reg() -> Result<(), AggErr> { 3297 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3298 let id = UserHandle::try_from([0; 1].as_slice())?; 3299 let mut opts = PublicKeyCredentialCreationOptions::passkey( 3300 &rp_id, 3301 PublicKeyCredentialUserEntity { 3302 name: "foo".try_into()?, 3303 id, 3304 display_name: None, 3305 }, 3306 Vec::new(), 3307 ); 3308 opts.challenge = Challenge(0); 3309 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3310 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 3311 let mut attestation_object = Vec::with_capacity(406); 3312 attestation_object.extend_from_slice( 3313 [ 3314 CBOR_MAP | 3, 3315 CBOR_TEXT | 3, 3316 b'f', 3317 b'm', 3318 b't', 3319 CBOR_TEXT | 4, 3320 b'n', 3321 b'o', 3322 b'n', 3323 b'e', 3324 CBOR_TEXT | 7, 3325 b'a', 3326 b't', 3327 b't', 3328 b'S', 3329 b't', 3330 b'm', 3331 b't', 3332 CBOR_MAP, 3333 CBOR_TEXT | 8, 3334 b'a', 3335 b'u', 3336 b't', 3337 b'h', 3338 b'D', 3339 b'a', 3340 b't', 3341 b'a', 3342 CBOR_BYTES | 25, 3343 // Length is 343 as 16-bit big-endian. 3344 1, 3345 87, 3346 // RP ID HASH. 3347 // This will be overwritten later. 3348 0, 3349 0, 3350 0, 3351 0, 3352 0, 3353 0, 3354 0, 3355 0, 3356 0, 3357 0, 3358 0, 3359 0, 3360 0, 3361 0, 3362 0, 3363 0, 3364 0, 3365 0, 3366 0, 3367 0, 3368 0, 3369 0, 3370 0, 3371 0, 3372 0, 3373 0, 3374 0, 3375 0, 3376 0, 3377 0, 3378 0, 3379 0, 3380 // FLAGS. 3381 // UP, UV, and AT (right-to-left). 3382 0b0100_0101, 3383 // COUNTER. 3384 // 0 as 32-bit big-endian. 3385 0, 3386 0, 3387 0, 3388 0, 3389 // AAGUID. 3390 0, 3391 0, 3392 0, 3393 0, 3394 0, 3395 0, 3396 0, 3397 0, 3398 0, 3399 0, 3400 0, 3401 0, 3402 0, 3403 0, 3404 0, 3405 0, 3406 // L. 3407 // CREDENTIAL ID length is 16 as 16-bit big endian. 3408 0, 3409 16, 3410 // CREDENTIAL ID. 3411 0, 3412 0, 3413 0, 3414 0, 3415 0, 3416 0, 3417 0, 3418 0, 3419 0, 3420 0, 3421 0, 3422 0, 3423 0, 3424 0, 3425 0, 3426 0, 3427 CBOR_MAP | 4, 3428 // COSE kty. 3429 CBOR_UINT | 1, 3430 // COSE RSA. 3431 CBOR_UINT | 3, 3432 // COSE alg. 3433 CBOR_UINT | 3, 3434 CBOR_NEG | 25, 3435 // COSE RS256. 3436 1, 3437 0, 3438 // COSE n. 3439 CBOR_NEG, 3440 CBOR_BYTES | 25, 3441 // Length is 256 as 16-bit big-endian. 3442 1, 3443 0, 3444 // N. This will be overwritten later. 3445 0, 3446 0, 3447 0, 3448 0, 3449 0, 3450 0, 3451 0, 3452 0, 3453 0, 3454 0, 3455 0, 3456 0, 3457 0, 3458 0, 3459 0, 3460 0, 3461 0, 3462 0, 3463 0, 3464 0, 3465 0, 3466 0, 3467 0, 3468 0, 3469 0, 3470 0, 3471 0, 3472 0, 3473 0, 3474 0, 3475 0, 3476 0, 3477 0, 3478 0, 3479 0, 3480 0, 3481 0, 3482 0, 3483 0, 3484 0, 3485 0, 3486 0, 3487 0, 3488 0, 3489 0, 3490 0, 3491 0, 3492 0, 3493 0, 3494 0, 3495 0, 3496 0, 3497 0, 3498 0, 3499 0, 3500 0, 3501 0, 3502 0, 3503 0, 3504 0, 3505 0, 3506 0, 3507 0, 3508 0, 3509 0, 3510 0, 3511 0, 3512 0, 3513 0, 3514 0, 3515 0, 3516 0, 3517 0, 3518 0, 3519 0, 3520 0, 3521 0, 3522 0, 3523 0, 3524 0, 3525 0, 3526 0, 3527 0, 3528 0, 3529 0, 3530 0, 3531 0, 3532 0, 3533 0, 3534 0, 3535 0, 3536 0, 3537 0, 3538 0, 3539 0, 3540 0, 3541 0, 3542 0, 3543 0, 3544 0, 3545 0, 3546 0, 3547 0, 3548 0, 3549 0, 3550 0, 3551 0, 3552 0, 3553 0, 3554 0, 3555 0, 3556 0, 3557 0, 3558 0, 3559 0, 3560 0, 3561 0, 3562 0, 3563 0, 3564 0, 3565 0, 3566 0, 3567 0, 3568 0, 3569 0, 3570 0, 3571 0, 3572 0, 3573 0, 3574 0, 3575 0, 3576 0, 3577 0, 3578 0, 3579 0, 3580 0, 3581 0, 3582 0, 3583 0, 3584 0, 3585 0, 3586 0, 3587 0, 3588 0, 3589 0, 3590 0, 3591 0, 3592 0, 3593 0, 3594 0, 3595 0, 3596 0, 3597 0, 3598 0, 3599 0, 3600 0, 3601 0, 3602 0, 3603 0, 3604 0, 3605 0, 3606 0, 3607 0, 3608 0, 3609 0, 3610 0, 3611 0, 3612 0, 3613 0, 3614 0, 3615 0, 3616 0, 3617 0, 3618 0, 3619 0, 3620 0, 3621 0, 3622 0, 3623 0, 3624 0, 3625 0, 3626 0, 3627 0, 3628 0, 3629 0, 3630 0, 3631 0, 3632 0, 3633 0, 3634 0, 3635 0, 3636 0, 3637 0, 3638 0, 3639 0, 3640 0, 3641 0, 3642 0, 3643 0, 3644 0, 3645 0, 3646 0, 3647 0, 3648 0, 3649 0, 3650 0, 3651 0, 3652 0, 3653 0, 3654 0, 3655 0, 3656 0, 3657 0, 3658 0, 3659 0, 3660 0, 3661 0, 3662 0, 3663 0, 3664 0, 3665 0, 3666 0, 3667 0, 3668 0, 3669 0, 3670 0, 3671 0, 3672 0, 3673 0, 3674 0, 3675 0, 3676 0, 3677 0, 3678 0, 3679 0, 3680 0, 3681 0, 3682 0, 3683 0, 3684 0, 3685 0, 3686 0, 3687 0, 3688 0, 3689 0, 3690 0, 3691 0, 3692 0, 3693 0, 3694 0, 3695 0, 3696 0, 3697 0, 3698 0, 3699 0, 3700 0, 3701 // COSE e. 3702 CBOR_NEG | 1, 3703 CBOR_BYTES | 3, 3704 // 65537 as 24-bit big-endian. 3705 1, 3706 0, 3707 1, 3708 ] 3709 .as_slice(), 3710 ); 3711 attestation_object[31..63] 3712 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3713 let n = [ 3714 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 3715 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 3716 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 3717 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 3718 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 3719 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 3720 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 3721 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 3722 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 3723 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 3724 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 3725 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 3726 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 3727 153, 79, 0, 133, 78, 7, 218, 165, 241, 3728 ]; 3729 let e = 65537; 3730 let d = [ 3731 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 3732 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 3733 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 3734 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 3735 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 3736 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 3737 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 3738 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 3739 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 3740 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 3741 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 3742 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 3743 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 3744 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 3745 ]; 3746 let p = BigUint::from_slice( 3747 [ 3748 352691927, 1294578443, 816143558, 690659917, 1161596366, 1544791087, 3999549486, 3749 3319149924, 2349250979, 1304689381, 3959753736, 3377900978, 866506027, 1671521644, 3750 3926847564, 898221388, 3448219846, 494454484, 3915534864, 2869735916, 2456511629, 3751 3397234721, 3012775852, 3472309790, 1923617705, 2993441050, 3210302569, 3605331368, 3752 3352563766, 688081007, 4104512503, 4145593376, 3753 ] 3754 .as_slice(), 3755 ); 3756 let p_2 = BigUint::from_slice( 3757 [ 3758 4039514409, 964284038, 3230008587, 3320139220, 3562360334, 3165876926, 212773653, 3759 2752465512, 2973674888, 1717425549, 2084262803, 3585031058, 4162394935, 1428626842, 3760 1015474994, 3283774155, 2840050110, 190639246, 147241978, 2994256073, 4081014755, 3761 3102401369, 3547397148, 1545029057, 895305733, 2689179461, 1593439337, 3960057302, 3762 193068804, 2835123424, 4054880057, 4200258364, 3763 ] 3764 .as_slice(), 3765 ); 3766 let rsa_key = RsaKey::<Sha256>::new( 3767 RsaPrivateKey::from_components( 3768 BigUint::from_bytes_le(n.as_slice()), 3769 e.into(), 3770 BigUint::from_bytes_le(d.as_slice()), 3771 vec![p, p_2], 3772 ) 3773 .unwrap(), 3774 ) 3775 .verifying_key(); 3776 let n = rsa_key.as_ref().n().to_bytes_be(); 3777 attestation_object[113..369].copy_from_slice(n.as_slice()); 3778 assert!(matches!(opts.start_ceremony()?.0.verify( 3779 &rp_id, 3780 id, 3781 &Registration { 3782 response: AuthenticatorAttestation::new( 3783 client_data_json, 3784 attestation_object, 3785 AuthTransports::NONE, 3786 ), 3787 authenticator_attachment: AuthenticatorAttachment::None, 3788 client_extension_results: ClientExtensionsOutputs { 3789 cred_props: None, 3790 prf: None, 3791 }, 3792 }, 3793 &RegistrationVerificationOptions::<&str, &str>::default(), 3794 )?.static_state.credential_public_key, UncompressedPubKey::Rsa(k) if *k.n() == n.as_slice() && k.e() == e)); 3795 Ok(()) 3796 } 3797 #[test] 3798 #[cfg(feature = "custom")] 3799 fn rsa_auth() -> Result<(), AggErr> { 3800 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3801 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 3802 opts.challenge = Challenge(0); 3803 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3804 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 3805 let mut authenticator_data = Vec::with_capacity(69); 3806 authenticator_data.extend_from_slice( 3807 [ 3808 // rpIdHash. 3809 // This will be overwritten later. 3810 0, 3811 0, 3812 0, 3813 0, 3814 0, 3815 0, 3816 0, 3817 0, 3818 0, 3819 0, 3820 0, 3821 0, 3822 0, 3823 0, 3824 0, 3825 0, 3826 0, 3827 0, 3828 0, 3829 0, 3830 0, 3831 0, 3832 0, 3833 0, 3834 0, 3835 0, 3836 0, 3837 0, 3838 0, 3839 0, 3840 0, 3841 0, 3842 // flags. 3843 // UP and UV (right-to-left). 3844 0b0000_0101, 3845 // signCount. 3846 // 0 as 32-bit big-endian. 3847 0, 3848 0, 3849 0, 3850 0, 3851 ] 3852 .as_slice(), 3853 ); 3854 authenticator_data[..32] 3855 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3856 authenticator_data 3857 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 3858 let n = [ 3859 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 3860 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 3861 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 3862 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 3863 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 3864 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 3865 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 3866 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 3867 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 3868 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 3869 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 3870 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 3871 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 3872 153, 79, 0, 133, 78, 7, 218, 165, 241, 3873 ]; 3874 let e = 65537; 3875 let d = [ 3876 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 3877 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 3878 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 3879 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 3880 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 3881 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 3882 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 3883 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 3884 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 3885 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 3886 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 3887 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 3888 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 3889 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 3890 ]; 3891 let p = BigUint::from_slice( 3892 [ 3893 352691927, 1294578443, 816143558, 690659917, 1161596366, 1544791087, 3999549486, 3894 3319149924, 2349250979, 1304689381, 3959753736, 3377900978, 866506027, 1671521644, 3895 3926847564, 898221388, 3448219846, 494454484, 3915534864, 2869735916, 2456511629, 3896 3397234721, 3012775852, 3472309790, 1923617705, 2993441050, 3210302569, 3605331368, 3897 3352563766, 688081007, 4104512503, 4145593376, 3898 ] 3899 .as_slice(), 3900 ); 3901 let p_2 = BigUint::from_slice( 3902 [ 3903 4039514409, 964284038, 3230008587, 3320139220, 3562360334, 3165876926, 212773653, 3904 2752465512, 2973674888, 1717425549, 2084262803, 3585031058, 4162394935, 1428626842, 3905 1015474994, 3283774155, 2840050110, 190639246, 147241978, 2994256073, 4081014755, 3906 3102401369, 3547397148, 1545029057, 895305733, 2689179461, 1593439337, 3960057302, 3907 193068804, 2835123424, 4054880057, 4200258364, 3908 ] 3909 .as_slice(), 3910 ); 3911 let rsa_key = RsaKey::<Sha256>::new( 3912 RsaPrivateKey::from_components( 3913 BigUint::from_bytes_le(n.as_slice()), 3914 e.into(), 3915 BigUint::from_bytes_le(d.as_slice()), 3916 vec![p, p_2], 3917 ) 3918 .unwrap(), 3919 ); 3920 let rsa_pub = rsa_key.verifying_key(); 3921 let sig = rsa_key.sign(authenticator_data.as_slice()).to_vec(); 3922 authenticator_data.truncate(37); 3923 assert!(!opts.start_ceremony()?.0.verify( 3924 &rp_id, 3925 &Authentication { 3926 raw_id: CredentialId::try_from(vec![0; 16])?, 3927 response: AuthenticatorAssertion::with_user( 3928 client_data_json, 3929 authenticator_data, 3930 sig, 3931 UserHandle::from([0]), 3932 ), 3933 authenticator_attachment: AuthenticatorAttachment::None, 3934 }, 3935 &mut AuthenticatedCredential::new( 3936 CredentialId::try_from([0; 16].as_slice())?, 3937 UserHandle::try_from([0].as_slice())?, 3938 StaticState { 3939 credential_public_key: CompressedPubKey::<&[u8], &[u8], &[u8], _>::Rsa( 3940 RsaPubKey::try_from((rsa_pub.as_ref().n().to_bytes_be(), e)).unwrap(), 3941 ), 3942 extensions: AuthenticatorExtensionOutputStaticState { 3943 cred_protect: CredentialProtectionPolicy::None, 3944 hmac_secret: None, 3945 }, 3946 }, 3947 DynamicState { 3948 user_verified: true, 3949 backup: Backup::NotEligible, 3950 sign_count: 0, 3951 authenticator_attachment: AuthenticatorAttachment::None, 3952 }, 3953 )?, 3954 &AuthenticationVerificationOptions::<&str, &str>::default(), 3955 )?); 3956 Ok(()) 3957 } 3958 }