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