request.rs (137759B)
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 rand::Rng as _; 32 use rsa::sha2::{Digest as _, Sha256}; 33 #[cfg(feature = "serializable_server_state")] 34 use std::time::SystemTime; 35 #[cfg(any(doc, not(feature = "serializable_server_state")))] 36 use std::{collections::HashSet, time::Instant}; 37 use url::Url as Uri; 38 /// Contains functionality for beginning the 39 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony). 40 /// 41 /// # Examples 42 /// 43 /// ``` 44 /// # #[cfg(not(feature = "serializable_server_state"))] 45 /// # use webauthn_rp::request::{FixedCapHashSet, InsertResult}; 46 /// # use webauthn_rp::{ 47 /// # request::{ 48 /// # auth::{AllowedCredentials, PublicKeyCredentialRequestOptions}, 49 /// # register::UserHandle, 50 /// # AsciiDomain, Credentials, PublicKeyCredentialDescriptor, RpId, 51 /// # }, 52 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 53 /// # AggErr, 54 /// # }; 55 /// # #[cfg(not(feature = "serializable_server_state"))] 56 /// let mut ceremonies = FixedCapHashSet::new(128); 57 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 58 /// let (server, client) = PublicKeyCredentialRequestOptions::passkey(&rp_id).start_ceremony()?; 59 /// # #[cfg(not(feature = "serializable_server_state"))] 60 /// assert!(matches!( 61 /// ceremonies.insert_or_replace_all_expired(server), 62 /// InsertResult::Success 63 /// )); 64 /// # #[cfg(feature = "serde")] 65 /// assert!(serde_json::to_string(&client).is_ok()); 66 /// let user_handle = get_user_handle(); 67 /// # #[cfg(feature = "custom")] 68 /// let creds = get_registered_credentials((&user_handle).into())?; 69 /// # #[cfg(feature = "custom")] 70 /// let (server_2, client_2) = 71 /// PublicKeyCredentialRequestOptions::second_factor(&rp_id, creds)?.start_ceremony()?; 72 /// # #[cfg(all(feature = "custom", not(feature = "serializable_server_state")))] 73 /// assert!(matches!( 74 /// ceremonies.insert_or_replace_all_expired(server_2), 75 /// InsertResult::Success 76 /// )); 77 /// # #[cfg(all(feature = "custom", feature = "serde"))] 78 /// assert!(serde_json::to_string(&client_2).is_ok()); 79 /// /// Extract `UserHandle` from session cookie. 80 /// fn get_user_handle() -> UserHandle<Vec<u8>> { 81 /// // ⋮ 82 /// # UserHandle::new() 83 /// } 84 /// # #[cfg(feature = "custom")] 85 /// /// Fetch the `AllowedCredentials` associated with `user`. 86 /// fn get_registered_credentials(user: UserHandle<&[u8]>) -> Result<AllowedCredentials, AggErr> { 87 /// // ⋮ 88 /// # let mut creds = AllowedCredentials::new(); 89 /// # creds.push( 90 /// # PublicKeyCredentialDescriptor { 91 /// # id: CredentialId::try_from(vec![0; CRED_ID_MIN_LEN])?, 92 /// # transports: AuthTransports::NONE, 93 /// # } 94 /// # .into(), 95 /// # ); 96 /// # Ok(creds) 97 /// } 98 /// # Ok::<_, AggErr>(()) 99 /// ``` 100 pub mod auth; 101 /// Contains error types. 102 pub mod error; 103 /// Contains functionality for beginning the 104 /// [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony). 105 /// 106 /// # Examples 107 /// 108 /// ``` 109 /// # #[cfg(not(feature = "serializable_server_state"))] 110 /// # use webauthn_rp::request::{FixedCapHashSet, InsertResult}; 111 /// # use webauthn_rp::{ 112 /// # request::{ 113 /// # register::{ 114 /// # PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle, 115 /// # }, 116 /// # AsciiDomain, PublicKeyCredentialDescriptor, RpId 117 /// # }, 118 /// # response::{AuthTransports, CredentialId, CRED_ID_MIN_LEN}, 119 /// # AggErr, 120 /// # }; 121 /// # #[cfg(not(feature = "serializable_server_state"))] 122 /// let mut ceremonies = FixedCapHashSet::new(128); 123 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 124 /// let user_handle = get_user_handle(); 125 /// let handle = (&user_handle).into(); 126 /// let user = get_user_entity(handle)?; 127 /// let creds = get_registered_credentials(handle)?; 128 /// let (server, client) = PublicKeyCredentialCreationOptions::passkey(&rp_id, user.clone(), creds) 129 /// .start_ceremony()?; 130 /// # #[cfg(not(feature = "serializable_server_state"))] 131 /// assert!(matches!( 132 /// ceremonies.insert_or_replace_all_expired(server), 133 /// InsertResult::Success 134 /// )); 135 /// # #[cfg(feature = "serde")] 136 /// assert!(serde_json::to_string(&client).is_ok()); 137 /// let creds_2 = get_registered_credentials(handle)?; 138 /// let (server_2, client_2) = 139 /// PublicKeyCredentialCreationOptions::second_factor(&rp_id, user, creds_2).start_ceremony()?; 140 /// # #[cfg(not(feature = "serializable_server_state"))] 141 /// assert!(matches!( 142 /// ceremonies.insert_or_replace_all_expired(server_2), 143 /// InsertResult::Success 144 /// )); 145 /// # #[cfg(feature = "serde")] 146 /// assert!(serde_json::to_string(&client_2).is_ok()); 147 /// /// Extract `UserHandle` from session cookie if this is not the first credential registered. 148 /// fn get_user_handle() -> UserHandle<Vec<u8>> { 149 /// // ⋮ 150 /// # UserHandle::new() 151 /// } 152 /// /// Fetch `PublicKeyCredentialUserEntity` info associated with `user`. 153 /// /// 154 /// /// If this is the first time a credential is being registered, then `PublicKeyCredentialUserEntity` 155 /// /// will need to be constructed with `name` and `display_name` passed from the client and `UserHandle::new` 156 /// /// used for `id`. Once created, this info can be stored such that the entity information 157 /// /// does not need to be requested for subsequent registrations. 158 /// fn get_user_entity(user: UserHandle<&[u8]>) -> Result<PublicKeyCredentialUserEntity<&[u8]>, AggErr> { 159 /// // ⋮ 160 /// # Ok(PublicKeyCredentialUserEntity { 161 /// # name: "foo".try_into()?, 162 /// # id: user, 163 /// # display_name: None, 164 /// # }) 165 /// } 166 /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`. 167 /// /// 168 /// /// This doesn't need to be called when this is the first credential registered for `user`; instead 169 /// /// an empty `Vec` should be passed. 170 /// fn get_registered_credentials( 171 /// user: UserHandle<&[u8]>, 172 /// ) -> Result<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>, AggErr> { 173 /// // ⋮ 174 /// # Ok(Vec::new()) 175 /// } 176 /// # Ok::<_, AggErr>(()) 177 /// ``` 178 pub mod register; 179 /// Contains functionality to serialize data to a client. 180 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 181 #[cfg(feature = "serde")] 182 mod ser; 183 /// Contains functionality to (de)serialize data needed for [`RegistrationServerState`] and 184 /// [`AuthenticationServerState`] to a data store. 185 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 186 #[cfg(feature = "serializable_server_state")] 187 pub(super) mod ser_server_state; 188 // `Challenge` must _never_ be constructable directly or indirectly; thus its tuple field must always be private, 189 // and it must never implement `trait`s (e.g., `Clone`) that would allow indirect creation. It must only ever 190 // be constructed via `Self::new` or `Self::default`. In contrast downstream code must be able to construct 191 // `SentChallenge` since it is used during ceremony validation; thus we must keep `Challenge` and `SentChallenge` 192 // as separate types. 193 /// [Cryptographic challenge](https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges). 194 #[derive(Debug)] 195 pub struct Challenge(u128); 196 impl Challenge { 197 // This won't `panic` since `16 < 0x4000`. 198 /// The number of bytes a `Challenge` takes to encode in base64url. 199 pub(super) const BASE64_LEN: usize = super::base64url_nopad_len(16); 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::thread_rng().r#gen()) 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(unsafe_code, reason = "we want a const, and this is completely safe")] 1389 pub(super) const THREE_HUNDRED_THOUSAND: NonZeroU32 = { 1390 // SAFETY: 1391 // `300_000 > 0`. 1392 // Compilation always fails when using a constant value of 0, 1393 // so there is _nothing_ wrong with this use of `unsafe`. 1394 unsafe { NonZeroU32::new_unchecked(300_000) } 1395 }; 1396 /// [`Hasher`] whose `write_*` methods simply store up to 64 bits of the passed argument _as is_ overwriting 1397 /// any previous state. 1398 /// 1399 /// This is designed to only be used indirectly via a hash map whose keys are randomly generated on the server 1400 /// based on at least 64 bits—the size of the integer returned from [`Self::finish`]—of entropy. 1401 /// This makes this `Hasher` usable (and ideal) in only the most niche circumstances. 1402 /// 1403 /// [`RegistrationServerState`] and [`AuthenticationServerState`] both implement [`Hash`] by simply writing the 1404 /// contained [`Challenge`]; thus when they are stored in a hashed collection (e.g., [`FixedCapHashSet`]), one can 1405 /// optimize without fear by using this `Hasher` since `Challenge`s are immutable and can only ever be created on 1406 /// the server via [`Challenge::new`] (and equivalently [`Challenge::default`]). `RegistrationServerState` and 1407 /// `AuthenticationServerState` are also immutable and only constructable via 1408 /// [`PublicKeyCredentialCreationOptions::start_ceremony`] and 1409 /// [`PublicKeyCredentialRequestOptions::start_ceremony`] respectively. Since `Challenge` is already based on 1410 /// a random `u128`, other `Hasher`s will be slower and likely produce lower-quality hashes (and never 1411 /// higher quality). 1412 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1413 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1414 #[derive(Debug)] 1415 pub struct IdentityHasher(u64); 1416 // Note it is _not_ required for `write_*` methods to do the same thing as other `write_*` methods 1417 // (e.g., `Self::write_u64` may not be the same thing as 8 calls to `Self::write_u8`). 1418 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1419 impl Hasher for IdentityHasher { 1420 /// Returns `0` if no `write_*` calls have been made; otherwise returns the result of the most recent 1421 /// `write_*` call. 1422 #[inline] 1423 fn finish(&self) -> u64 { 1424 self.0 1425 } 1426 /// Writes `i` to `self`. 1427 #[inline] 1428 fn write_u64(&mut self, i: u64) { 1429 self.0 = i; 1430 } 1431 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1432 #[expect( 1433 clippy::as_conversions, 1434 clippy::cast_sign_loss, 1435 reason = "we simply need to convert into a u64 in a deterministic way" 1436 )] 1437 #[inline] 1438 fn write_i8(&mut self, i: i8) { 1439 self.write_u64(i as u64); 1440 } 1441 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1442 #[expect( 1443 clippy::as_conversions, 1444 clippy::cast_sign_loss, 1445 reason = "we simply need to convert into a u64 in a deterministic way" 1446 )] 1447 #[inline] 1448 fn write_i16(&mut self, i: i16) { 1449 self.write_u64(i as u64); 1450 } 1451 /// Sign-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1452 #[expect( 1453 clippy::as_conversions, 1454 clippy::cast_sign_loss, 1455 reason = "we simply need to convert into a u64 in a deterministic way" 1456 )] 1457 #[inline] 1458 fn write_i32(&mut self, i: i32) { 1459 self.write_u64(i as u64); 1460 } 1461 /// Redirects to [`Self::write_u64`]. 1462 #[expect( 1463 clippy::as_conversions, 1464 clippy::cast_sign_loss, 1465 reason = "we simply need to convert into a u64 in a deterministic way" 1466 )] 1467 #[inline] 1468 fn write_i64(&mut self, i: i64) { 1469 self.write_u64(i as u64); 1470 } 1471 /// Truncates `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1472 #[expect( 1473 clippy::as_conversions, 1474 clippy::cast_possible_truncation, 1475 clippy::cast_sign_loss, 1476 reason = "we simply need to convert into a u64 in a deterministic way" 1477 )] 1478 #[inline] 1479 fn write_i128(&mut self, i: i128) { 1480 self.write_u64(i as u64); 1481 } 1482 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1483 #[inline] 1484 fn write_u8(&mut self, i: u8) { 1485 self.write_u64(u64::from(i)); 1486 } 1487 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1488 #[inline] 1489 fn write_u16(&mut self, i: u16) { 1490 self.write_u64(u64::from(i)); 1491 } 1492 /// Zero-extends `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1493 #[inline] 1494 fn write_u32(&mut self, i: u32) { 1495 self.write_u64(u64::from(i)); 1496 } 1497 /// Truncates `i` to a [`u64`] before redirecting to [`Self::write_u64`]. 1498 #[expect( 1499 clippy::as_conversions, 1500 clippy::cast_possible_truncation, 1501 reason = "we simply need to convert into a u64 in a deterministic way" 1502 )] 1503 #[inline] 1504 fn write_u128(&mut self, i: u128) { 1505 self.write_u64(i as u64); 1506 } 1507 /// This does nothing iff `bytes.len() < 8`; otherwise the first 8 bytes are converted 1508 /// to a [`u64`] that is written via [`Self::write_u64`]; 1509 #[expect(clippy::host_endian_bytes, reason = "endianness does not matter")] 1510 #[inline] 1511 fn write(&mut self, bytes: &[u8]) { 1512 if let Some(data) = bytes.get(..8) { 1513 let mut val = [0; 8]; 1514 val.copy_from_slice(data); 1515 self.write_u64(u64::from_ne_bytes(val)); 1516 } 1517 } 1518 } 1519 /// [`BuildHasher`] of an [`IdentityHasher`]. 1520 /// 1521 /// This MUST only be used with hash maps with keys that are randomly generated on the server based on at least 64 1522 /// bits of entropy. 1523 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1524 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1525 #[derive(Clone, Copy, Debug)] 1526 pub struct BuildIdentityHasher; 1527 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1528 #[cfg(not(feature = "serializable_server_state"))] 1529 impl BuildHasher for BuildIdentityHasher { 1530 type Hasher = IdentityHasher; 1531 #[inline] 1532 fn build_hasher(&self) -> Self::Hasher { 1533 IdentityHasher(0) 1534 } 1535 } 1536 /// Prevent users from implementing [`ServerState`]. 1537 mod private { 1538 /// Marker trait used as a supertrait of `ServerState`. 1539 pub trait Sealed {} 1540 impl Sealed for super::AuthenticationServerState {} 1541 impl Sealed for super::RegistrationServerState {} 1542 } 1543 /// Subset of data shared by both [`RegistrationServerState`] and [`AuthenticationServerState`]. 1544 /// 1545 /// This trait is sealed and cannot be implemented for types outside of `webauthn_rp`. 1546 pub trait ServerState: private::Sealed { 1547 /// Returns the `Instant` the ceremony expires. 1548 /// 1549 /// Note when `serializable_server_state` is enabled, [`SystemTime`] is returned instead. 1550 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1551 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1552 fn expiration(&self) -> Instant; 1553 /// Returns the `SystemTime` the ceremony expires. 1554 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1555 fn expiration(&self) -> SystemTime; 1556 /// Returns the `SentChallenge` associated with the ceremony. 1557 fn sent_challenge(&self) -> SentChallenge; 1558 } 1559 /// Fixed-capacity hash set that only inserts items when there is available capacity. 1560 /// 1561 /// This should only be used if _both_ of the following conditions are met: 1562 /// 1563 /// * Application is already protected from memory exhaustion attacks (e.g., users must be connected 1564 /// via VPN). 1565 /// * There are legitimate reasons for an in-memory collection to grow unbounded. 1566 /// 1567 /// The first point is necessary; otherwise an attacker could trivially slow down the application 1568 /// by causing repeated inserts forcing repeated calls to functions like [`Self::retain`]. The second 1569 /// point is necessary; otherwise any in-memory collection would suffice. 1570 /// 1571 /// When `T` is a [`ServerState`], there are legitimate reasons why a ceremony will never finish (e.g., 1572 /// an outage could kill a user's connection after starting a ceremony). The longer the application 1573 /// runs the more such instances occur to the point where the in-memory collection is full of expired 1574 /// ceremonies. Since this should rarely occur and as long as [`Self::capacity`] is appropriate, 1575 /// [`Self::insert`] should almost always succeed; however very rarely there will be a point when 1576 /// one will have to [`Self::remove_expired_ceremonies`]. A vast majority of the time a user 1577 /// will complete the ceremony which requires ownership of the `ServerState` which in turn requires 1578 /// [`Self::take`] which will add an available slot. 1579 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1580 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1581 #[derive(Debug)] 1582 pub struct FixedCapHashSet<T, S = BuildIdentityHasher>(HashSet<T, S>); 1583 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1584 impl<T> FixedCapHashSet<T, BuildIdentityHasher> { 1585 /// Creates an empty `FixedCapHashSet` with at least the specified capacity. 1586 /// 1587 /// The hash set will be able to hold at least `capacity` elements without reallocating. This method is allowed 1588 /// to allocate for more elements than `capacity`. 1589 #[inline] 1590 #[must_use] 1591 pub fn new(capacity: usize) -> Self { 1592 Self(HashSet::with_capacity_and_hasher( 1593 capacity, 1594 BuildIdentityHasher, 1595 )) 1596 } 1597 } 1598 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1599 impl<T, S> FixedCapHashSet<T, S> { 1600 /// Creates an empty `FixedCapHashSet` with at least the specified capacity, using `hasher` to hash the keys. 1601 /// 1602 /// The hash set will be able to hold at least `capacity` elements without reallocating. This method is allowed 1603 /// to allocate for more elements than `capacity`. 1604 #[inline] 1605 #[must_use] 1606 pub fn new_with_hasher(capacity: usize, hasher: S) -> Self { 1607 Self(HashSet::with_capacity_and_hasher(capacity, hasher)) 1608 } 1609 /// Returns the immutable capacity. 1610 /// 1611 /// This number is a lower bound; the `FixedCapHashSet` might be able to hold more, but is guaranteed to be 1612 /// able to hold at least this many. 1613 #[inline] 1614 #[must_use] 1615 pub fn capacity(&self) -> usize { 1616 self.0.capacity() 1617 } 1618 /// Clears the set, removing all values. 1619 #[inline] 1620 pub fn clear(&mut self) { 1621 self.0.clear(); 1622 } 1623 /// Returns the number of elements in the set. 1624 #[inline] 1625 #[must_use] 1626 pub fn len(&self) -> usize { 1627 self.0.len() 1628 } 1629 /// Returns `true` iff the set contains no elements. 1630 #[inline] 1631 #[must_use] 1632 pub fn is_empty(&self) -> bool { 1633 self.0.is_empty() 1634 } 1635 /// Retains only the elements specified by the predicate. 1636 /// 1637 /// In other words, remove all elements `e` for which `f(&e)` returns `false`. The elements are visited in 1638 /// unsorted (and unspecified) order. 1639 #[inline] 1640 pub fn retain<F>(&mut self, f: F) 1641 where 1642 F: FnMut(&T) -> bool, 1643 { 1644 self.0.retain(f); 1645 } 1646 } 1647 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1648 impl<T: ServerState, S> FixedCapHashSet<T, S> { 1649 /// Removes all expired ceremonies. 1650 #[inline] 1651 pub fn remove_expired_ceremonies(&mut self) { 1652 // Even though it's more accurate to check the current `Instant` for each ceremony, we elect to capture 1653 // the `Instant` we begin iteration for performance reasons. It's unlikely an appreciable amount of 1654 // additional ceremonies would be removed. 1655 let now = Instant::now(); 1656 self.retain(|v| v.expiration() >= now); 1657 } 1658 } 1659 /// Result returned from [`FixedCapHashSet::insert`], [`FixedCapHashSet::insert_or_replace_expired`], and 1660 /// [`FixedCapHashSet::insert_or_replace_all_expired`]. 1661 #[cfg_attr(docsrs, doc(cfg(not(feature = "serializable_server_state"))))] 1662 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1663 #[derive(Clone, Copy, Debug)] 1664 pub enum InsertResult { 1665 /// Value was successfully inserted. 1666 Success, 1667 /// Value was not inserted since the capacity was full. 1668 CapacityFull, 1669 /// Value was not inserted since the value already existed. 1670 /// 1671 /// When the keys are based on [`Challenge`]s, this should almost never occur since `Challenge`s 1672 /// are 16 bytes of random data. 1673 Duplicate, 1674 } 1675 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1676 impl<T: Eq + Hash, S: BuildHasher> FixedCapHashSet<T, S> { 1677 /// Returns `true` iff the set contains a value. 1678 /// 1679 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1680 /// match those for the value type. 1681 #[inline] 1682 #[must_use] 1683 pub fn contains<Q>(&self, value: &Q) -> bool 1684 where 1685 T: Borrow<Q>, 1686 Q: Eq + Hash + ?Sized, 1687 { 1688 self.0.contains(value) 1689 } 1690 /// Returns a reference to the value in the set, if any, that is equal to the given value. 1691 /// 1692 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1693 /// match those for the value type. 1694 #[inline] 1695 #[must_use] 1696 pub fn get<Q>(&self, value: &Q) -> Option<&T> 1697 where 1698 T: Borrow<Q>, 1699 Q: Eq + Hash + ?Sized, 1700 { 1701 self.0.get(value) 1702 } 1703 /// Removes a value from the set. Returns whether the value was present in the set. 1704 /// 1705 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1706 /// match those for the value type. 1707 #[inline] 1708 pub fn remove<Q>(&mut self, value: &Q) -> bool 1709 where 1710 T: Borrow<Q>, 1711 Q: Eq + Hash + ?Sized, 1712 { 1713 self.0.remove(value) 1714 } 1715 /// Removes and returns the value in the set, if any, that is equal to the given one. 1716 /// 1717 /// The value may be any borrowed form of the set's value type, but `Hash` and `Eq` on the borrowed form _must_ 1718 /// match those for the value type. 1719 #[inline] 1720 pub fn take<Q>(&mut self, value: &Q) -> Option<T> 1721 where 1722 T: Borrow<Q>, 1723 Q: Eq + Hash + ?Sized, 1724 { 1725 self.0.take(value) 1726 } 1727 /// Adds `value` to the set iff [`Self::capacity`] `>` [`Self::len`]. 1728 #[inline] 1729 pub fn insert(&mut self, value: T) -> InsertResult { 1730 if self.len() == self.capacity() { 1731 InsertResult::CapacityFull 1732 } else if self.0.insert(value) { 1733 InsertResult::Success 1734 } else { 1735 InsertResult::Duplicate 1736 } 1737 } 1738 } 1739 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1740 impl<T: Borrow<SentChallenge> + Eq + Hash + ServerState, S: BuildHasher> FixedCapHashSet<T, S> { 1741 /// Adds a ceremony to the set. 1742 /// 1743 /// This will only insert `value` iff [`Self::capacity`] `>` [`Self::len`]. When [`Self::len`] `==` 1744 /// [`Self::capacity`], this will iterate items in the set until an expired ceremony is encountered; at which 1745 /// point, the expired ceremony will be removed before `value` is inserted. In the event no expired ceremonies 1746 /// exist, [`InsertResult::CapacityFull`] will be returned. 1747 /// 1748 /// If one wants to avoid the potentially expensive operation of iterating the set for an expired ceremony, 1749 /// call [`Self::insert`]. Alternatively one can call [`Self::insert_or_replace_all_expired`] to avoid 1750 /// repeatedly iterating the hash set once its capacity is full. 1751 #[inline] 1752 pub fn insert_or_replace_expired(&mut self, value: T) -> InsertResult { 1753 if self.len() == self.capacity() { 1754 let now = Instant::now(); 1755 self.0 1756 .iter() 1757 .try_fold((), |(), v| { 1758 if v.expiration() < now { 1759 Err(v.sent_challenge()) 1760 } else { 1761 Ok(()) 1762 } 1763 }) 1764 .map_or_else( 1765 |chall| { 1766 self.remove(&chall); 1767 if self.0.insert(value) { 1768 InsertResult::Success 1769 } else { 1770 InsertResult::Duplicate 1771 } 1772 }, 1773 |()| InsertResult::CapacityFull, 1774 ) 1775 } else if self.0.insert(value) { 1776 InsertResult::Success 1777 } else { 1778 InsertResult::Duplicate 1779 } 1780 } 1781 /// Adds a ceremony to the set. 1782 /// 1783 /// This will only insert `value` iff [`Self::capacity`] `>` [`Self::len`]. When [`Self::len`] `==` 1784 /// [`Self::capacity`], this will [`Self::remove_expired_ceremonies`] before `value` is inserted. In the event 1785 /// no expired ceremonies exist, [`InsertResult::CapacityFull`] will be returned. 1786 #[inline] 1787 pub fn insert_or_replace_all_expired(&mut self, value: T) -> InsertResult { 1788 if self.len() == self.capacity() { 1789 self.remove_expired_ceremonies(); 1790 } 1791 if self.len() == self.capacity() { 1792 InsertResult::CapacityFull 1793 } else if self.0.insert(value) { 1794 InsertResult::Success 1795 } else { 1796 InsertResult::Duplicate 1797 } 1798 } 1799 } 1800 #[cfg(test)] 1801 mod tests { 1802 #[cfg(feature = "custom")] 1803 use super::{ 1804 super::{ 1805 AggErr, AuthenticatedCredential, 1806 response::{ 1807 AuthTransports, AuthenticatorAttachment, Backup, CredentialId, 1808 auth::{Authentication, AuthenticatorAssertion}, 1809 register::{ 1810 AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation, 1811 AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputs, 1812 CompressedP256PubKey, CompressedP384PubKey, CompressedPubKey, 1813 CredentialProtectionPolicy, DynamicState, Ed25519PubKey, Registration, 1814 RsaPubKey, StaticState, UncompressedPubKey, 1815 }, 1816 }, 1817 }, 1818 AsciiDomain, Challenge, Credentials, ExtensionInfo, ExtensionReq, 1819 PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, 1820 auth::{ 1821 AllowedCredential, AllowedCredentials, AuthenticationVerificationOptions, 1822 CredentialSpecificExtension, Extension as AuthExt, PrfInputOwned, 1823 PublicKeyCredentialRequestOptions, 1824 }, 1825 register::{ 1826 CredProtect, Extension as RegExt, PublicKeyCredentialCreationOptions, 1827 PublicKeyCredentialUserEntity, RegistrationVerificationOptions, UserHandle, 1828 }, 1829 }; 1830 #[cfg(feature = "custom")] 1831 use ed25519_dalek::{Signer, SigningKey}; 1832 #[cfg(feature = "custom")] 1833 use p256::{ 1834 ecdsa::{DerSignature as P256DerSig, SigningKey as P256Key}, 1835 elliptic_curve::sec1::Tag, 1836 }; 1837 #[cfg(feature = "custom")] 1838 use p384::ecdsa::{DerSignature as P384DerSig, SigningKey as P384Key}; 1839 #[cfg(feature = "custom")] 1840 use rsa::{ 1841 BigUint, RsaPrivateKey, 1842 pkcs1v15::SigningKey as RsaKey, 1843 sha2::{Digest, Sha256}, 1844 signature::{Keypair, SignatureEncoding}, 1845 traits::PublicKeyParts, 1846 }; 1847 #[cfg(feature = "custom")] 1848 const CBOR_UINT: u8 = 0b000_00000; 1849 #[cfg(feature = "custom")] 1850 const CBOR_NEG: u8 = 0b001_00000; 1851 #[cfg(feature = "custom")] 1852 const CBOR_BYTES: u8 = 0b010_00000; 1853 #[cfg(feature = "custom")] 1854 const CBOR_TEXT: u8 = 0b011_00000; 1855 #[cfg(feature = "custom")] 1856 const CBOR_MAP: u8 = 0b101_00000; 1857 #[cfg(feature = "custom")] 1858 const CBOR_SIMPLE: u8 = 0b111_00000; 1859 #[cfg(feature = "custom")] 1860 const CBOR_TRUE: u8 = CBOR_SIMPLE | 21; 1861 #[test] 1862 #[cfg(feature = "custom")] 1863 fn ed25519_reg() -> Result<(), AggErr> { 1864 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 1865 let id = UserHandle::try_from([0; 1].as_slice())?; 1866 let mut opts = PublicKeyCredentialCreationOptions::passkey( 1867 &rp_id, 1868 PublicKeyCredentialUserEntity { 1869 name: "foo".try_into()?, 1870 id, 1871 display_name: None, 1872 }, 1873 Vec::new(), 1874 ); 1875 opts.challenge = Challenge(0); 1876 opts.extensions = RegExt { 1877 cred_props: None, 1878 cred_protect: CredProtect::UserVerificationRequired(ExtensionInfo::RequireEnforceValue), 1879 min_pin_length: Some((10, ExtensionInfo::RequireEnforceValue)), 1880 prf: Some(ExtensionInfo::RequireEnforceValue), 1881 }; 1882 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 1883 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 1884 let mut attestation_object = Vec::new(); 1885 attestation_object.extend_from_slice( 1886 [ 1887 CBOR_MAP | 3, 1888 CBOR_TEXT | 3, 1889 b'f', 1890 b'm', 1891 b't', 1892 CBOR_TEXT | 6, 1893 b'p', 1894 b'a', 1895 b'c', 1896 b'k', 1897 b'e', 1898 b'd', 1899 CBOR_TEXT | 7, 1900 b'a', 1901 b't', 1902 b't', 1903 b'S', 1904 b't', 1905 b'm', 1906 b't', 1907 CBOR_MAP | 2, 1908 CBOR_TEXT | 3, 1909 b'a', 1910 b'l', 1911 b'g', 1912 // COSE EdDSA. 1913 CBOR_NEG | 7, 1914 CBOR_TEXT | 3, 1915 b's', 1916 b'i', 1917 b'g', 1918 CBOR_BYTES | 24, 1919 64, 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 0, 1983 0, 1984 CBOR_TEXT | 8, 1985 b'a', 1986 b'u', 1987 b't', 1988 b'h', 1989 b'D', 1990 b'a', 1991 b't', 1992 b'a', 1993 CBOR_BYTES | 24, 1994 // Length is 154. 1995 154, 1996 // RP ID HASH. 1997 // This will be overwritten later. 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 0, 2029 0, 2030 // FLAGS. 2031 // UP, UV, AT, and ED (right-to-left). 2032 0b1100_0101, 2033 // COUNTER. 2034 // 0 as 32-bit big endian. 2035 0, 2036 0, 2037 0, 2038 0, 2039 // AAGUID. 2040 0, 2041 0, 2042 0, 2043 0, 2044 0, 2045 0, 2046 0, 2047 0, 2048 0, 2049 0, 2050 0, 2051 0, 2052 0, 2053 0, 2054 0, 2055 0, 2056 // L. 2057 // CREDENTIAL ID length is 16 as 16-bit big endian. 2058 0, 2059 16, 2060 // CREDENTIAL ID. 2061 0, 2062 0, 2063 0, 2064 0, 2065 0, 2066 0, 2067 0, 2068 0, 2069 0, 2070 0, 2071 0, 2072 0, 2073 0, 2074 0, 2075 0, 2076 0, 2077 CBOR_MAP | 4, 2078 // COSE kty. 2079 CBOR_UINT | 1, 2080 // COSE OKP. 2081 CBOR_UINT | 1, 2082 // COSE alg. 2083 CBOR_UINT | 3, 2084 // COSE EdDSA. 2085 CBOR_NEG | 7, 2086 // COSE OKP crv. 2087 CBOR_NEG, 2088 // COSE Ed25519. 2089 CBOR_UINT | 6, 2090 // COSE OKP x. 2091 CBOR_NEG | 1, 2092 CBOR_BYTES | 24, 2093 // Length is 32. 2094 32, 2095 // Compressed-y coordinate. 2096 // This will be overwritten later. 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 0, 2128 0, 2129 CBOR_MAP | 3, 2130 CBOR_TEXT | 11, 2131 b'c', 2132 b'r', 2133 b'e', 2134 b'd', 2135 b'P', 2136 b'r', 2137 b'o', 2138 b't', 2139 b'e', 2140 b'c', 2141 b't', 2142 // userVerificationRequired. 2143 CBOR_UINT | 3, 2144 // CBOR text of length 11. 2145 CBOR_TEXT | 11, 2146 b'h', 2147 b'm', 2148 b'a', 2149 b'c', 2150 b'-', 2151 b's', 2152 b'e', 2153 b'c', 2154 b'r', 2155 b'e', 2156 b't', 2157 CBOR_TRUE, 2158 CBOR_TEXT | 12, 2159 b'm', 2160 b'i', 2161 b'n', 2162 b'P', 2163 b'i', 2164 b'n', 2165 b'L', 2166 b'e', 2167 b'n', 2168 b'g', 2169 b't', 2170 b'h', 2171 CBOR_UINT | 16, 2172 ] 2173 .as_slice(), 2174 ); 2175 attestation_object 2176 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2177 let sig_key = SigningKey::from_bytes(&[0; 32]); 2178 let ver_key = sig_key.verifying_key(); 2179 let pub_key = ver_key.as_bytes(); 2180 attestation_object[107..139] 2181 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2182 attestation_object[188..220].copy_from_slice(pub_key); 2183 let sig = sig_key.sign(&attestation_object[107..]); 2184 attestation_object[32..96].copy_from_slice(sig.to_bytes().as_slice()); 2185 attestation_object.truncate(261); 2186 assert!(matches!(opts.start_ceremony()?.0.verify( 2187 &rp_id, 2188 id, 2189 &Registration { 2190 response: AuthenticatorAttestation::new( 2191 client_data_json, 2192 attestation_object, 2193 AuthTransports::NONE, 2194 ), 2195 authenticator_attachment: AuthenticatorAttachment::None, 2196 client_extension_results: ClientExtensionsOutputs { 2197 cred_props: None, 2198 prf: Some(AuthenticationExtensionsPrfOutputs { enabled: true, }), 2199 }, 2200 }, 2201 &RegistrationVerificationOptions::<&str, &str>::default(), 2202 )?.static_state.credential_public_key, UncompressedPubKey::Ed25519(k) if k.into_inner() == pub_key)); 2203 Ok(()) 2204 } 2205 #[test] 2206 #[cfg(feature = "custom")] 2207 fn ed25519_auth() -> Result<(), AggErr> { 2208 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2209 let mut creds = AllowedCredentials::with_capacity(1); 2210 creds.push(AllowedCredential { 2211 credential: PublicKeyCredentialDescriptor { 2212 id: CredentialId::try_from(vec![0; 16])?, 2213 transports: AuthTransports::NONE, 2214 }, 2215 extension: CredentialSpecificExtension { 2216 prf: Some(PrfInputOwned { 2217 first: Vec::new(), 2218 second: Some(Vec::new()), 2219 ext_info: ExtensionReq::Require, 2220 }), 2221 }, 2222 }); 2223 let mut opts = PublicKeyCredentialRequestOptions::second_factor(&rp_id, creds)?; 2224 opts.user_verification = UserVerificationRequirement::Required; 2225 opts.challenge = Challenge(0); 2226 opts.extensions = AuthExt { prf: None }; 2227 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2228 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 2229 let mut authenticator_data = Vec::with_capacity(164); 2230 authenticator_data.extend_from_slice( 2231 [ 2232 // rpIdHash. 2233 // This will be overwritten later. 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 0, 2265 0, 2266 // flags. 2267 // UP, UV, and ED (right-to-left). 2268 0b1000_0101, 2269 // signCount. 2270 // 0 as 32-bit big endian. 2271 0, 2272 0, 2273 0, 2274 0, 2275 CBOR_MAP | 1, 2276 CBOR_TEXT | 11, 2277 b'h', 2278 b'm', 2279 b'a', 2280 b'c', 2281 b'-', 2282 b's', 2283 b'e', 2284 b'c', 2285 b'r', 2286 b'e', 2287 b't', 2288 CBOR_BYTES | 24, 2289 // Length is 80. 2290 80, 2291 // Two HMAC outputs concatenated and encrypted. 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 0, 2371 0, 2372 ] 2373 .as_slice(), 2374 ); 2375 authenticator_data[..32] 2376 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2377 authenticator_data 2378 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2379 let ed_priv = SigningKey::from([0; 32]); 2380 let sig = ed_priv.sign(authenticator_data.as_slice()).to_vec(); 2381 authenticator_data.truncate(132); 2382 assert!(!opts.start_ceremony()?.0.verify( 2383 &rp_id, 2384 &Authentication { 2385 raw_id: CredentialId::try_from(vec![0; 16])?, 2386 response: AuthenticatorAssertion::new( 2387 client_data_json, 2388 authenticator_data, 2389 sig, 2390 Some(UserHandle::try_from(vec![0])?), 2391 ), 2392 authenticator_attachment: AuthenticatorAttachment::None, 2393 }, 2394 &mut AuthenticatedCredential::new( 2395 CredentialId::try_from([0; 16].as_slice())?, 2396 UserHandle::try_from([0].as_slice())?, 2397 StaticState { 2398 credential_public_key: CompressedPubKey::<_, &[u8], &[u8], &[u8]>::Ed25519( 2399 Ed25519PubKey::from(ed_priv.verifying_key().to_bytes()), 2400 ), 2401 extensions: AuthenticatorExtensionOutputStaticState { 2402 cred_protect: CredentialProtectionPolicy::None, 2403 hmac_secret: Some(true), 2404 }, 2405 }, 2406 DynamicState { 2407 user_verified: true, 2408 backup: Backup::NotEligible, 2409 sign_count: 0, 2410 authenticator_attachment: AuthenticatorAttachment::None, 2411 }, 2412 )?, 2413 &AuthenticationVerificationOptions::<&str, &str>::default(), 2414 )?); 2415 Ok(()) 2416 } 2417 #[test] 2418 #[cfg(feature = "custom")] 2419 fn p256_reg() -> Result<(), AggErr> { 2420 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2421 let id = UserHandle::try_from([0; 1].as_slice())?; 2422 let mut opts = PublicKeyCredentialCreationOptions::passkey( 2423 &rp_id, 2424 PublicKeyCredentialUserEntity { 2425 name: "foo".try_into()?, 2426 id, 2427 display_name: None, 2428 }, 2429 Vec::new(), 2430 ); 2431 opts.challenge = Challenge(0); 2432 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2433 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 2434 let mut attestation_object = Vec::with_capacity(210); 2435 attestation_object.extend_from_slice( 2436 [ 2437 CBOR_MAP | 3, 2438 CBOR_TEXT | 3, 2439 b'f', 2440 b'm', 2441 b't', 2442 CBOR_TEXT | 4, 2443 b'n', 2444 b'o', 2445 b'n', 2446 b'e', 2447 CBOR_TEXT | 7, 2448 b'a', 2449 b't', 2450 b't', 2451 b'S', 2452 b't', 2453 b'm', 2454 b't', 2455 CBOR_MAP, 2456 CBOR_TEXT | 8, 2457 b'a', 2458 b'u', 2459 b't', 2460 b'h', 2461 b'D', 2462 b'a', 2463 b't', 2464 b'a', 2465 CBOR_BYTES | 24, 2466 // Length is 148. 2467 148, 2468 // RP ID HASH. 2469 // This will be overwritten later. 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 0, 2501 0, 2502 // FLAGS. 2503 // UP, UV, and AT (right-to-left). 2504 0b0100_0101, 2505 // COUNTER. 2506 // 0 as 32-bit big endian. 2507 0, 2508 0, 2509 0, 2510 0, 2511 // AAGUID. 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 0, 2527 0, 2528 // L. 2529 // CREDENTIAL ID length is 16 as 16-bit big endian. 2530 0, 2531 16, 2532 // CREDENTIAL ID. 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 0, 2548 0, 2549 CBOR_MAP | 5, 2550 // COSE kty. 2551 CBOR_UINT | 1, 2552 // COSE EC2. 2553 CBOR_UINT | 2, 2554 // COSE alg. 2555 CBOR_UINT | 3, 2556 // COSE ES256. 2557 CBOR_NEG | 6, 2558 // COSE EC2 crv. 2559 CBOR_NEG, 2560 // COSE P-256. 2561 CBOR_UINT | 1, 2562 // COSE EC2 x. 2563 CBOR_NEG | 1, 2564 CBOR_BYTES | 24, 2565 // Length is 32. 2566 32, 2567 // X-coordinate. This will be overwritten later. 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 0, 2599 0, 2600 // COSE EC2 y. 2601 CBOR_NEG | 2, 2602 CBOR_BYTES | 24, 2603 // Length is 32. 2604 32, 2605 // Y-coordinate. This will be overwritten later. 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 0, 2637 0, 2638 ] 2639 .as_slice(), 2640 ); 2641 attestation_object[30..62] 2642 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2643 let p256_key = P256Key::from_bytes( 2644 &[ 2645 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 2646 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 2647 ] 2648 .into(), 2649 ) 2650 .unwrap() 2651 .verifying_key() 2652 .to_encoded_point(false); 2653 let x = p256_key.x().unwrap(); 2654 let y = p256_key.y().unwrap(); 2655 attestation_object[111..143].copy_from_slice(x); 2656 attestation_object[146..].copy_from_slice(y); 2657 assert!(matches!(opts.start_ceremony()?.0.verify( 2658 &rp_id, 2659 id, 2660 &Registration { 2661 response: AuthenticatorAttestation::new( 2662 client_data_json, 2663 attestation_object, 2664 AuthTransports::NONE, 2665 ), 2666 authenticator_attachment: AuthenticatorAttachment::None, 2667 client_extension_results: ClientExtensionsOutputs { 2668 cred_props: None, 2669 prf: None, 2670 }, 2671 }, 2672 &RegistrationVerificationOptions::<&str, &str>::default(), 2673 )?.static_state.credential_public_key, UncompressedPubKey::P256(k) if k.x() == x.as_slice() && k.y() == y.as_slice())); 2674 Ok(()) 2675 } 2676 #[test] 2677 #[cfg(feature = "custom")] 2678 fn p256_auth() -> Result<(), AggErr> { 2679 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2680 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 2681 opts.challenge = Challenge(0); 2682 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2683 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 2684 let mut authenticator_data = Vec::with_capacity(69); 2685 authenticator_data.extend_from_slice( 2686 [ 2687 // rpIdHash. 2688 // This will be overwritten later. 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 0, 2720 0, 2721 // flags. 2722 // UP and UV (right-to-left). 2723 0b0000_0101, 2724 // signCount. 2725 // 0 as 32-bit big endian. 2726 0, 2727 0, 2728 0, 2729 0, 2730 ] 2731 .as_slice(), 2732 ); 2733 authenticator_data[..32] 2734 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 2735 authenticator_data 2736 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2737 let p256_key = P256Key::from_bytes( 2738 &[ 2739 137, 133, 36, 206, 163, 47, 255, 5, 76, 144, 163, 141, 40, 109, 108, 240, 246, 115, 2740 178, 237, 169, 68, 6, 129, 92, 21, 238, 127, 55, 158, 207, 95, 2741 ] 2742 .into(), 2743 ) 2744 .unwrap(); 2745 let der_sig: P256DerSig = p256_key.sign(authenticator_data.as_slice()); 2746 let pub_key = p256_key.verifying_key().to_encoded_point(true); 2747 authenticator_data.truncate(37); 2748 assert!(!opts.start_ceremony()?.0.verify( 2749 &rp_id, 2750 &Authentication { 2751 raw_id: CredentialId::try_from(vec![0; 16])?, 2752 response: AuthenticatorAssertion::new( 2753 client_data_json, 2754 authenticator_data, 2755 der_sig.as_bytes().into(), 2756 Some(UserHandle::try_from(vec![0])?), 2757 ), 2758 authenticator_attachment: AuthenticatorAttachment::None, 2759 }, 2760 &mut AuthenticatedCredential::new( 2761 CredentialId::try_from([0; 16].as_slice())?, 2762 UserHandle::try_from([0].as_slice())?, 2763 StaticState { 2764 credential_public_key: CompressedPubKey::<&[u8], _, &[u8], &[u8]>::P256( 2765 CompressedP256PubKey::from(( 2766 (*pub_key.x().unwrap()).into(), 2767 pub_key.tag() == Tag::CompressedOddY 2768 )), 2769 ), 2770 extensions: AuthenticatorExtensionOutputStaticState { 2771 cred_protect: CredentialProtectionPolicy::None, 2772 hmac_secret: None, 2773 }, 2774 }, 2775 DynamicState { 2776 user_verified: true, 2777 backup: Backup::NotEligible, 2778 sign_count: 0, 2779 authenticator_attachment: AuthenticatorAttachment::None, 2780 }, 2781 )?, 2782 &AuthenticationVerificationOptions::<&str, &str>::default(), 2783 )?); 2784 Ok(()) 2785 } 2786 #[test] 2787 #[cfg(feature = "custom")] 2788 fn p384_reg() -> Result<(), AggErr> { 2789 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 2790 let id = UserHandle::try_from([0; 1].as_slice())?; 2791 let mut opts = PublicKeyCredentialCreationOptions::passkey( 2792 &rp_id, 2793 PublicKeyCredentialUserEntity { 2794 name: "foo".try_into()?, 2795 id, 2796 display_name: None, 2797 }, 2798 Vec::new(), 2799 ); 2800 opts.challenge = Challenge(0); 2801 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 2802 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 2803 let mut attestation_object = Vec::with_capacity(243); 2804 attestation_object.extend_from_slice( 2805 [ 2806 CBOR_MAP | 3, 2807 CBOR_TEXT | 3, 2808 b'f', 2809 b'm', 2810 b't', 2811 CBOR_TEXT | 4, 2812 b'n', 2813 b'o', 2814 b'n', 2815 b'e', 2816 // CBOR text of length 7. 2817 CBOR_TEXT | 7, 2818 b'a', 2819 b't', 2820 b't', 2821 b'S', 2822 b't', 2823 b'm', 2824 b't', 2825 CBOR_MAP, 2826 CBOR_TEXT | 8, 2827 b'a', 2828 b'u', 2829 b't', 2830 b'h', 2831 b'D', 2832 b'a', 2833 b't', 2834 b'a', 2835 CBOR_BYTES | 24, 2836 // Length is 181. 2837 181, 2838 // RP ID HASH. 2839 // This will be overwritten later. 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 0, 2871 0, 2872 // FLAGS. 2873 // UP, UV, and AT (right-to-left). 2874 0b0100_0101, 2875 // COUNTER. 2876 // 0 as 32-bit big-endian. 2877 0, 2878 0, 2879 0, 2880 0, 2881 // AAGUID. 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 0, 2897 0, 2898 // L. 2899 // CREDENTIAL ID length is 16 as 16-bit big endian. 2900 0, 2901 16, 2902 // CREDENTIAL ID. 2903 0, 2904 0, 2905 0, 2906 0, 2907 0, 2908 0, 2909 0, 2910 0, 2911 0, 2912 0, 2913 0, 2914 0, 2915 0, 2916 0, 2917 0, 2918 0, 2919 CBOR_MAP | 5, 2920 // COSE kty. 2921 CBOR_UINT | 1, 2922 // COSE EC2. 2923 CBOR_UINT | 2, 2924 // COSE alg. 2925 CBOR_UINT | 3, 2926 CBOR_NEG | 24, 2927 // COSE ES384. 2928 34, 2929 // COSE EC2 crv. 2930 CBOR_NEG, 2931 // COSE P-384. 2932 CBOR_UINT | 2, 2933 // COSE EC2 x. 2934 CBOR_NEG | 1, 2935 CBOR_BYTES | 24, 2936 // Length is 48. 2937 48, 2938 // X-coordinate. This will be overwritten later. 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 0, 2986 0, 2987 // COSE EC2 y. 2988 CBOR_NEG | 2, 2989 CBOR_BYTES | 24, 2990 // Length is 48. 2991 48, 2992 // Y-coordinate. This will be overwritten later. 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 0, 3040 0, 3041 ] 3042 .as_slice(), 3043 ); 3044 attestation_object[30..62] 3045 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3046 let p384_key = P384Key::from_bytes( 3047 &[ 3048 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 3049 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 3050 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 3051 ] 3052 .into(), 3053 ) 3054 .unwrap() 3055 .verifying_key() 3056 .to_encoded_point(false); 3057 let x = p384_key.x().unwrap(); 3058 let y = p384_key.y().unwrap(); 3059 attestation_object[112..160].copy_from_slice(x); 3060 attestation_object[163..].copy_from_slice(y); 3061 assert!(matches!(opts.start_ceremony()?.0.verify( 3062 &rp_id, 3063 id, 3064 &Registration { 3065 response: AuthenticatorAttestation::new( 3066 client_data_json, 3067 attestation_object, 3068 AuthTransports::NONE, 3069 ), 3070 authenticator_attachment: AuthenticatorAttachment::None, 3071 client_extension_results: ClientExtensionsOutputs { 3072 cred_props: None, 3073 prf: None, 3074 }, 3075 }, 3076 &RegistrationVerificationOptions::<&str, &str>::default(), 3077 )?.static_state.credential_public_key, UncompressedPubKey::P384(k) if k.x() == x.as_slice() && k.y() == y.as_slice())); 3078 Ok(()) 3079 } 3080 #[test] 3081 #[cfg(feature = "custom")] 3082 fn p384_auth() -> Result<(), AggErr> { 3083 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3084 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 3085 opts.challenge = Challenge(0); 3086 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3087 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 3088 let mut authenticator_data = Vec::with_capacity(69); 3089 authenticator_data.extend_from_slice( 3090 [ 3091 // rpIdHash. 3092 // This will be overwritten later. 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 0, 3124 0, 3125 // flags. 3126 // UP and UV (right-to-left). 3127 0b0000_0101, 3128 // signCount. 3129 // 0 as 32-bit big-endian. 3130 0, 3131 0, 3132 0, 3133 0, 3134 ] 3135 .as_slice(), 3136 ); 3137 authenticator_data[..32] 3138 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3139 authenticator_data 3140 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 3141 let p384_key = P384Key::from_bytes( 3142 &[ 3143 158, 99, 156, 49, 190, 211, 85, 167, 28, 2, 80, 57, 31, 22, 17, 38, 85, 78, 232, 3144 42, 45, 199, 154, 243, 136, 251, 84, 34, 5, 120, 208, 91, 61, 248, 64, 144, 87, 1, 3145 32, 86, 220, 68, 182, 11, 105, 223, 75, 70, 3146 ] 3147 .into(), 3148 ) 3149 .unwrap(); 3150 let der_sig: P384DerSig = p384_key.sign(authenticator_data.as_slice()); 3151 let pub_key = p384_key.verifying_key().to_encoded_point(true); 3152 authenticator_data.truncate(37); 3153 assert!(!opts.start_ceremony()?.0.verify( 3154 &rp_id, 3155 &Authentication { 3156 raw_id: CredentialId::try_from(vec![0; 16])?, 3157 response: AuthenticatorAssertion::new( 3158 client_data_json, 3159 authenticator_data, 3160 der_sig.as_bytes().into(), 3161 Some(UserHandle::try_from(vec![0])?), 3162 ), 3163 authenticator_attachment: AuthenticatorAttachment::None, 3164 }, 3165 &mut AuthenticatedCredential::new( 3166 CredentialId::try_from([0; 16].as_slice())?, 3167 UserHandle::try_from([0].as_slice())?, 3168 StaticState { 3169 credential_public_key: CompressedPubKey::<&[u8], &[u8], _, &[u8]>::P384( 3170 CompressedP384PubKey::from(( 3171 (*pub_key.x().unwrap()).into(), 3172 pub_key.tag() == Tag::CompressedOddY 3173 )), 3174 ), 3175 extensions: AuthenticatorExtensionOutputStaticState { 3176 cred_protect: CredentialProtectionPolicy::None, 3177 hmac_secret: None, 3178 }, 3179 }, 3180 DynamicState { 3181 user_verified: true, 3182 backup: Backup::NotEligible, 3183 sign_count: 0, 3184 authenticator_attachment: AuthenticatorAttachment::None, 3185 }, 3186 )?, 3187 &AuthenticationVerificationOptions::<&str, &str>::default(), 3188 )?); 3189 Ok(()) 3190 } 3191 #[test] 3192 #[cfg(feature = "custom")] 3193 fn rsa_reg() -> Result<(), AggErr> { 3194 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3195 let id = UserHandle::try_from([0; 1].as_slice())?; 3196 let mut opts = PublicKeyCredentialCreationOptions::passkey( 3197 &rp_id, 3198 PublicKeyCredentialUserEntity { 3199 name: "foo".try_into()?, 3200 id, 3201 display_name: None, 3202 }, 3203 Vec::new(), 3204 ); 3205 opts.challenge = Challenge(0); 3206 let client_data_json = br#"{"type":"webauthn.create","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3207 // We over-allocate by 32 bytes. See [`AuthenticatorAttestation::new`] for more information. 3208 let mut attestation_object = Vec::with_capacity(406); 3209 attestation_object.extend_from_slice( 3210 [ 3211 CBOR_MAP | 3, 3212 CBOR_TEXT | 3, 3213 b'f', 3214 b'm', 3215 b't', 3216 CBOR_TEXT | 4, 3217 b'n', 3218 b'o', 3219 b'n', 3220 b'e', 3221 CBOR_TEXT | 7, 3222 b'a', 3223 b't', 3224 b't', 3225 b'S', 3226 b't', 3227 b'm', 3228 b't', 3229 CBOR_MAP, 3230 CBOR_TEXT | 8, 3231 b'a', 3232 b'u', 3233 b't', 3234 b'h', 3235 b'D', 3236 b'a', 3237 b't', 3238 b'a', 3239 CBOR_BYTES | 25, 3240 // Length is 343 as 16-bit big-endian. 3241 1, 3242 87, 3243 // RP ID HASH. 3244 // This will be overwritten later. 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 0, 3276 0, 3277 // FLAGS. 3278 // UP, UV, and AT (right-to-left). 3279 0b0100_0101, 3280 // COUNTER. 3281 // 0 as 32-bit big-endian. 3282 0, 3283 0, 3284 0, 3285 0, 3286 // AAGUID. 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 0, 3302 0, 3303 // L. 3304 // CREDENTIAL ID length is 16 as 16-bit big endian. 3305 0, 3306 16, 3307 // CREDENTIAL ID. 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 0, 3323 0, 3324 CBOR_MAP | 4, 3325 // COSE kty. 3326 CBOR_UINT | 1, 3327 // COSE RSA. 3328 CBOR_UINT | 3, 3329 // COSE alg. 3330 CBOR_UINT | 3, 3331 CBOR_NEG | 25, 3332 // COSE RS256. 3333 1, 3334 0, 3335 // COSE n. 3336 CBOR_NEG, 3337 CBOR_BYTES | 25, 3338 // Length is 256 as 16-bit big-endian. 3339 1, 3340 0, 3341 // N. This will be overwritten later. 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 0, 3597 0, 3598 // COSE e. 3599 CBOR_NEG | 1, 3600 CBOR_BYTES | 3, 3601 // 65537 as 24-bit big-endian. 3602 1, 3603 0, 3604 1, 3605 ] 3606 .as_slice(), 3607 ); 3608 attestation_object[31..63] 3609 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3610 let n = [ 3611 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 3612 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 3613 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 3614 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 3615 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 3616 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 3617 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 3618 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 3619 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 3620 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 3621 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 3622 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 3623 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 3624 153, 79, 0, 133, 78, 7, 218, 165, 241, 3625 ]; 3626 let e = 65537; 3627 let d = [ 3628 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 3629 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 3630 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 3631 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 3632 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 3633 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 3634 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 3635 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 3636 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 3637 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 3638 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 3639 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 3640 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 3641 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 3642 ]; 3643 let p = BigUint::from_slice( 3644 [ 3645 352691927, 1294578443, 816143558, 690659917, 1161596366, 1544791087, 3999549486, 3646 3319149924, 2349250979, 1304689381, 3959753736, 3377900978, 866506027, 1671521644, 3647 3926847564, 898221388, 3448219846, 494454484, 3915534864, 2869735916, 2456511629, 3648 3397234721, 3012775852, 3472309790, 1923617705, 2993441050, 3210302569, 3605331368, 3649 3352563766, 688081007, 4104512503, 4145593376, 3650 ] 3651 .as_slice(), 3652 ); 3653 let p_2 = BigUint::from_slice( 3654 [ 3655 4039514409, 964284038, 3230008587, 3320139220, 3562360334, 3165876926, 212773653, 3656 2752465512, 2973674888, 1717425549, 2084262803, 3585031058, 4162394935, 1428626842, 3657 1015474994, 3283774155, 2840050110, 190639246, 147241978, 2994256073, 4081014755, 3658 3102401369, 3547397148, 1545029057, 895305733, 2689179461, 1593439337, 3960057302, 3659 193068804, 2835123424, 4054880057, 4200258364, 3660 ] 3661 .as_slice(), 3662 ); 3663 let rsa_key = RsaKey::<Sha256>::new( 3664 RsaPrivateKey::from_components( 3665 BigUint::from_bytes_le(n.as_slice()), 3666 e.into(), 3667 BigUint::from_bytes_le(d.as_slice()), 3668 vec![p, p_2], 3669 ) 3670 .unwrap(), 3671 ) 3672 .verifying_key(); 3673 let n = rsa_key.as_ref().n().to_bytes_be(); 3674 attestation_object[113..369].copy_from_slice(n.as_slice()); 3675 assert!(matches!(opts.start_ceremony()?.0.verify( 3676 &rp_id, 3677 id, 3678 &Registration { 3679 response: AuthenticatorAttestation::new( 3680 client_data_json, 3681 attestation_object, 3682 AuthTransports::NONE, 3683 ), 3684 authenticator_attachment: AuthenticatorAttachment::None, 3685 client_extension_results: ClientExtensionsOutputs { 3686 cred_props: None, 3687 prf: None, 3688 }, 3689 }, 3690 &RegistrationVerificationOptions::<&str, &str>::default(), 3691 )?.static_state.credential_public_key, UncompressedPubKey::Rsa(k) if *k.n() == n.as_slice() && k.e() == e)); 3692 Ok(()) 3693 } 3694 #[test] 3695 #[cfg(feature = "custom")] 3696 fn rsa_auth() -> Result<(), AggErr> { 3697 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 3698 let mut opts = PublicKeyCredentialRequestOptions::passkey(&rp_id); 3699 opts.challenge = Challenge(0); 3700 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 3701 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 3702 let mut authenticator_data = Vec::with_capacity(69); 3703 authenticator_data.extend_from_slice( 3704 [ 3705 // rpIdHash. 3706 // This will be overwritten later. 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 0, 3738 0, 3739 // flags. 3740 // UP and UV (right-to-left). 3741 0b0000_0101, 3742 // signCount. 3743 // 0 as 32-bit big-endian. 3744 0, 3745 0, 3746 0, 3747 0, 3748 ] 3749 .as_slice(), 3750 ); 3751 authenticator_data[..32] 3752 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 3753 authenticator_data 3754 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 3755 let n = [ 3756 111, 183, 124, 133, 38, 167, 70, 148, 44, 50, 30, 60, 121, 14, 38, 37, 96, 114, 107, 3757 195, 248, 64, 79, 36, 237, 140, 43, 27, 94, 74, 102, 152, 135, 102, 184, 150, 186, 206, 3758 185, 19, 165, 209, 48, 98, 98, 9, 3, 205, 208, 82, 250, 105, 132, 201, 73, 62, 60, 165, 3759 100, 128, 153, 9, 41, 118, 66, 95, 236, 214, 73, 135, 197, 68, 184, 10, 27, 116, 204, 3760 145, 50, 174, 58, 42, 183, 181, 119, 232, 126, 252, 217, 96, 162, 190, 103, 122, 64, 3761 87, 145, 45, 32, 207, 17, 239, 223, 3, 35, 14, 112, 119, 124, 141, 123, 208, 239, 105, 3762 81, 217, 151, 162, 190, 17, 88, 182, 176, 158, 81, 200, 42, 166, 133, 48, 23, 236, 55, 3763 117, 248, 233, 151, 203, 122, 155, 231, 46, 177, 20, 20, 151, 64, 222, 239, 226, 7, 21, 3764 254, 81, 202, 64, 232, 161, 235, 22, 51, 246, 207, 213, 0, 229, 138, 46, 222, 205, 157, 3765 108, 139, 253, 230, 80, 50, 2, 122, 212, 163, 100, 180, 114, 12, 113, 52, 56, 99, 188, 3766 42, 198, 212, 23, 182, 222, 56, 221, 200, 79, 96, 239, 221, 135, 10, 17, 106, 183, 56, 3767 104, 68, 94, 198, 196, 35, 200, 83, 204, 26, 185, 204, 212, 31, 183, 19, 111, 233, 13, 3768 72, 93, 53, 65, 111, 59, 242, 122, 160, 244, 162, 126, 38, 235, 156, 47, 88, 39, 132, 3769 153, 79, 0, 133, 78, 7, 218, 165, 241, 3770 ]; 3771 let e = 65537; 3772 let d = [ 3773 145, 79, 21, 97, 233, 3, 192, 194, 177, 68, 181, 80, 120, 197, 23, 44, 185, 74, 144, 0, 3774 132, 149, 139, 11, 16, 224, 4, 112, 236, 94, 238, 97, 121, 124, 213, 145, 24, 253, 168, 3775 35, 190, 205, 132, 115, 33, 201, 38, 253, 246, 180, 66, 155, 165, 46, 3, 254, 68, 108, 3776 154, 247, 246, 45, 187, 0, 204, 96, 185, 157, 249, 174, 158, 38, 62, 244, 183, 76, 102, 3777 6, 219, 92, 212, 138, 59, 147, 163, 219, 111, 39, 105, 21, 236, 196, 38, 255, 114, 247, 3778 82, 104, 113, 204, 29, 152, 209, 219, 48, 239, 74, 129, 19, 247, 33, 239, 119, 166, 3779 216, 152, 94, 138, 238, 164, 242, 129, 50, 150, 57, 20, 53, 224, 56, 241, 138, 97, 111, 3780 215, 107, 212, 195, 146, 108, 143, 0, 229, 181, 171, 73, 152, 105, 146, 25, 243, 242, 3781 140, 252, 248, 162, 247, 63, 168, 180, 20, 153, 120, 10, 248, 211, 1, 71, 127, 212, 3782 249, 237, 203, 202, 48, 26, 216, 226, 228, 186, 13, 204, 70, 255, 240, 89, 255, 59, 83, 3783 31, 253, 55, 43, 158, 90, 248, 83, 32, 159, 105, 57, 134, 34, 96, 18, 255, 245, 153, 3784 162, 60, 91, 99, 220, 51, 44, 85, 114, 67, 125, 202, 65, 217, 245, 40, 8, 81, 165, 142, 3785 24, 245, 127, 122, 247, 152, 212, 75, 45, 59, 90, 184, 234, 31, 147, 36, 8, 212, 45, 3786 50, 23, 3, 25, 253, 87, 227, 79, 119, 161, 3787 ]; 3788 let p = BigUint::from_slice( 3789 [ 3790 352691927, 1294578443, 816143558, 690659917, 1161596366, 1544791087, 3999549486, 3791 3319149924, 2349250979, 1304689381, 3959753736, 3377900978, 866506027, 1671521644, 3792 3926847564, 898221388, 3448219846, 494454484, 3915534864, 2869735916, 2456511629, 3793 3397234721, 3012775852, 3472309790, 1923617705, 2993441050, 3210302569, 3605331368, 3794 3352563766, 688081007, 4104512503, 4145593376, 3795 ] 3796 .as_slice(), 3797 ); 3798 let p_2 = BigUint::from_slice( 3799 [ 3800 4039514409, 964284038, 3230008587, 3320139220, 3562360334, 3165876926, 212773653, 3801 2752465512, 2973674888, 1717425549, 2084262803, 3585031058, 4162394935, 1428626842, 3802 1015474994, 3283774155, 2840050110, 190639246, 147241978, 2994256073, 4081014755, 3803 3102401369, 3547397148, 1545029057, 895305733, 2689179461, 1593439337, 3960057302, 3804 193068804, 2835123424, 4054880057, 4200258364, 3805 ] 3806 .as_slice(), 3807 ); 3808 let rsa_key = RsaKey::<Sha256>::new( 3809 RsaPrivateKey::from_components( 3810 BigUint::from_bytes_le(n.as_slice()), 3811 e.into(), 3812 BigUint::from_bytes_le(d.as_slice()), 3813 vec![p, p_2], 3814 ) 3815 .unwrap(), 3816 ); 3817 let rsa_pub = rsa_key.verifying_key(); 3818 let sig = rsa_key.sign(authenticator_data.as_slice()).to_vec(); 3819 authenticator_data.truncate(37); 3820 assert!(!opts.start_ceremony()?.0.verify( 3821 &rp_id, 3822 &Authentication { 3823 raw_id: CredentialId::try_from(vec![0; 16])?, 3824 response: AuthenticatorAssertion::new( 3825 client_data_json, 3826 authenticator_data, 3827 sig, 3828 Some(UserHandle::try_from(vec![0])?), 3829 ), 3830 authenticator_attachment: AuthenticatorAttachment::None, 3831 }, 3832 &mut AuthenticatedCredential::new( 3833 CredentialId::try_from([0; 16].as_slice())?, 3834 UserHandle::try_from([0].as_slice())?, 3835 StaticState { 3836 credential_public_key: CompressedPubKey::<&[u8], &[u8], &[u8], _>::Rsa( 3837 RsaPubKey::try_from((rsa_pub.as_ref().n().to_bytes_be(), e)).unwrap(), 3838 ), 3839 extensions: AuthenticatorExtensionOutputStaticState { 3840 cred_protect: CredentialProtectionPolicy::None, 3841 hmac_secret: None, 3842 }, 3843 }, 3844 DynamicState { 3845 user_verified: true, 3846 backup: Backup::NotEligible, 3847 sign_count: 0, 3848 authenticator_attachment: AuthenticatorAttachment::None, 3849 }, 3850 )?, 3851 &AuthenticationVerificationOptions::<&str, &str>::default(), 3852 )?); 3853 Ok(()) 3854 } 3855 }