register.rs (89418B)
1 #[cfg(test)] 2 mod tests; 3 use super::{ 4 super::{ 5 DynamicState, Metadata, RegisteredCredential, StaticState, 6 response::{ 7 AuthenticatorAttachment, 8 register::{ 9 Attestation, AttestationFormat, AuthenticatorExtensionOutput, 10 ClientExtensionsOutputs, CredentialProtectionPolicy, HmacSecret, Registration, 11 UncompressedPubKey, 12 error::{ExtensionErr, RegCeremonyErr}, 13 }, 14 }, 15 }, 16 Ceremony, Challenge, CredentialMediationRequirement, ExtensionInfo, ExtensionReq, FIVE_MINUTES, 17 Hints, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId, SentChallenge, TimedCeremony, 18 UserVerificationRequirement, 19 register::error::CreationOptionsErr, 20 }; 21 #[cfg(doc)] 22 use crate::{ 23 request::{ 24 AsciiDomain, AsciiDomainStatic, DomainOrigin, Url, 25 auth::{AuthenticationVerificationOptions, PublicKeyCredentialRequestOptions}, 26 }, 27 response::{AuthTransports, AuthenticatorTransport, Backup, CollectedClientData, Flag}, 28 }; 29 use core::{ 30 borrow::Borrow, 31 cmp::Ordering, 32 fmt::{self, Display, Formatter}, 33 hash::{Hash, Hasher}, 34 mem, 35 num::{NonZeroU32, NonZeroU64}, 36 time::Duration, 37 }; 38 #[cfg(any(doc, not(feature = "serializable_server_state")))] 39 use std::time::Instant; 40 #[cfg(any(doc, feature = "serializable_server_state"))] 41 use std::time::SystemTime; 42 /// Contains functionality to (de)serialize data to a data store. 43 #[cfg(feature = "bin")] 44 pub mod bin; 45 /// Contains functionality that needs to be accessible when `bin` or `serde` are not enabled. 46 #[cfg(feature = "custom")] 47 mod custom; 48 /// Contains error types. 49 pub mod error; 50 /// Contains functionality to (de)serialize data to a client. 51 #[cfg(feature = "serde")] 52 pub mod ser; 53 /// Contains functionality to (de)serialize [`RegistrationServerState`] to a data store. 54 #[cfg(feature = "serializable_server_state")] 55 pub mod ser_server_state; 56 /// Backup requirements for the credential. 57 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 58 pub enum BackupReq { 59 /// No requirements (i.e., any [`Backup`] is allowed). 60 #[default] 61 None, 62 /// Credential must not be eligible for backup. 63 NotEligible, 64 /// Credential must be eligible for backup. 65 /// 66 /// Note the existence of a backup is ignored. If a backup must exist, then use [`Self::Exists`]; if a 67 /// backup must not exist, then use [`Self::EligibleNotExists`]. 68 Eligible, 69 /// Credential must be eligible for backup, but a backup must not exist. 70 EligibleNotExists, 71 /// Credential must be backed up. 72 Exists, 73 } 74 /// Used by [`Extension::cred_protect`] to enforce the [`CredentialProtectionPolicy`] sent by the client via 75 /// [`Registration`]. 76 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 77 pub enum CredProtect { 78 /// No `credProtect` request. 79 #[default] 80 None, 81 /// Request 82 /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional) 83 /// but allow any. 84 /// 85 /// The `bool` corresponds to 86 /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy). 87 UserVerificationOptional(bool, ExtensionInfo), 88 /// Request 89 /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist); 90 /// and when enforcing the value, disallow [`CredentialProtectionPolicy::UserVerificationOptional`]. 91 /// 92 /// The `bool` corresponds to 93 /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy). 94 UserVerificationOptionalWithCredentialIdList(bool, ExtensionInfo), 95 /// Request 96 /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired); 97 /// and when enforcing the value, only allow [`CredentialProtectionPolicy::UserVerificationRequired`]. 98 /// 99 /// The `bool` corresponds to 100 /// [`enforceCredentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#dom-authenticationextensionsclientinputs-enforcecredentialprotectionpolicy). 101 UserVerificationRequired(bool, ExtensionInfo), 102 } 103 impl CredProtect { 104 /// Validates `other` is allowed based on `self`. 105 /// 106 /// # Errors 107 /// 108 /// Errors iff other is a less "secure" policy than `self` when enforcing the value or other does not exist 109 /// despite requiring a value to be sent back. 110 /// 111 /// Note a missing response is OK when enforcing a value. 112 const fn validate(self, other: CredentialProtectionPolicy) -> Result<(), ExtensionErr> { 113 match self { 114 Self::None => Ok(()), 115 Self::UserVerificationOptional(_, info) => { 116 if matches!(other, CredentialProtectionPolicy::None) { 117 if matches!( 118 info, 119 ExtensionInfo::RequireEnforceValue | ExtensionInfo::RequireDontEnforceValue 120 ) { 121 Err(ExtensionErr::MissingCredProtect) 122 } else { 123 Ok(()) 124 } 125 } else { 126 Ok(()) 127 } 128 } 129 Self::UserVerificationOptionalWithCredentialIdList(_, info) => match info { 130 ExtensionInfo::RequireEnforceValue => match other { 131 CredentialProtectionPolicy::None => Err(ExtensionErr::MissingCredProtect), 132 CredentialProtectionPolicy::UserVerificationOptional => { 133 Err(ExtensionErr::InvalidCredProtectValue(self, other)) 134 } 135 CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList 136 | CredentialProtectionPolicy::UserVerificationRequired => Ok(()), 137 }, 138 ExtensionInfo::RequireDontEnforceValue => { 139 if matches!(other, CredentialProtectionPolicy::None) { 140 Err(ExtensionErr::MissingCredProtect) 141 } else { 142 Ok(()) 143 } 144 } 145 ExtensionInfo::AllowEnforceValue => { 146 if matches!(other, CredentialProtectionPolicy::UserVerificationOptional) { 147 Err(ExtensionErr::InvalidCredProtectValue(self, other)) 148 } else { 149 Ok(()) 150 } 151 } 152 ExtensionInfo::AllowDontEnforceValue => Ok(()), 153 }, 154 Self::UserVerificationRequired(_, info) => match info { 155 ExtensionInfo::RequireEnforceValue => match other { 156 CredentialProtectionPolicy::None => Err(ExtensionErr::MissingCredProtect), 157 CredentialProtectionPolicy::UserVerificationOptional 158 | CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => { 159 Err(ExtensionErr::InvalidCredProtectValue(self, other)) 160 } 161 CredentialProtectionPolicy::UserVerificationRequired => Ok(()), 162 }, 163 ExtensionInfo::RequireDontEnforceValue => { 164 if matches!(other, CredentialProtectionPolicy::None) { 165 Err(ExtensionErr::MissingCredProtect) 166 } else { 167 Ok(()) 168 } 169 } 170 ExtensionInfo::AllowEnforceValue => { 171 if matches!( 172 other, 173 CredentialProtectionPolicy::None 174 | CredentialProtectionPolicy::UserVerificationRequired 175 ) { 176 Ok(()) 177 } else { 178 Err(ExtensionErr::InvalidCredProtectValue(self, other)) 179 } 180 } 181 ExtensionInfo::AllowDontEnforceValue => Ok(()), 182 }, 183 } 184 } 185 } 186 impl Display for CredProtect { 187 #[inline] 188 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 189 match *self { 190 Self::None => f.write_str("do not sent a credProtect request"), 191 Self::UserVerificationOptional(enforce, info) => { 192 write!( 193 f, 194 "request user verification optional with enforcement {enforce} and {info}" 195 ) 196 } 197 Self::UserVerificationOptionalWithCredentialIdList(enforce, info) => write!( 198 f, 199 "user verification optional with credential ID list with enforcement {enforce} and {info}" 200 ), 201 Self::UserVerificationRequired(enforce, info) => { 202 write!( 203 f, 204 "user verification required with enforcement {enforce} and {info}" 205 ) 206 } 207 } 208 } 209 } 210 /// [`COSEAlgorithmIdentifier`](https://www.w3.org/TR/webauthn-3/#typedefdef-cosealgorithmidentifier). 211 /// 212 /// Note the order of variants is the following: 213 /// 214 /// [`Self::Mldsa87`] `<` [`Self::Mldsa65`] `<` [`Self::Mldsa44`] `<` [`Self::Eddsa`] `<` [`Self::Es256`] 215 /// `<` [`Self::Es384`] `<` [`Self::Rs256`]. 216 /// 217 /// This is relevant for [`CoseAlgorithmIdentifiers`]. For example a `CoseAlgorithmIdentifiers` 218 /// that contains `Self::Mldsa87` will prioritize it over all others. 219 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 220 pub enum CoseAlgorithmIdentifier { 221 /// [ML-DSA-87](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 222 Mldsa87, 223 /// [ML-DSA-65](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 224 Mldsa65, 225 /// [ML-DSA-44](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 226 Mldsa44, 227 /// [EdDSA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 228 /// 229 /// Note that Ed25519 must be used for the `crv` parameter 230 /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier). 231 Eddsa, 232 /// [ES256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 233 /// 234 /// Note the uncompressed form must be used and P-256 must be used for the `crv` parameter 235 /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier). 236 Es256, 237 /// [ES384](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 238 /// 239 /// Note the uncompressed form must be used and P-384 must be used for the `crv` parameter 240 /// [per the spec](https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier). 241 Es384, 242 /// [RS256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 243 Rs256, 244 } 245 impl CoseAlgorithmIdentifier { 246 /// Transforms `self` into a `u8`. 247 const fn to_u8(self) -> u8 { 248 match self { 249 Self::Mldsa87 => 0x1, 250 Self::Mldsa65 => 0x2, 251 Self::Mldsa44 => 0x4, 252 Self::Eddsa => 0x8, 253 Self::Es256 => 0x10, 254 Self::Es384 => 0x20, 255 Self::Rs256 => 0x40, 256 } 257 } 258 } 259 impl PartialEq<&Self> for CoseAlgorithmIdentifier { 260 #[inline] 261 fn eq(&self, other: &&Self) -> bool { 262 *self == **other 263 } 264 } 265 impl PartialEq<CoseAlgorithmIdentifier> for &CoseAlgorithmIdentifier { 266 #[inline] 267 fn eq(&self, other: &CoseAlgorithmIdentifier) -> bool { 268 **self == *other 269 } 270 } 271 /// Non-empty ordered set of [`CoseAlgorithmIdentifier`]s. 272 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 273 pub struct CoseAlgorithmIdentifiers(u8); 274 impl CoseAlgorithmIdentifiers { 275 /// Contains all [`CoseAlgorithmIdentifier`]s. 276 pub const ALL: Self = Self(0) 277 .add(CoseAlgorithmIdentifier::Mldsa87) 278 .add(CoseAlgorithmIdentifier::Mldsa65) 279 .add(CoseAlgorithmIdentifier::Mldsa44) 280 .add(CoseAlgorithmIdentifier::Eddsa) 281 .add(CoseAlgorithmIdentifier::Es256) 282 .add(CoseAlgorithmIdentifier::Es384) 283 .add(CoseAlgorithmIdentifier::Rs256); 284 /// Returns a `CoseAlgorithmIdentifiers` containing all `CoseAlgorithmIdentifier`s in `self` plus `alg`. 285 const fn add(self, alg: CoseAlgorithmIdentifier) -> Self { 286 Self(self.0 | alg.to_u8()) 287 } 288 /// Returns a copy of `self` with `alg` removed if there would be at least one `CoseAlgorithmIdentifier` 289 /// remaining; otherwise returns `self`. 290 #[inline] 291 #[must_use] 292 pub const fn remove(self, alg: CoseAlgorithmIdentifier) -> Self { 293 let val = alg.to_u8(); 294 if self.0 == val { 295 self 296 } else { 297 Self(self.0 & !val) 298 } 299 } 300 /// Returns `true` iff `self` contains `alg`. 301 #[inline] 302 #[must_use] 303 pub const fn contains(self, alg: CoseAlgorithmIdentifier) -> bool { 304 let val = alg.to_u8(); 305 self.0 & val == val 306 } 307 /// Validates `other` is allowed based on `self`. 308 const fn validate(self, other: UncompressedPubKey<'_>) -> Result<(), RegCeremonyErr> { 309 if match other { 310 UncompressedPubKey::MlDsa87(_) => self.contains(CoseAlgorithmIdentifier::Mldsa87), 311 UncompressedPubKey::MlDsa65(_) => self.contains(CoseAlgorithmIdentifier::Mldsa65), 312 UncompressedPubKey::MlDsa44(_) => self.contains(CoseAlgorithmIdentifier::Mldsa44), 313 UncompressedPubKey::Ed25519(_) => self.contains(CoseAlgorithmIdentifier::Eddsa), 314 UncompressedPubKey::P256(_) => self.contains(CoseAlgorithmIdentifier::Es256), 315 UncompressedPubKey::P384(_) => self.contains(CoseAlgorithmIdentifier::Es384), 316 UncompressedPubKey::Rsa(_) => self.contains(CoseAlgorithmIdentifier::Rs256), 317 } { 318 Ok(()) 319 } else { 320 Err(RegCeremonyErr::PublicKeyAlgorithmMismatch) 321 } 322 } 323 } 324 impl Default for CoseAlgorithmIdentifiers { 325 /// Returns [`Self::ALL`]. 326 #[inline] 327 fn default() -> Self { 328 Self::ALL 329 } 330 } 331 /// Four to sixty-three inclusively. 332 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 333 #[repr(u8)] 334 pub enum FourToSixtyThree { 335 /// 4. 336 Four = 4, 337 /// 5. 338 Five, 339 /// 6. 340 Six, 341 /// 7. 342 Seven, 343 /// 8. 344 Eight, 345 /// 9. 346 Nine, 347 /// 10. 348 Ten, 349 /// 11. 350 Eleven, 351 /// 12. 352 Twelve, 353 /// 13. 354 Thirteen, 355 /// 14. 356 Fourteen, 357 /// 15. 358 Fifteen, 359 /// 16. 360 Sixteen, 361 /// 17. 362 Seventeen, 363 /// 18. 364 Eighteen, 365 /// 19. 366 Nineteen, 367 /// 20. 368 Twenty, 369 /// 21. 370 TwentyOne, 371 /// 22. 372 TwentyTwo, 373 /// 23. 374 TwentyThree, 375 /// 24. 376 TwentyFour, 377 /// 25. 378 TwentyFive, 379 /// 26. 380 TwentySix, 381 /// 27. 382 TwentySeven, 383 /// 28. 384 TwentyEight, 385 /// 29. 386 TwentyNine, 387 /// 30. 388 Thirty, 389 /// 31. 390 ThirtyOne, 391 /// 32. 392 ThirtyTwo, 393 /// 33. 394 ThirtyThree, 395 /// 34. 396 ThirtyFour, 397 /// 35. 398 ThirtyFive, 399 /// 36. 400 ThirtySix, 401 /// 37. 402 ThirtySeven, 403 /// 38. 404 ThirtyEight, 405 /// 39. 406 ThirtyNine, 407 /// 40. 408 Fourty, 409 /// 41. 410 FourtyOne, 411 /// 42. 412 FourtyTwo, 413 /// 43. 414 FourtyThree, 415 /// 44. 416 FourtyFour, 417 /// 45. 418 FourtyFive, 419 /// 46. 420 FourtySix, 421 /// 47. 422 FourtySeven, 423 /// 48. 424 FourtyEight, 425 /// 49. 426 FourtyNine, 427 /// 50. 428 Fifty, 429 /// 51. 430 FiftyOne, 431 /// 52. 432 FiftyTwo, 433 /// 53. 434 FiftyThree, 435 /// 54. 436 FiftyFour, 437 /// 55. 438 FiftyFive, 439 /// 56. 440 FiftySix, 441 /// 57. 442 FiftySeven, 443 /// 58. 444 FiftyEight, 445 /// 59. 446 FiftyNine, 447 /// 60. 448 Sixty, 449 /// 61. 450 SixtyOne, 451 /// 62. 452 SixtyTwo, 453 /// 63. 454 SixtyThree, 455 } 456 impl FourToSixtyThree { 457 /// Returns the equivalent `u8`. 458 #[expect(clippy::as_conversions, reason = "comment justifies correctness")] 459 #[inline] 460 #[must_use] 461 pub const fn into_u8(self) -> u8 { 462 // This is correct since `Self` is `repr(u8)`, and the initial discriminant has the value `4` 463 // and subsequent discriminants are implicitly incremented by 1. 464 self as u8 465 } 466 /// Returns `Some` representing `val` iff `val` is inclusively between 4 and 63. 467 #[expect(unsafe_code, reason = "comment justifies correctness")] 468 #[inline] 469 #[must_use] 470 pub const fn from_u8(val: u8) -> Option<Self> { 471 match val { 472 0..=3 | 64.. => None, 473 _ => { 474 // SAFETY: 475 // `val` is inclusively between 4 and 63, and `Self` is `repr(u8)`; thus this 476 // is safe and correct. 477 Some(unsafe { mem::transmute::<u8, Self>(val) }) 478 } 479 } 480 } 481 } 482 impl Display for FourToSixtyThree { 483 #[inline] 484 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 485 self.into_u8().fmt(f) 486 } 487 } 488 impl From<FourToSixtyThree> for u8 { 489 #[inline] 490 fn from(value: FourToSixtyThree) -> Self { 491 value.into_u8() 492 } 493 } 494 impl Default for FourToSixtyThree { 495 #[inline] 496 fn default() -> Self { 497 Self::Four 498 } 499 } 500 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client. 501 #[derive(Clone, Copy, Debug)] 502 pub struct Extension<'prf_first, 'prf_second> { 503 /// [`credProps`](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension). 504 /// 505 /// The best one can do to ensure a server-side credential is created is by sending 506 /// [`ResidentKeyRequirement::Discouraged`]; however authenticators are still allowed 507 /// to create a client-side credential. To more definitively check that a server-side credential is 508 /// created, send this extension. Note that it can be difficult to impossible for a client/user agent to 509 /// know that a server-side credential is created; thus even when the response is 510 /// `Some(CredentialPropertiesOutput { rk: Some(false) })`, a client-side/"resident" credential could still 511 /// have been created. One may have better luck checking if [`AuthTransports::contains`] 512 /// [`AuthenticatorTransport::Internal`] and using that as an indicator if a client-side credential was created. 513 /// 514 /// In the event [`ClientExtensionsOutputs::cred_props`] is `Some(CredentialPropertiesOutput { rk: Some(false) })` 515 /// and [`ResidentKeyRequirement::Required`] was sent, an error will happen regardless of this value. 516 pub cred_props: Option<ExtensionReq>, 517 /// [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension). 518 pub cred_protect: CredProtect, 519 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension). 520 /// 521 /// When the value is enforced, that corresponds to 522 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension) 523 /// in [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) set to a value at least as large 524 /// as the contained `FourToSixtyThree`. 525 pub min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>, 526 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 527 /// 528 /// When the value is enforced, that corresponds to 529 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) set to `true`. 530 /// In contrast [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) 531 /// must not exist, be `null`, or be an 532 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 533 /// such that [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is `null` 534 /// and [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) does not 535 /// exist or is `null`. This is to ensure the decrypted outputs stay on the client. 536 /// 537 /// Note some authenticators can only enable `prf` during registration (e.g., CTAP authenticators that only 538 /// support 539 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-extension) 540 /// and not 541 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension)); 542 /// thus the value of `PrfInput` is ignored and only used as a signal to enable `prf`. For many such 543 /// authenticators, not using this extension during registration will not preclude them from being used during 544 /// authentication; however it is still encouraged to use the extension during registration since some 545 /// authenticators actually require it. 546 /// 547 /// When the underlying credential is expected to be used during discoverable requests, it is likely that 548 /// `'prf_first` will be `'static` and [`PrfInput::second`] is `None` since one will not be able to 549 /// realistically rotate the underlying inputs and further the same input will likely be used for all credentials. 550 /// For credentials intended to be used during non-discoverable requests, however, one is encouraged to rotate 551 /// the inputs and have unique values for each credential. 552 pub prf: Option<(PrfInput<'prf_first, 'prf_second>, ExtensionInfo)>, 553 } 554 impl<'prf_first, 'prf_second> Extension<'prf_first, 'prf_second> { 555 /// Returns an empty `Extension`. 556 #[inline] 557 #[must_use] 558 pub const fn none() -> Self { 559 Self { 560 cred_props: None, 561 cred_protect: CredProtect::None, 562 min_pin_length: None, 563 prf: None, 564 } 565 } 566 /// Same as [`Self::none`] except [`Self::cred_props`] is `Some` containing `req`. 567 #[inline] 568 #[must_use] 569 pub const fn with_cred_props(req: ExtensionReq) -> Self { 570 Self { 571 cred_props: Some(req), 572 ..Self::none() 573 } 574 } 575 /// Same as [`Self::none`] except [`Self::cred_protect`] is `cred_protect`. 576 #[inline] 577 #[must_use] 578 pub const fn with_cred_protect(cred_protect: CredProtect) -> Self { 579 Self { 580 cred_protect, 581 ..Self::none() 582 } 583 } 584 /// Same as [`Self::none`] except [`Self::min_pin_length`] is `Some` containing `min_len` and `info`. 585 #[inline] 586 #[must_use] 587 pub const fn with_min_pin_length(min_len: FourToSixtyThree, info: ExtensionInfo) -> Self { 588 Self { 589 min_pin_length: Some((min_len, info)), 590 ..Self::none() 591 } 592 } 593 /// Same as [`Self::none`] except [`Self::prf`] is `Some` containing `input` and `info`. 594 #[expect(single_use_lifetimes, reason = "false positive")] 595 #[inline] 596 #[must_use] 597 pub const fn with_prf<'a: 'prf_first, 'b: 'prf_second>( 598 input: PrfInput<'a, 'b>, 599 info: ExtensionInfo, 600 ) -> Self { 601 Self { 602 prf: Some((input, info)), 603 ..Self::none() 604 } 605 } 606 } 607 impl Default for Extension<'_, '_> { 608 /// Same as [`Self::none`]. 609 #[inline] 610 fn default() -> Self { 611 Self::none() 612 } 613 } 614 #[cfg(test)] 615 impl PartialEq for Extension<'_, '_> { 616 #[inline] 617 fn eq(&self, other: &Self) -> bool { 618 self.cred_props == other.cred_props 619 && self.cred_protect == other.cred_protect 620 && self.min_pin_length == other.min_pin_length 621 && self.prf == other.prf 622 } 623 } 624 /// The maximum number of bytes a [`UserHandle`] can be made of per 625 /// [WebAuthn](https://www.w3.org/TR/webauthn-3/#user-handle). 626 pub const USER_HANDLE_MAX_LEN: usize = 64; 627 /// The minimum number of bytes a [`UserHandle`] can be made of per 628 /// [WebAuthn](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id). 629 pub const USER_HANDLE_MIN_LEN: usize = 1; 630 /// A [user handle](https://www.w3.org/TR/webauthn-3/#user-handle) that is made up of 631 /// [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] bytes. 632 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 633 pub struct UserHandle<const LEN: usize>([u8; LEN]); 634 impl<const LEN: usize> UserHandle<LEN> { 635 /// Returns the contained data as a `slice`. 636 #[inline] 637 #[must_use] 638 pub const fn as_slice(&self) -> &[u8] { 639 self.0.as_slice() 640 } 641 /// Returns the contained data as a shared reference to the array. 642 #[inline] 643 #[must_use] 644 pub const fn as_array(&self) -> &[u8; LEN] { 645 &self.0 646 } 647 /// Returns the contained data. 648 #[inline] 649 #[must_use] 650 pub const fn into_array(self) -> [u8; LEN] { 651 self.0 652 } 653 } 654 /// Implements [`Default`] for [`UserHandle`] of array of length of the passed `usize` literal. 655 /// 656 /// Only [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively are allowed to be passed. 657 macro_rules! user { 658 ( $( $x:literal),* ) => { 659 $( 660 impl Default for UserHandle<$x> { 661 #[inline] 662 fn default() -> Self { 663 let mut data = [0; $x]; 664 rand::fill(data.as_mut_slice()); 665 Self(data) 666 } 667 } 668 )* 669 }; 670 } 671 // MUST only pass [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively. 672 user!( 673 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 674 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 675 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 676 ); 677 impl<const LEN: usize> UserHandle<LEN> 678 where 679 Self: Default, 680 { 681 /// Returns a new `UserHandle` based on `LEN` randomly-generated [`u8`]s. 682 /// 683 /// # Examples 684 /// 685 /// ``` 686 /// # use webauthn_rp::request::register::{UserHandle, UserHandle64, USER_HANDLE_MIN_LEN, USER_HANDLE_MAX_LEN}; 687 /// assert_eq!( 688 /// UserHandle::<USER_HANDLE_MIN_LEN>::new() 689 /// .as_ref() 690 /// .len(), 691 /// 1 692 /// ); 693 /// // The probability of an all-zero `UserHandle` being generated (assuming a good entropy 694 /// // source) is 2^-512 ≈ 7.5 x 10^-155. 695 /// assert_ne!( 696 /// UserHandle64::new().as_ref(), 697 /// [0; USER_HANDLE_MAX_LEN] 698 /// ); 699 /// ``` 700 #[inline] 701 #[must_use] 702 pub fn new() -> Self { 703 Self::default() 704 } 705 } 706 impl<const LEN: usize> AsRef<[u8]> for UserHandle<LEN> { 707 #[inline] 708 fn as_ref(&self) -> &[u8] { 709 self.as_slice() 710 } 711 } 712 impl<const LEN: usize> Borrow<[u8]> for UserHandle<LEN> { 713 #[inline] 714 fn borrow(&self) -> &[u8] { 715 self.as_slice() 716 } 717 } 718 impl<const LEN: usize> PartialEq<&Self> for UserHandle<LEN> { 719 #[inline] 720 fn eq(&self, other: &&Self) -> bool { 721 *self == **other 722 } 723 } 724 impl<const LEN: usize> PartialEq<UserHandle<LEN>> for &UserHandle<LEN> { 725 #[inline] 726 fn eq(&self, other: &UserHandle<LEN>) -> bool { 727 **self == *other 728 } 729 } 730 impl<const LEN: usize> From<UserHandle<LEN>> for [u8; LEN] { 731 #[inline] 732 fn from(value: UserHandle<LEN>) -> Self { 733 value.into_array() 734 } 735 } 736 impl<'a: 'b, 'b, const LEN: usize> From<&'a UserHandle<LEN>> for &'b [u8; LEN] { 737 #[inline] 738 fn from(value: &'a UserHandle<LEN>) -> Self { 739 value.as_array() 740 } 741 } 742 /// `UserHandle` that is based on the [spec recommendation](https://www.w3.org/TR/webauthn-3/#user-handle). 743 pub type UserHandle64 = UserHandle<USER_HANDLE_MAX_LEN>; 744 /// `UserHandle` that is based on 16 bytes. 745 /// 746 /// While not the recommended size like [`UserHandle64`], 16 bytes is common for many deployments since 747 /// it's the same size as [Universally Unique IDentifiers (UUIDs)](https://www.rfc-editor.org/rfc/rfc9562). 748 pub type UserHandle16 = UserHandle<16>; 749 impl UserHandle16 { 750 /// Same as [`Self::new`] except 6 bits of metadata is encoded to conform with 751 /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4). 752 /// 753 /// # Examples 754 /// 755 /// ``` 756 /// # use webauthn_rp::request::register::UserHandle16; 757 /// assert!(UserHandle16::new_uuid_v4().is_uuid_v4()); 758 /// ``` 759 #[inline] 760 #[must_use] 761 pub fn new_uuid_v4() -> Self { 762 let mut this = Self::new(); 763 // The first 4 bits of the 6th octet (0-based index) represents the UUID version (i.e., 4). 764 // We first 0-out the version bits retaining the other 4 bits, then set the version bits to 4. 765 this.0[6] = (this.0[6] & 0x0F) | 0x40; 766 // The first 2 bits of the 8th octet (0-based index) represents the UUID variant (i.e., 8,9,A,B) 767 // which is defined to be 2. 768 // We first 0-out the variant bits retaining the other 6 bits, then set the variant bits to 2. 769 this.0[8] = (this.0[8] & 0x3F) | 0x80; 770 this 771 } 772 /// Returns `true` iff `self` is a valid 773 /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4). 774 /// 775 /// # Examples 776 /// 777 /// ``` 778 /// # use webauthn_rp::request::register::UserHandle16; 779 /// assert!(UserHandle16::new_uuid_v4().is_uuid_v4()); 780 /// let mut user = UserHandle16::new_uuid_v4().into_array(); 781 /// user[6] = 255; 782 /// # #[cfg(feature = "custom")] 783 /// assert!(!UserHandle16::from(user).is_uuid_v4()); 784 /// ``` 785 #[inline] 786 #[must_use] 787 pub const fn is_uuid_v4(&self) -> bool { 788 // The first 4 bits of the 6th octet (0-based index) represents the UUID version (i.e., 4). 789 // The first 2 bits of the 8th octet (0-based index) represents the UUID variant (i.e., 8,9,A,B) which 790 // is defined to be 2. 791 self.0[6] >> 4 == 0x4 && self.0[8] >> 6 == 0x2 792 } 793 /// Returns `Some` containing `uuid_v4` iff `uuid_v4` is a valid 794 /// [UUID Version 4](https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-4). 795 /// 796 /// # Examples 797 /// 798 /// ``` 799 /// # use webauthn_rp::request::register::UserHandle16; 800 /// let mut user = UserHandle16::new_uuid_v4().into_array(); 801 /// assert!(UserHandle16::from_uuid_v4(user).is_some()); 802 /// user[8] = 255; 803 /// assert!(UserHandle16::from_uuid_v4(user).is_none()); 804 /// ``` 805 #[cfg(feature = "custom")] 806 #[inline] 807 #[must_use] 808 pub fn from_uuid_v4(uuid_v4: [u8; 16]) -> Option<Self> { 809 let this = Self(uuid_v4); 810 this.is_uuid_v4().then_some(this) 811 } 812 } 813 /// [The `PublicKeyCredentialUserEntity`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentity) 814 /// sent to the client. 815 #[derive(Clone, Debug)] 816 pub struct PublicKeyCredentialUserEntity<'name, 'display_name, 'id, const LEN: usize> { 817 /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name). 818 /// 819 /// Note the spec recommends RPs enforce 820 /// [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265#section-3.4.3). 821 pub name: &'name str, 822 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id). 823 pub id: &'id UserHandle<LEN>, 824 /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname). 825 /// 826 /// Note the spec recommends RPs enforce [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266#section-2.3) 827 /// when this isn't empty. 828 pub display_name: &'display_name str, 829 } 830 /// `PublicKeyCredentialUserEntity` based on a [`UserHandle64`]. 831 pub type PublicKeyCredentialUserEntity64<'name, 'display_name, 'id> = 832 PublicKeyCredentialUserEntity<'name, 'display_name, 'id, USER_HANDLE_MAX_LEN>; 833 /// `PublicKeyCredentialUserEntity` based on a [`UserHandle16`]. 834 pub type PublicKeyCredentialUserEntity16<'name, 'display_name, 'id> = 835 PublicKeyCredentialUserEntity<'name, 'display_name, 'id, 16>; 836 /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement) sent to the client. 837 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 838 pub enum ResidentKeyRequirement { 839 /// [`required`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-required). 840 Required, 841 /// [`discouraged`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-discouraged). 842 Discouraged, 843 /// [`preferred`](https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-preferred). 844 Preferred, 845 } 846 impl AuthenticatorAttachment { 847 /// Validates `self` against `other` based on `require_response`. 848 const fn validate(self, require_response: bool, other: Self) -> Result<(), RegCeremonyErr> { 849 match self { 850 Self::None => { 851 if require_response && matches!(other, Self::None) { 852 Err(RegCeremonyErr::MissingAuthenticatorAttachment) 853 } else { 854 Ok(()) 855 } 856 } 857 Self::Platform => match other { 858 Self::None => { 859 if require_response { 860 Err(RegCeremonyErr::MissingAuthenticatorAttachment) 861 } else { 862 Ok(()) 863 } 864 } 865 Self::Platform => Ok(()), 866 Self::CrossPlatform => Err(RegCeremonyErr::AuthenticatorAttachmentMismatch), 867 }, 868 Self::CrossPlatform => match other { 869 Self::None => { 870 if require_response { 871 Err(RegCeremonyErr::MissingAuthenticatorAttachment) 872 } else { 873 Ok(()) 874 } 875 } 876 Self::CrossPlatform => Ok(()), 877 Self::Platform => Err(RegCeremonyErr::AuthenticatorAttachmentMismatch), 878 }, 879 } 880 } 881 } 882 /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictionary-authenticatorSelection). 883 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 884 pub struct AuthenticatorSelectionCriteria { 885 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-authenticatorattachment). 886 pub authenticator_attachment: AuthenticatorAttachment, 887 /// [`residentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey). 888 pub resident_key: ResidentKeyRequirement, 889 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-userverification). 890 pub user_verification: UserVerificationRequirement, 891 } 892 impl AuthenticatorSelectionCriteria { 893 /// Returns an `AuthenticatorSelectionCriteria` useful for passkeys (i.e., [`Self::resident_key`] is set to 894 /// [`ResidentKeyRequirement::Required`] and [`Self::user_verification`] is set to 895 /// [`UserVerificationRequirement::Required`]). 896 /// 897 /// # Examples 898 /// 899 /// ``` 900 /// # use webauthn_rp::{request::{ 901 /// # register::{ 902 /// # AuthenticatorSelectionCriteria, ResidentKeyRequirement, 903 /// # }, 904 /// # UserVerificationRequirement, 905 /// # }, response::AuthenticatorAttachment}; 906 /// let crit = AuthenticatorSelectionCriteria::passkey(); 907 /// assert_eq!(crit.authenticator_attachment, AuthenticatorAttachment::None); 908 /// assert_eq!(crit.resident_key, ResidentKeyRequirement::Required); 909 /// assert_eq!(crit.user_verification, UserVerificationRequirement::Required); 910 /// ``` 911 #[inline] 912 #[must_use] 913 pub fn passkey() -> Self { 914 Self { 915 authenticator_attachment: AuthenticatorAttachment::default(), 916 resident_key: ResidentKeyRequirement::Required, 917 user_verification: UserVerificationRequirement::Required, 918 } 919 } 920 /// Returns an `AuthenticatorSelectionCriteria` useful for second-factor flows (i.e., [`Self::resident_key`] 921 /// is set to [`ResidentKeyRequirement::Discouraged`] and [`Self::user_verification`] is set to 922 /// [`UserVerificationRequirement::Discouraged`]). 923 /// 924 /// Note some authenticators require user verification during credential registration (e.g., 925 /// [CTAP 2.0 authenticators](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorMakeCredential)). 926 /// When an authenticator supports both CTAP 2.0 and 927 /// [Universal 2nd Factor (U2F)](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html#registration-creating-a-key-pair) 928 /// protocols, user agents will sometimes fall back to U2F when `UserVerificationRequirement::Discouraged` 929 /// is requested since the latter allows for registration without user verification. If the user agent does not 930 /// do this, then users will have an inconsistent experience when authenticating an already-registered 931 /// credential. If this is undesirable, one can use [`UserVerificationRequirement::Required`] for this and 932 /// [`PublicKeyCredentialRequestOptions::user_verification`] at the expense of requiring a user to verify 933 /// themselves twice: once for the first factor and again here. 934 /// 935 /// # Examples 936 /// 937 /// ``` 938 /// # use webauthn_rp::{request::{ 939 /// # register::{ 940 /// # AuthenticatorSelectionCriteria, ResidentKeyRequirement, 941 /// # }, 942 /// # UserVerificationRequirement, 943 /// # }, response::AuthenticatorAttachment}; 944 /// let crit = AuthenticatorSelectionCriteria::second_factor(); 945 /// assert_eq!(crit.authenticator_attachment, AuthenticatorAttachment::None); 946 /// assert_eq!(crit.resident_key, ResidentKeyRequirement::Discouraged); 947 /// assert_eq!(crit.user_verification, UserVerificationRequirement::Discouraged); 948 /// ``` 949 #[inline] 950 #[must_use] 951 pub fn second_factor() -> Self { 952 Self { 953 authenticator_attachment: AuthenticatorAttachment::default(), 954 resident_key: ResidentKeyRequirement::Discouraged, 955 user_verification: UserVerificationRequirement::Discouraged, 956 } 957 } 958 /// Ensures a client-side credential was created when applicable. Also enforces `auth_attachment` when 959 /// applicable. 960 const fn validate( 961 self, 962 require_auth_attachment: bool, 963 auth_attachment: AuthenticatorAttachment, 964 ) -> Result<(), RegCeremonyErr> { 965 self.authenticator_attachment 966 .validate(require_auth_attachment, auth_attachment) 967 } 968 } 969 /// Helper that verifies the overlap of [`CredentialCreationOptions::start_ceremony`] and 970 /// [`RegistrationServerState::decode`]. 971 const fn validate_options_helper( 972 auth_crit: AuthenticatorSelectionCriteria, 973 extensions: ServerExtensionInfo, 974 ) -> Result<(), CreationOptionsErr> { 975 if matches!( 976 auth_crit.user_verification, 977 UserVerificationRequirement::Required 978 ) { 979 Ok(()) 980 } else if matches!( 981 extensions.cred_protect, 982 CredProtect::UserVerificationRequired(_, _) 983 ) { 984 Err(CreationOptionsErr::CredProtectRequiredWithoutUserVerification) 985 } else { 986 Ok(()) 987 } 988 } 989 /// The [`CredentialCreationOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialcreationoptions) 990 /// to send to the client when registering a new credential. 991 /// 992 /// Upon saving the [`RegistrationServerState`] returned from [`Self::start_ceremony`], one MUST send 993 /// [`RegistrationClientState`] to the client ASAP. After receiving the newly created [`Registration`], it is 994 /// validated using [`RegistrationServerState::verify`]. 995 #[derive(Debug)] 996 pub struct CredentialCreationOptions< 997 'rp_id, 998 'user_name, 999 'user_display_name, 1000 'user_id, 1001 'prf_first, 1002 'prf_second, 1003 const USER_LEN: usize, 1004 > { 1005 /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-mediation). 1006 /// 1007 /// Note if this is [`CredentialMediationRequirement::Conditional`], one may want to ensure 1008 /// [`AuthenticatorSelectionCriteria::user_verification`] is not [`UserVerificationRequirement::Required`] 1009 /// since some authenticators cannot enforce user verification during registration ceremonies when conditional 1010 /// mediation is used. Do note that in the event passkeys are to be created, one may want to set 1011 /// [`AuthenticationVerificationOptions::update_uv`] to `true` since [`Flag::user_verified`] will 1012 /// potentially be `false`. 1013 pub mediation: CredentialMediationRequirement, 1014 /// `public-key` [credential type](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry). 1015 pub public_key: PublicKeyCredentialCreationOptions< 1016 'rp_id, 1017 'user_name, 1018 'user_display_name, 1019 'user_id, 1020 'prf_first, 1021 'prf_second, 1022 USER_LEN, 1023 >, 1024 } 1025 impl< 1026 'rp_id, 1027 'user_name, 1028 'user_display_name, 1029 'user_id, 1030 'prf_first, 1031 'prf_second, 1032 const USER_LEN: usize, 1033 > 1034 CredentialCreationOptions< 1035 'rp_id, 1036 'user_name, 1037 'user_display_name, 1038 'user_id, 1039 'prf_first, 1040 'prf_second, 1041 USER_LEN, 1042 > 1043 { 1044 /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and 1045 /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::passkey`]. 1046 #[expect(single_use_lifetimes, reason = "false positive")] 1047 #[inline] 1048 #[must_use] 1049 pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( 1050 rp_id: &'a RpId, 1051 user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>, 1052 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 1053 ) -> Self { 1054 Self { 1055 mediation: CredentialMediationRequirement::default(), 1056 public_key: PublicKeyCredentialCreationOptions::passkey( 1057 rp_id, 1058 user, 1059 exclude_credentials, 1060 ), 1061 } 1062 } 1063 /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and 1064 /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::second_factor`]. 1065 #[expect(single_use_lifetimes, reason = "false positive")] 1066 #[inline] 1067 #[must_use] 1068 pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( 1069 rp_id: &'a RpId, 1070 user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>, 1071 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 1072 ) -> Self { 1073 let mut opts = Self::passkey(rp_id, user, exclude_credentials); 1074 opts.public_key.authenticator_selection = AuthenticatorSelectionCriteria::second_factor(); 1075 opts.public_key.extensions.cred_props = Some(ExtensionReq::Allow); 1076 opts.public_key.extensions.cred_protect = 1077 CredProtect::UserVerificationOptionalWithCredentialIdList( 1078 false, 1079 ExtensionInfo::AllowEnforceValue, 1080 ); 1081 opts 1082 } 1083 /// Begins the [registration ceremony](https://www.w3.org/TR/webauthn-3/#registration-ceremony) consuming 1084 /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `RegistrationClientState` MUST be 1085 /// sent ASAP. In order to complete registration, the returned `RegistrationServerState` MUST be saved so that 1086 /// it can later be used to verify the new credential with [`RegistrationServerState::verify`]. 1087 /// 1088 /// # Errors 1089 /// 1090 /// Errors iff `self` contains incompatible configuration. 1091 /// 1092 /// # Examples 1093 /// 1094 /// ``` 1095 /// # #[cfg(not(feature = "serializable_server_state"))] 1096 /// # use std::time::Instant; 1097 /// # #[cfg(not(feature = "serializable_server_state"))] 1098 /// # use webauthn_rp::request::TimedCeremony as _; 1099 /// # use webauthn_rp::request::{ 1100 /// # register::{CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64}, 1101 /// # AsciiDomain, RpId 1102 /// # }; 1103 /// # #[cfg(not(feature = "serializable_server_state"))] 1104 /// assert!( 1105 /// CredentialCreationOptions::passkey( 1106 /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 1107 /// PublicKeyCredentialUserEntity { 1108 /// name: "bernard.riemann", 1109 /// id: &UserHandle64::new(), 1110 /// display_name: "Georg Friedrich Bernhard Riemann", 1111 /// }, 1112 /// Vec::new() 1113 /// ).start_ceremony()?.0.expiration() > Instant::now() 1114 /// ); 1115 /// # Ok::<_, webauthn_rp::AggErr>(()) 1116 /// ``` 1117 #[inline] 1118 pub fn start_ceremony( 1119 mut self, 1120 ) -> Result< 1121 ( 1122 RegistrationServerState<USER_LEN>, 1123 RegistrationClientState< 1124 'rp_id, 1125 'user_name, 1126 'user_display_name, 1127 'user_id, 1128 'prf_first, 1129 'prf_second, 1130 USER_LEN, 1131 >, 1132 ), 1133 CreationOptionsErr, 1134 > { 1135 let extensions = self.public_key.extensions.into(); 1136 validate_options_helper(self.public_key.authenticator_selection, extensions).and_then( 1137 |()| { 1138 match self 1139 .public_key 1140 .authenticator_selection 1141 .authenticator_attachment 1142 { 1143 AuthenticatorAttachment::None => Ok(()), 1144 AuthenticatorAttachment::Platform => { 1145 if self.public_key.hints.contains_cross_platform_hints() { 1146 Err(CreationOptionsErr::HintsIncompatibleWithAuthAttachment) 1147 } else { 1148 Ok(()) 1149 } 1150 } 1151 AuthenticatorAttachment::CrossPlatform => { 1152 if self.public_key.hints.contains_platform_hints() { 1153 Err(CreationOptionsErr::HintsIncompatibleWithAuthAttachment) 1154 } else { 1155 Ok(()) 1156 } 1157 } 1158 } 1159 .and_then(|()| { 1160 #[cfg(not(feature = "serializable_server_state"))] 1161 let now = Instant::now(); 1162 #[cfg(feature = "serializable_server_state")] 1163 let now = SystemTime::now(); 1164 now.checked_add(Duration::from_millis( 1165 NonZeroU64::from(self.public_key.timeout).get(), 1166 )) 1167 .ok_or(CreationOptionsErr::InvalidTimeout) 1168 .map(|expiration| { 1169 // We remove duplicates. The order has no significance, so this is OK. 1170 self.public_key 1171 .exclude_credentials 1172 .sort_unstable_by(|a, b| a.id.as_ref().cmp(b.id.as_ref())); 1173 self.public_key 1174 .exclude_credentials 1175 .dedup_by(|a, b| a.id.as_ref() == b.id.as_ref()); 1176 ( 1177 RegistrationServerState { 1178 mediation: self.mediation, 1179 challenge: SentChallenge(self.public_key.challenge.0), 1180 pub_key_cred_params: self.public_key.pub_key_cred_params, 1181 authenticator_selection: self.public_key.authenticator_selection, 1182 extensions, 1183 expiration, 1184 user_id: *self.public_key.user.id, 1185 }, 1186 RegistrationClientState(self), 1187 ) 1188 }) 1189 }) 1190 }, 1191 ) 1192 } 1193 } 1194 /// `CredentialCreationOptions` based on a [`UserHandle64`]. 1195 pub type CredentialCreationOptions64< 1196 'rp_id, 1197 'user_name, 1198 'user_display_name, 1199 'user_id, 1200 'prf_first, 1201 'prf_second, 1202 > = CredentialCreationOptions< 1203 'rp_id, 1204 'user_name, 1205 'user_display_name, 1206 'user_id, 1207 'prf_first, 1208 'prf_second, 1209 USER_HANDLE_MAX_LEN, 1210 >; 1211 /// `CredentialCreationOptions` based on a [`UserHandle16`]. 1212 pub type CredentialCreationOptions16< 1213 'rp_id, 1214 'user_name, 1215 'user_display_name, 1216 'user_id, 1217 'prf_first, 1218 'prf_second, 1219 > = CredentialCreationOptions< 1220 'rp_id, 1221 'user_name, 1222 'user_display_name, 1223 'user_id, 1224 'prf_first, 1225 'prf_second, 1226 16, 1227 >; 1228 /// The [`PublicKeyCredentialCreationOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptions) 1229 /// to send to the client when registering a new credential. 1230 #[derive(Debug)] 1231 pub struct PublicKeyCredentialCreationOptions< 1232 'rp_id, 1233 'user_name, 1234 'user_display_name, 1235 'user_id, 1236 'prf_first, 1237 'prf_second, 1238 const USER_LEN: usize, 1239 > { 1240 /// [`rp`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-rp). 1241 pub rp_id: &'rp_id RpId, 1242 /// [`user`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-user). 1243 pub user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>, 1244 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-challenge). 1245 pub challenge: Challenge, 1246 /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams). 1247 pub pub_key_cred_params: CoseAlgorithmIdentifiers, 1248 /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-timeout). 1249 /// 1250 /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives 1251 /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled 1252 /// when attesting credentials as no timeout would make out-of-memory (OOM) conditions more likely. 1253 pub timeout: NonZeroU32, 1254 /// [`excludeCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-excludecredentials). 1255 pub exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 1256 /// [`authenticatorSelection`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-authenticatorselection). 1257 pub authenticator_selection: AuthenticatorSelectionCriteria, 1258 /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-hints). 1259 pub hints: Hints, 1260 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-extensions). 1261 pub extensions: Extension<'prf_first, 'prf_second>, 1262 } 1263 impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> 1264 PublicKeyCredentialCreationOptions< 1265 'rp_id, 1266 'user_name, 1267 'user_display_name, 1268 'user_id, 1269 '_, 1270 '_, 1271 USER_LEN, 1272 > 1273 { 1274 /// Most deployments of passkeys should use this function. Specifically deployments that are both userless and 1275 /// passwordless and desire multi-factor authentication (MFA) to be done entirely on the authenticator. It 1276 /// is important `exclude_credentials` contains the information for _all_ [`RegisteredCredential`]s registered to 1277 /// [`PublicKeyCredentialUserEntity::id`] to avoid accidentally overwriting existing credentials that 1278 /// have been previously registered. 1279 /// 1280 /// Creates a `PublicKeyCredentialCreationOptions` that requires the authenticator to create a client-side 1281 /// discoverable credential enforcing any form of user verification. [`Self::timeout`] is [`FIVE_MINUTES`]. 1282 /// [`Extension::cred_protect`] with [`CredProtect::UserVerificationRequired`] with `false` and 1283 /// [`ExtensionInfo::AllowEnforceValue`] is used. 1284 /// 1285 /// # Examples 1286 /// 1287 /// ``` 1288 /// # use webauthn_rp::request::{ 1289 /// # register::{ 1290 /// # PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64 1291 /// # }, 1292 /// # AsciiDomain, RpId, UserVerificationRequirement 1293 /// # }; 1294 /// assert!(matches!( 1295 /// PublicKeyCredentialCreationOptions::passkey( 1296 /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 1297 /// PublicKeyCredentialUserEntity { 1298 /// name: "archimedes.of.syracuse", 1299 /// id: &UserHandle64::new(), 1300 /// display_name: "Αρχιμήδης ο Συρακούσιος", 1301 /// }, 1302 /// Vec::new() 1303 /// ) 1304 /// .authenticator_selection.user_verification, UserVerificationRequirement::Required 1305 /// )); 1306 /// # Ok::<_, webauthn_rp::AggErr>(()) 1307 /// ``` 1308 #[expect(single_use_lifetimes, reason = "false positive")] 1309 #[inline] 1310 #[must_use] 1311 pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( 1312 rp_id: &'a RpId, 1313 user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>, 1314 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 1315 ) -> Self { 1316 Self { 1317 rp_id, 1318 user, 1319 challenge: Challenge::new(), 1320 pub_key_cred_params: CoseAlgorithmIdentifiers::default(), 1321 timeout: FIVE_MINUTES, 1322 exclude_credentials, 1323 authenticator_selection: AuthenticatorSelectionCriteria::passkey(), 1324 hints: Hints::EMPTY, 1325 extensions: Extension { 1326 cred_props: None, 1327 cred_protect: CredProtect::UserVerificationRequired( 1328 false, 1329 ExtensionInfo::AllowEnforceValue, 1330 ), 1331 min_pin_length: None, 1332 prf: None, 1333 }, 1334 } 1335 } 1336 /// Deployments that want to incorporate a "something a user has" factor into a larger multi-factor 1337 /// authentication (MFA) setup. Specifically deployments that are _not_ userless or passwordless. It 1338 /// is important `exclude_credentials` contains the information for _all_ [`RegisteredCredential`]s registered 1339 /// to [`PublicKeyCredentialUserEntity::id`] to avoid accidentally overwriting existing credentials that 1340 /// have been previously registered. 1341 /// 1342 /// Creates a `PublicKeyCredentialCreationOptions` that prefers the authenticator to create a server-side 1343 /// credential without requiring user verification. [`Self::timeout`] is [`FIVE_MINUTES`]. 1344 /// [`Extension::cred_props`] is [`ExtensionReq::Allow`]. [`Extension::cred_protect`] is 1345 /// [`CredProtect::UserVerificationOptionalWithCredentialIdList`] with `false` and 1346 /// [`ExtensionInfo::AllowEnforceValue`]. 1347 /// 1348 /// Note some authenticators require user verification during credential registration (e.g., 1349 /// [CTAP 2.0 authenticators](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorMakeCredential)). 1350 /// When an authenticator supports both CTAP 2.0 and 1351 /// [Universal 2nd Factor (U2F)](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html#registration-creating-a-key-pair) 1352 /// protocols, user agents will sometimes fall back to U2F when [`UserVerificationRequirement::Discouraged`] 1353 /// is requested since the latter allows for registration without user verification. If the user agent does not 1354 /// do this, then users will have an inconsistent experience when authenticating an already-registered 1355 /// credential. If this is undesirable, one can use [`UserVerificationRequirement::Required`] for this and 1356 /// [`PublicKeyCredentialRequestOptions::user_verification`] at the expense of requiring a user to verify 1357 /// themselves twice: once for the first factor and again here. 1358 /// 1359 /// # Examples 1360 /// 1361 /// ``` 1362 /// # use webauthn_rp::request::{register::{ 1363 /// # PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle64 1364 /// # }, AsciiDomain, RpId}; 1365 /// assert_eq!( 1366 /// PublicKeyCredentialCreationOptions::second_factor( 1367 /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 1368 /// PublicKeyCredentialUserEntity { 1369 /// name: "carl.gauss", 1370 /// id: &UserHandle64::new(), 1371 /// display_name: "Johann Carl Friedrich Gauß", 1372 /// }, 1373 /// Vec::new() 1374 /// ) 1375 /// .timeout 1376 /// .get(), 1377 /// 300_000 1378 /// ); 1379 /// # Ok::<_, webauthn_rp::AggErr>(()) 1380 /// ``` 1381 #[expect(single_use_lifetimes, reason = "false positive")] 1382 #[inline] 1383 #[must_use] 1384 pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( 1385 rp_id: &'a RpId, 1386 user: PublicKeyCredentialUserEntity<'b, 'c, 'd, USER_LEN>, 1387 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 1388 ) -> Self { 1389 let mut opts = Self::passkey(rp_id, user, exclude_credentials); 1390 opts.authenticator_selection = AuthenticatorSelectionCriteria::second_factor(); 1391 opts.extensions.cred_props = Some(ExtensionReq::Allow); 1392 opts.extensions.cred_protect = CredProtect::UserVerificationOptionalWithCredentialIdList( 1393 false, 1394 ExtensionInfo::AllowEnforceValue, 1395 ); 1396 opts 1397 } 1398 } 1399 /// `PublicKeyCredentialCreationOptions` based on a [`UserHandle64`]. 1400 pub type PublicKeyCredentialCreationOptions64< 1401 'rp_id, 1402 'user_name, 1403 'user_display_name, 1404 'user_id, 1405 'prf_first, 1406 'prf_second, 1407 > = PublicKeyCredentialCreationOptions< 1408 'rp_id, 1409 'user_name, 1410 'user_display_name, 1411 'user_id, 1412 'prf_first, 1413 'prf_second, 1414 USER_HANDLE_MAX_LEN, 1415 >; 1416 /// `PublicKeyCredentialCreationOptions` based on a [`UserHandle16`]. 1417 pub type PublicKeyCredentialCreationOptions16< 1418 'rp_id, 1419 'user_name, 1420 'user_display_name, 1421 'user_id, 1422 'prf_first, 1423 'prf_second, 1424 > = PublicKeyCredentialCreationOptions< 1425 'rp_id, 1426 'user_name, 1427 'user_display_name, 1428 'user_id, 1429 'prf_first, 1430 'prf_second, 1431 16, 1432 >; 1433 /// Container of a [`CredentialCreationOptions`] that has been used to start the registration ceremony. 1434 /// This gets sent to the client ASAP. 1435 #[derive(Debug)] 1436 pub struct RegistrationClientState< 1437 'rp_id, 1438 'user_name, 1439 'user_display_name, 1440 'user_id, 1441 'prf_first, 1442 'prf_second, 1443 const USER_LEN: usize, 1444 >( 1445 CredentialCreationOptions< 1446 'rp_id, 1447 'user_name, 1448 'user_display_name, 1449 'user_id, 1450 'prf_first, 1451 'prf_second, 1452 USER_LEN, 1453 >, 1454 ); 1455 impl< 1456 'rp_id, 1457 'user_name, 1458 'user_display_name, 1459 'user_id, 1460 'prf_first, 1461 'prf_second, 1462 const USER_LEN: usize, 1463 > 1464 RegistrationClientState< 1465 'rp_id, 1466 'user_name, 1467 'user_display_name, 1468 'user_id, 1469 'prf_first, 1470 'prf_second, 1471 USER_LEN, 1472 > 1473 { 1474 /// Returns the `CredentialCreationOptions` that was used to start a registration ceremony. 1475 /// 1476 /// # Examples 1477 /// 1478 /// ``` 1479 /// # use webauthn_rp::request::{register::{ 1480 /// # CoseAlgorithmIdentifiers, CredentialCreationOptions, 1481 /// # PublicKeyCredentialUserEntity, UserHandle64, 1482 /// # }, AsciiDomain, RpId}; 1483 /// assert_eq!( 1484 /// CredentialCreationOptions::passkey( 1485 /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 1486 /// PublicKeyCredentialUserEntity { 1487 /// name: "david.hilbert", 1488 /// id: &UserHandle64::new(), 1489 /// display_name: "David Hilbert", 1490 /// }, 1491 /// Vec::new() 1492 /// ) 1493 /// .start_ceremony()? 1494 /// .1 1495 /// .options() 1496 /// .public_key 1497 /// .rp_id.as_ref(), 1498 /// "example.com" 1499 /// ); 1500 /// # Ok::<_, webauthn_rp::AggErr>(()) 1501 /// ``` 1502 #[inline] 1503 #[must_use] 1504 pub const fn options( 1505 &self, 1506 ) -> &CredentialCreationOptions< 1507 'rp_id, 1508 'user_name, 1509 'user_display_name, 1510 'user_id, 1511 'prf_first, 1512 'prf_second, 1513 USER_LEN, 1514 > { 1515 &self.0 1516 } 1517 } 1518 /// `RegistrationClientState` based on a [`UserHandle64`]. 1519 pub type RegistrationClientState64< 1520 'rp_id, 1521 'user_name, 1522 'user_display_name, 1523 'user_id, 1524 'prf_first, 1525 'prf_second, 1526 > = RegistrationClientState< 1527 'rp_id, 1528 'user_name, 1529 'user_display_name, 1530 'user_id, 1531 'prf_first, 1532 'prf_second, 1533 USER_HANDLE_MAX_LEN, 1534 >; 1535 /// `RegistrationClientState` based on a [`UserHandle16`]. 1536 pub type RegistrationClientState16< 1537 'rp_id, 1538 'user_name, 1539 'user_display_name, 1540 'user_id, 1541 'prf_first, 1542 'prf_second, 1543 > = RegistrationClientState< 1544 'rp_id, 1545 'user_name, 1546 'user_display_name, 1547 'user_id, 1548 'prf_first, 1549 'prf_second, 1550 16, 1551 >; 1552 /// Additional verification options to perform in [`RegistrationServerState::verify`]. 1553 #[derive(Clone, Copy, Debug)] 1554 pub struct RegistrationVerificationOptions<'origins, 'top_origins, O, T> { 1555 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1556 /// 1557 /// When this is empty, the origin that will be used will be based on 1558 /// the [`RpId`] passed to [`RegistrationServerState::verify`]. If [`RpId::Domain`] or [`RpId::StaticDomain`], 1559 /// then the [`DomainOrigin`] returned from passing [`AsciiDomain::as_ref`] and [`AsciiDomainStatic::as_str`] 1560 /// to [`DomainOrigin::new`] respectively will be used; otherwise the [`Url`] in [`RpId::Url`] will be used. 1561 pub allowed_origins: &'origins [O], 1562 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 1563 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1564 /// 1565 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 1566 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 1567 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 1568 pub allowed_top_origins: Option<&'top_origins [T]>, 1569 /// The required [`Backup`] state of the credential. 1570 pub backup_requirement: BackupReq, 1571 /// Error when unsolicited extensions are sent back iff `true`. 1572 pub error_on_unsolicited_extensions: bool, 1573 /// [`AuthenticatorAttachment`] must be sent iff `true`. 1574 pub require_authenticator_attachment: bool, 1575 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 1576 #[cfg(feature = "serde_relaxed")] 1577 pub client_data_json_relaxed: bool, 1578 } 1579 impl<O, T> RegistrationVerificationOptions<'_, '_, O, T> { 1580 /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`, 1581 /// [`Self::backup_requirement`] is [`BackupReq::None`], [`Self::error_on_unsolicited_extensions`] is `true`, 1582 /// [`Self::require_authenticator_attachment`] is `false`, and [`Self::client_data_json_relaxed`] is 1583 /// `true`. 1584 /// 1585 /// Note `O` and `T` should implement `PartialEq<Origin<'_>>` (e.g., `&str`). 1586 #[inline] 1587 #[must_use] 1588 pub const fn new() -> Self { 1589 Self { 1590 allowed_origins: [].as_slice(), 1591 allowed_top_origins: None, 1592 backup_requirement: BackupReq::None, 1593 error_on_unsolicited_extensions: true, 1594 require_authenticator_attachment: false, 1595 #[cfg(feature = "serde_relaxed")] 1596 client_data_json_relaxed: true, 1597 } 1598 } 1599 } 1600 impl<O, T> Default for RegistrationVerificationOptions<'_, '_, O, T> { 1601 /// Same as [`Self::new`]. 1602 #[inline] 1603 fn default() -> Self { 1604 Self { 1605 allowed_origins: &[], 1606 allowed_top_origins: None, 1607 backup_requirement: BackupReq::default(), 1608 error_on_unsolicited_extensions: true, 1609 require_authenticator_attachment: false, 1610 #[cfg(feature = "serde_relaxed")] 1611 client_data_json_relaxed: true, 1612 } 1613 } 1614 } 1615 /// `PrfInput` without the actual data sent to reduce memory usage when storing [`RegistrationServerState`] in an 1616 /// in-memory collection. 1617 #[derive(Clone, Copy, Debug)] 1618 enum ServerPrfInfo { 1619 /// No `PrfInput`. 1620 None, 1621 /// `PrfInput::second` was `None`. 1622 One(ExtensionInfo), 1623 /// `PrfInput::second` was `Some`. 1624 Two(ExtensionInfo), 1625 } 1626 #[cfg(test)] 1627 impl PartialEq for ServerPrfInfo { 1628 fn eq(&self, other: &Self) -> bool { 1629 match *self { 1630 Self::None => matches!(*other, Self::None), 1631 Self::One(info) => matches!(*other, Self::One(info2) if info == info2), 1632 Self::Two(info) => matches!(*other, Self::Two(info2) if info == info2), 1633 } 1634 } 1635 } 1636 impl From<Option<(PrfInput<'_, '_>, ExtensionInfo)>> for ServerPrfInfo { 1637 fn from(value: Option<(PrfInput<'_, '_>, ExtensionInfo)>) -> Self { 1638 value.map_or(Self::None, |val| { 1639 val.0 1640 .second 1641 .map_or_else(|| Self::One(val.1), |_| Self::Two(val.1)) 1642 }) 1643 } 1644 } 1645 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 1646 /// in an in-memory collection. 1647 #[derive(Clone, Copy, Debug)] 1648 struct ServerExtensionInfo { 1649 /// `Extension::cred_props`. 1650 cred_props: Option<ExtensionReq>, 1651 /// `Extension::cred_protect`. 1652 cred_protect: CredProtect, 1653 /// `Extension::min_pin_length`. 1654 min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>, 1655 /// `Extension::prf`. 1656 prf: ServerPrfInfo, 1657 } 1658 impl ServerExtensionInfo { 1659 /// Validates the extensions. 1660 fn validate( 1661 self, 1662 client_ext: ClientExtensionsOutputs, 1663 auth_ext: AuthenticatorExtensionOutput, 1664 error_unsolicited: bool, 1665 ) -> Result<(), ExtensionErr> { 1666 if error_unsolicited { 1667 self.validate_unsolicited(client_ext, auth_ext) 1668 } else { 1669 Ok(()) 1670 } 1671 .and_then(|()| { 1672 self.validate_required(client_ext, auth_ext) 1673 .and_then(|()| self.validate_value(client_ext, auth_ext)) 1674 }) 1675 } 1676 /// Validates if there are any unsolicited extensions. 1677 /// 1678 /// Note no distinction is made between an extension that is empty and one that is not (i.e., we are checking 1679 /// purely for the existence of extension keys). 1680 fn validate_unsolicited( 1681 mut self, 1682 client_ext: ClientExtensionsOutputs, 1683 auth_ext: AuthenticatorExtensionOutput, 1684 ) -> Result<(), ExtensionErr> { 1685 // For simpler code, we artificially set non-requested extensions after verifying there was not an error 1686 // and recursively call this function. There are so few extensions and the checks are fast that there 1687 // should be no worry of stack overflow or performance overhead. 1688 if self.cred_props.is_some() { 1689 if !matches!(self.cred_protect, CredProtect::None) { 1690 if self.min_pin_length.is_some() { 1691 // This is the last extension, so recursion stops here. 1692 if !matches!(self.prf, ServerPrfInfo::None) { 1693 Ok(()) 1694 } else if client_ext.prf.is_some() { 1695 Err(ExtensionErr::ForbiddenPrf) 1696 } else if !matches!(auth_ext.hmac_secret, HmacSecret::None) { 1697 Err(ExtensionErr::ForbiddenHmacSecret) 1698 } else { 1699 Ok(()) 1700 } 1701 } else if auth_ext.min_pin_length.is_some() { 1702 Err(ExtensionErr::ForbiddenMinPinLength) 1703 } else { 1704 // Pretend to set `minPinLength`, so we can check `prf`. 1705 self.min_pin_length = 1706 Some((FourToSixtyThree::Four, ExtensionInfo::RequireEnforceValue)); 1707 self.validate_unsolicited(client_ext, auth_ext) 1708 } 1709 } else if !matches!(auth_ext.cred_protect, CredentialProtectionPolicy::None) { 1710 Err(ExtensionErr::ForbiddenCredProtect) 1711 } else { 1712 // Pretend to set `credProtect`, so we can check `minPinLength` and `prf` extensions. 1713 self.cred_protect = CredProtect::UserVerificationOptional( 1714 false, 1715 ExtensionInfo::RequireEnforceValue, 1716 ); 1717 self.validate_unsolicited(client_ext, auth_ext) 1718 } 1719 } else if client_ext.cred_props.is_some() { 1720 Err(ExtensionErr::ForbiddenCredProps) 1721 } else { 1722 // Pretend to set `credProps`; so we can check `credProtect`, `minPinLength`, and `prf` extensions. 1723 self.cred_props = Some(ExtensionReq::Require); 1724 self.validate_unsolicited(client_ext, auth_ext) 1725 } 1726 } 1727 /// Validates if any required extensions don't have a corresponding response. 1728 /// 1729 /// Note empty extensions are treated as missing. For example when requiring the `credProps` extension, 1730 /// all of the following responses would lead to a failure: 1731 /// `{"clientExtensionResults":{}}`: no extensions. 1732 /// `{"clientExtensionResults":{"prf":true}}`: only the `prf` extension. 1733 /// `{"clientExtensionResults":{"credProps":{}}}`: empty `credProps` extension. 1734 /// `{"clientExtensionResults":{"credProps":{"foo":false}}}`: `credProps` extension doesn't contain at least one 1735 /// expected field (i.e., still "empty"). 1736 fn validate_required( 1737 self, 1738 client_ext: ClientExtensionsOutputs, 1739 auth_ext: AuthenticatorExtensionOutput, 1740 ) -> Result<(), ExtensionErr> { 1741 // We don't check `self.cred_protect` since `CredProtect::validate` checks for both a required response 1742 // and value enforcement; thus it only needs to be checked once (which it is in `Self::validate_value`). 1743 self.cred_props 1744 .map_or(Ok(()), |info| { 1745 if matches!(info, ExtensionReq::Require) { 1746 if client_ext 1747 .cred_props 1748 .is_some_and(|props| props.rk.is_some()) 1749 { 1750 Ok(()) 1751 } else { 1752 Err(ExtensionErr::MissingCredProps) 1753 } 1754 } else { 1755 Ok(()) 1756 } 1757 }) 1758 .and_then(|()| { 1759 self.min_pin_length 1760 .map_or(Ok(()), |info| { 1761 if matches!( 1762 info.1, 1763 ExtensionInfo::RequireEnforceValue 1764 | ExtensionInfo::RequireDontEnforceValue 1765 ) { 1766 auth_ext 1767 .min_pin_length 1768 .ok_or(ExtensionErr::MissingMinPinLength) 1769 .map(|_| ()) 1770 } else { 1771 Ok(()) 1772 } 1773 }) 1774 .and_then(|()| match self.prf { 1775 ServerPrfInfo::None => Ok(()), 1776 ServerPrfInfo::One(info) | ServerPrfInfo::Two(info) => { 1777 if matches!( 1778 info, 1779 ExtensionInfo::RequireEnforceValue 1780 | ExtensionInfo::RequireDontEnforceValue 1781 ) { 1782 if client_ext.prf.is_some() { 1783 Ok(()) 1784 } else { 1785 Err(ExtensionErr::MissingPrf) 1786 } 1787 } else { 1788 Ok(()) 1789 } 1790 } 1791 }) 1792 }) 1793 } 1794 /// Validates the value of any extensions sent from the client. 1795 /// 1796 /// Note missing and empty extensions are always OK. 1797 fn validate_value( 1798 self, 1799 client_ext: ClientExtensionsOutputs, 1800 auth_ext: AuthenticatorExtensionOutput, 1801 ) -> Result<(), ExtensionErr> { 1802 // This also checks for a missing response. Instead of duplicating that check, we only call 1803 // `self.cred_protect.validate` once here and not also in `Self::validate_required`. 1804 self.cred_protect 1805 .validate(auth_ext.cred_protect) 1806 .and_then(|()| { 1807 self.min_pin_length 1808 .map_or(Ok(()), |info| { 1809 if matches!( 1810 info.1, 1811 ExtensionInfo::RequireEnforceValue | ExtensionInfo::AllowEnforceValue 1812 ) { 1813 auth_ext.min_pin_length.map_or(Ok(()), |pin| { 1814 if pin >= info.0 { 1815 Ok(()) 1816 } else { 1817 Err(ExtensionErr::InvalidMinPinLength(info.0, pin)) 1818 } 1819 }) 1820 } else { 1821 Ok(()) 1822 } 1823 }) 1824 .and_then(|()| match self.prf { 1825 ServerPrfInfo::None => Ok(()), 1826 ServerPrfInfo::One(info) | ServerPrfInfo::Two(info) => { 1827 if matches!( 1828 info, 1829 ExtensionInfo::RequireEnforceValue 1830 | ExtensionInfo::AllowEnforceValue 1831 ) { 1832 client_ext 1833 .prf 1834 .map_or(Ok(()), |prf| { 1835 if prf.enabled { 1836 Ok(()) 1837 } else { 1838 Err(ExtensionErr::InvalidPrfValue) 1839 } 1840 }) 1841 .and({ 1842 if matches!(auth_ext.hmac_secret, HmacSecret::NotEnabled) { 1843 Err(ExtensionErr::InvalidHmacSecretValue) 1844 } else { 1845 Ok(()) 1846 } 1847 }) 1848 } else { 1849 Ok(()) 1850 } 1851 } 1852 }) 1853 }) 1854 } 1855 } 1856 impl From<Extension<'_, '_>> for ServerExtensionInfo { 1857 fn from(value: Extension<'_, '_>) -> Self { 1858 Self { 1859 cred_props: value.cred_props, 1860 cred_protect: value.cred_protect, 1861 min_pin_length: value.min_pin_length, 1862 prf: value.prf.into(), 1863 } 1864 } 1865 } 1866 #[cfg(test)] 1867 impl PartialEq for ServerExtensionInfo { 1868 fn eq(&self, other: &Self) -> bool { 1869 self.prf == other.prf 1870 } 1871 } 1872 // This is essentially the `PublicKeyCredentialCreationOptions` used to create it; however to reduce 1873 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size on 1874 // `x86_64-unknown-linux-gnu` platforms when `USER_LEN` is `USER_HANDLE_MIN_LEN`. 1875 /// State needed to be saved when beginning the registration ceremony. 1876 /// 1877 /// Saves the necessary information associated with the [`CredentialCreationOptions`] used to create it 1878 /// via [`CredentialCreationOptions::start_ceremony`] so that registration of a new credential can be 1879 /// performed with [`Self::verify`]. 1880 /// 1881 /// `RegistrationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct 1882 /// `RegistrationServerState` associated with a [`Registration`], one should use its corresponding 1883 /// [`Registration::challenge`]. 1884 #[derive(Debug)] 1885 pub struct RegistrationServerState<const USER_LEN: usize> { 1886 /// [`mediation`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-mediation). 1887 mediation: CredentialMediationRequirement, 1888 // This is a `SentChallenge` since we need `RegistrationServerState` to be fetchable after receiving the 1889 // response from the client. This response must obviously be constructable; thus its challenge is a 1890 // `SentChallenge`. 1891 // 1892 // This must never be mutated since we want to ensure it is actually a `Challenge` (which 1893 // can only be constructed via `Challenge::new`). This is guaranteed to be true iff 1894 // `serializable_server_state` is not enabled. We avoid implementing `trait`s like `Hash` when that 1895 // is enabled. 1896 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-challenge). 1897 challenge: SentChallenge, 1898 /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams). 1899 pub_key_cred_params: CoseAlgorithmIdentifiers, 1900 /// [`authenticatorSelection`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-authenticatorselection). 1901 authenticator_selection: AuthenticatorSelectionCriteria, 1902 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-extensions). 1903 extensions: ServerExtensionInfo, 1904 /// `Instant` the ceremony expires. 1905 #[cfg(not(feature = "serializable_server_state"))] 1906 expiration: Instant, 1907 /// `SystemTime` the ceremony expires. 1908 #[cfg(feature = "serializable_server_state")] 1909 expiration: SystemTime, 1910 /// User handle. 1911 user_id: UserHandle<USER_LEN>, 1912 } 1913 impl<const USER_LEN: usize> RegistrationServerState<USER_LEN> { 1914 #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))] 1915 fn is_eq(&self, other: &Self) -> bool { 1916 self.mediation == other.mediation 1917 && self.challenge == other.challenge 1918 && self.pub_key_cred_params == other.pub_key_cred_params 1919 && self.authenticator_selection == other.authenticator_selection 1920 && self.extensions == other.extensions 1921 && self.expiration == other.expiration 1922 && self.user_id == other.user_id 1923 } 1924 /// Verifies `response` is valid based on `self` consuming `self` and returning a `RegisteredCredential` that 1925 /// borrows the necessary data from `response`. 1926 /// 1927 /// `rp_id` MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when starting the 1928 /// ceremony. 1929 /// 1930 /// It is _essential_ to ensure [`RegisteredCredential::id`] has not been previously registered; if 1931 /// so, the ceremony SHOULD be aborted and a failure reported. When saving `RegisteredCredential`, one may 1932 /// want to save the [`RpId`] and [`PublicKeyCredentialUserEntity`] information; however since [`RpId`] is 1933 /// likely static, that may not be necessary. User information is also likely static for a given [`UserHandle`] 1934 /// (which is saved in `RegisteredCredential`); so if such info is saved, one may want to save it once per 1935 /// `UserHandle` and not per `RegisteredCredential`. 1936 /// 1937 /// # Errors 1938 /// 1939 /// Errors iff `response` is not valid according to the 1940 /// [registration ceremony criteria](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential) 1941 /// or violates any of the settings in `options`. 1942 #[inline] 1943 pub fn verify<'a, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>>( 1944 self, 1945 rp_id: &RpId, 1946 response: &'a Registration, 1947 options: &RegistrationVerificationOptions<'_, '_, O, T>, 1948 ) -> Result<RegisteredCredential<'a, USER_LEN>, RegCeremonyErr> { 1949 // [Registration ceremony](https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential) 1950 // is handled by: 1951 // 1952 // 1. Calling code. 1953 // 2. Client code and the construction of `resp` (hopefully via [`Registration::deserialize`]). 1954 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAttestation::deserialize`]). 1955 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1956 // 5. [`Self::partial_validate`]. 1957 // 6. [`Self::partial_validate`]. 1958 // 7. [`Self::partial_validate`]. 1959 // 8. [`Self::partial_validate`]. 1960 // 9. [`Self::partial_validate`]. 1961 // 10. [`Self::partial_validate`]. 1962 // 11. [`Self::partial_validate`]. 1963 // 12. [`Self::partial_validate`]. 1964 // 13. [`Self::partial_validate`]. 1965 // 14. [`Self::partial_validate`]. 1966 // 15. Below. 1967 // 16. [`Self::partial_validate`]. 1968 // 17. [`Self::partial_validate`]. 1969 // 18. [`Self::partial_validate`]. 1970 // 19. [`Self::partial_validate`]. 1971 // 20. Below. 1972 // 21. [`Self::partial_validate`]. 1973 // 22. [`Self::partial_validate`]. 1974 // 23. N/A since only none and self attestations are supported. 1975 // 24. Always satisfied since only none and self attestations are supported (Item 3 is N/A). 1976 // 25. [`Self::partial_validate`]. 1977 // 26. Calling code. 1978 // 27. Below. 1979 // 28. N/A since only none and self attestations are supported. 1980 // 29. Below. 1981 1982 // Steps 5–14, 16–19, 21–22, and 25. 1983 self.partial_validate(rp_id, response, (), &options.into()) 1984 .map_err(RegCeremonyErr::from) 1985 .and_then(|attestation_object| { 1986 let auth_data = attestation_object.auth_data(); 1987 let flags = auth_data.flags(); 1988 // Step 15. 1989 if matches!(self.mediation, CredentialMediationRequirement::Conditional) 1990 || flags.user_present 1991 { 1992 self.authenticator_selection 1993 // Verify any required authenticator attachment modality. 1994 .validate( 1995 options.require_authenticator_attachment, 1996 response.authenticator_attachment, 1997 ) 1998 .and_then(|()| { 1999 let attested_credential_data = auth_data.attested_credential_data(); 2000 self.pub_key_cred_params 2001 // Step 20. 2002 .validate(attested_credential_data.credential_public_key) 2003 .and_then(|()| { 2004 let extensions = auth_data.extensions(); 2005 // Step 27. 2006 self.extensions 2007 .validate( 2008 response.client_extension_results, 2009 extensions, 2010 options.error_on_unsolicited_extensions, 2011 ) 2012 .map_err(RegCeremonyErr::Extension) 2013 .and_then(|()| { 2014 // Step 29. 2015 RegisteredCredential::new( 2016 attested_credential_data.credential_id, 2017 response.response.transports(), 2018 self.user_id, 2019 StaticState { 2020 credential_public_key: attested_credential_data 2021 .credential_public_key, 2022 extensions: extensions.into(), 2023 client_extension_results: response 2024 .client_extension_results 2025 .into(), 2026 }, 2027 DynamicState { 2028 user_verified: flags.user_verified, 2029 backup: flags.backup, 2030 sign_count: auth_data.sign_count(), 2031 authenticator_attachment: response 2032 .authenticator_attachment, 2033 }, 2034 Metadata { 2035 attestation: match attestation_object 2036 .attestation() 2037 { 2038 AttestationFormat::None => { 2039 Attestation::None 2040 } 2041 AttestationFormat::Packed(_) => { 2042 Attestation::Surrogate 2043 } 2044 }, 2045 aaguid: attested_credential_data.aaguid, 2046 extensions: extensions.into(), 2047 client_extension_results: response 2048 .client_extension_results 2049 .into(), 2050 resident_key: self 2051 .authenticator_selection 2052 .resident_key, 2053 }, 2054 ) 2055 .map_err(RegCeremonyErr::Credential) 2056 }) 2057 }) 2058 }) 2059 } else { 2060 Err(RegCeremonyErr::UserNotPresent) 2061 } 2062 }) 2063 } 2064 } 2065 impl<const USER_LEN: usize> TimedCeremony for RegistrationServerState<USER_LEN> { 2066 #[cfg(any(doc, not(feature = "serializable_server_state")))] 2067 #[inline] 2068 fn expiration(&self) -> Instant { 2069 self.expiration 2070 } 2071 #[cfg(all(not(doc), feature = "serializable_server_state"))] 2072 #[inline] 2073 fn expiration(&self) -> SystemTime { 2074 self.expiration 2075 } 2076 } 2077 impl<const USER_LEN: usize> Ceremony<USER_LEN, false> for RegistrationServerState<USER_LEN> { 2078 type R = Registration; 2079 fn rand_challenge(&self) -> SentChallenge { 2080 self.challenge 2081 } 2082 #[cfg(not(feature = "serializable_server_state"))] 2083 fn expiry(&self) -> Instant { 2084 self.expiration 2085 } 2086 #[cfg(feature = "serializable_server_state")] 2087 fn expiry(&self) -> SystemTime { 2088 self.expiration 2089 } 2090 fn user_verification(&self) -> UserVerificationRequirement { 2091 self.authenticator_selection.user_verification 2092 } 2093 } 2094 impl<const USER_LEN: usize> Borrow<SentChallenge> for RegistrationServerState<USER_LEN> { 2095 #[inline] 2096 fn borrow(&self) -> &SentChallenge { 2097 &self.challenge 2098 } 2099 } 2100 impl<const USER_LEN: usize> PartialEq for RegistrationServerState<USER_LEN> { 2101 #[inline] 2102 fn eq(&self, other: &Self) -> bool { 2103 self.challenge == other.challenge 2104 } 2105 } 2106 impl<const USER_LEN: usize> PartialEq<&Self> for RegistrationServerState<USER_LEN> { 2107 #[inline] 2108 fn eq(&self, other: &&Self) -> bool { 2109 *self == **other 2110 } 2111 } 2112 impl<const USER_LEN: usize> PartialEq<RegistrationServerState<USER_LEN>> 2113 for &RegistrationServerState<USER_LEN> 2114 { 2115 #[inline] 2116 fn eq(&self, other: &RegistrationServerState<USER_LEN>) -> bool { 2117 **self == *other 2118 } 2119 } 2120 impl<const USER_LEN: usize> Eq for RegistrationServerState<USER_LEN> {} 2121 impl<const USER_LEN: usize> Hash for RegistrationServerState<USER_LEN> { 2122 #[inline] 2123 fn hash<H: Hasher>(&self, state: &mut H) { 2124 self.challenge.hash(state); 2125 } 2126 } 2127 impl<const USER_LEN: usize> PartialOrd for RegistrationServerState<USER_LEN> { 2128 #[inline] 2129 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 2130 Some(self.cmp(other)) 2131 } 2132 } 2133 impl<const USER_LEN: usize> Ord for RegistrationServerState<USER_LEN> { 2134 #[inline] 2135 fn cmp(&self, other: &Self) -> Ordering { 2136 self.challenge.cmp(&other.challenge) 2137 } 2138 } 2139 /// `RegistrationServerState` based on a [`UserHandle64`]. 2140 pub type RegistrationServerState64 = RegistrationServerState<USER_HANDLE_MAX_LEN>; 2141 /// `RegistrationServerState` based on a [`UserHandle16`]. 2142 pub type RegistrationServerState16 = RegistrationServerState<16>;