auth.rs (90674B)
1 #[cfg(doc)] 2 use super::{ 3 super::response::{ 4 Backup, CollectedClientData, Flag, 5 auth::AuthenticatorData, 6 register::{ 7 AuthenticatorExtensionOutputStaticState, ClientExtensionsOutputsStaticState, 8 DynamicState, StaticState, 9 }, 10 }, 11 AsciiDomain, AsciiDomainStatic, DomainOrigin, Url, 12 register::{self, PublicKeyCredentialCreationOptions}, 13 }; 14 use super::{ 15 super::{ 16 AuthenticatedCredential, 17 response::{ 18 AuthenticatorAttachment, 19 auth::{ 20 Authentication, AuthenticatorExtensionOutput, DiscoverableAuthentication, 21 HmacSecret, NonDiscoverableAuthentication, 22 error::{AuthCeremonyErr, ExtensionErr, OneOrTwo}, 23 }, 24 register::{CompressedPubKey, CredentialProtectionPolicy}, 25 }, 26 }, 27 BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId, CredentialMediationRequirement, 28 Credentials, ExtensionReq, FIVE_MINUTES, Hint, Origin, PrfInput, PublicKeyCredentialDescriptor, 29 RpId, SentChallenge, TimedCeremony, UserVerificationRequirement, 30 auth::error::{InvalidTimeout, NonDiscoverableCredentialRequestOptionsErr}, 31 }; 32 use core::{ 33 borrow::Borrow, 34 cmp::Ordering, 35 hash::{Hash, Hasher}, 36 num::{NonZeroU32, NonZeroU64}, 37 time::Duration, 38 }; 39 #[cfg(any(doc, not(feature = "serializable_server_state")))] 40 use std::time::Instant; 41 #[cfg(any(doc, feature = "serializable_server_state"))] 42 use std::time::SystemTime; 43 /// Contains error types. 44 pub mod error; 45 /// Contains functionality to serialize data to a client. 46 #[cfg(feature = "serde")] 47 pub mod ser; 48 /// Contains functionality to (de)serialize [`DiscoverableAuthenticationServerState`] and 49 /// [`NonDiscoverableAuthenticationServerState`] to a data store. 50 #[cfg(feature = "serializable_server_state")] 51 pub mod ser_server_state; 52 /// Controls how [signature counter](https://www.w3.org/TR/webauthn-3/#signature-counter) is enforced. 53 /// 54 /// Note that if the previous signature counter is positive and the new counter is not strictly greater, then the 55 /// authenticator is likely a clone (i.e., there are at least two copies of the private key). 56 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 57 pub enum SignatureCounterEnforcement { 58 /// Fail the authentication ceremony if the counter is less than or equal to the previous value when the 59 /// previous value is positive. 60 #[default] 61 Fail, 62 /// When the counter is less than the previous value, don't fail and update the value. 63 /// 64 /// Note in the special case that the new signature counter is 0, [`DynamicState::sign_count`] _won't_ 65 /// be updated since that would allow an attacker to permanently disable the counter. 66 Update, 67 /// When the counter is less than the previous value, don't fail but don't update the value. 68 Ignore, 69 } 70 impl SignatureCounterEnforcement { 71 /// Validates the signature counter based on `self`. 72 const fn validate(self, prev: u32, cur: u32) -> Result<u32, AuthCeremonyErr> { 73 if prev == 0 || cur > prev { 74 Ok(cur) 75 } else { 76 match self { 77 Self::Fail => Err(AuthCeremonyErr::SignatureCounter), 78 // When the new counter is `0`, we use the previous counter to avoid an attacker from 79 // being able to permanently disable it. 80 Self::Update => Ok(if cur == 0 { prev } else { cur }), 81 Self::Ignore => Ok(prev), 82 } 83 } 84 } 85 } 86 /// Owned version of [`PrfInput`]. 87 /// 88 /// When relying on [`NonDiscoverableCredentialRequestOptions`], it's recommended to use credential-specific PRF 89 /// inputs that are continuously rolled over. One uses this type for such a thing. 90 #[derive(Clone, Debug, Eq, PartialEq)] 91 pub struct PrfInputOwned { 92 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 93 pub first: Vec<u8>, 94 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 95 pub second: Option<Vec<u8>>, 96 /// Note this is only applicable for authenticators that implement the 97 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the 98 /// [`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) 99 /// extension since the data is encrypted and is part of the [`AuthenticatorData`]. 100 pub ext_req: ExtensionReq, 101 } 102 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client. 103 #[derive(Clone, Copy, Debug)] 104 pub struct Extension<'prf_first, 'prf_second> { 105 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 106 /// 107 /// If both [`CredentialSpecificExtension::prf`] and this are [`Some`], then `CredentialSpecificExtension::prf` 108 /// takes priority. 109 /// 110 /// Note `ExtensionReq` is only applicable for authenticators that implement the 111 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension) extension on top of the 112 /// [`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) 113 /// extension since the data is encrypted and is part of the [`AuthenticatorData`]. 114 pub prf: Option<(PrfInput<'prf_first, 'prf_second>, ExtensionReq)>, 115 } 116 impl<'prf_first, 'prf_second> Extension<'prf_first, 'prf_second> { 117 /// Returns an `Extension` with [`Self::prf`] set to `None`. 118 #[inline] 119 #[must_use] 120 pub const fn none() -> Self { 121 Self { prf: None } 122 } 123 /// Returns an `Extension` with [`Self::prf`] set to `None`. 124 #[expect(single_use_lifetimes, reason = "false positive")] 125 #[inline] 126 #[must_use] 127 pub const fn with_prf<'a: 'prf_first, 'b: 'prf_second>( 128 input: PrfInput<'a, 'b>, 129 req: ExtensionReq, 130 ) -> Self { 131 Self { 132 prf: Some((input, req)), 133 } 134 } 135 } 136 impl Default for Extension<'_, '_> { 137 /// Same as [`Self::none`]. 138 #[inline] 139 fn default() -> Self { 140 Self::none() 141 } 142 } 143 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client that 144 /// are credential-specific which among other things implies a non-discoverable request. 145 #[derive(Clone, Debug, Default)] 146 pub struct CredentialSpecificExtension { 147 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 148 /// 149 /// If both [`Extension::prf`] and this are [`Some`], then this take priority. 150 pub prf: Option<PrfInputOwned>, 151 } 152 /// Registered credential used in 153 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 154 #[derive(Clone, Debug)] 155 pub struct AllowedCredential { 156 /// The registered credential. 157 pub credential: PublicKeyCredentialDescriptor<Vec<u8>>, 158 /// Credential-specific extensions. 159 pub extension: CredentialSpecificExtension, 160 } 161 impl From<PublicKeyCredentialDescriptor<Vec<u8>>> for AllowedCredential { 162 #[inline] 163 fn from(credential: PublicKeyCredentialDescriptor<Vec<u8>>) -> Self { 164 Self { 165 credential, 166 extension: CredentialSpecificExtension::default(), 167 } 168 } 169 } 170 impl From<AllowedCredential> for PublicKeyCredentialDescriptor<Vec<u8>> { 171 #[inline] 172 fn from(credential: AllowedCredential) -> Self { 173 credential.credential 174 } 175 } 176 /// Queue of unique [`AllowedCredential`]s. 177 #[derive(Clone, Debug, Default)] 178 pub struct AllowedCredentials { 179 /// Allowed credentials. 180 creds: Vec<AllowedCredential>, 181 /// Number of `AllowedCredential`s that have PRF inputs. 182 /// 183 /// Useful to help serialization. 184 prf_count: usize, 185 } 186 impl Credentials for AllowedCredentials { 187 type Credential = AllowedCredential; 188 /// # Examples 189 /// 190 /// ``` 191 /// # use webauthn_rp::request::{auth::AllowedCredentials, Credentials}; 192 /// assert!(AllowedCredentials::with_capacity(1).as_ref().is_empty()); 193 /// ``` 194 #[inline] 195 fn with_capacity(capacity: usize) -> Self { 196 Self { 197 creds: Vec::with_capacity(capacity), 198 prf_count: 0, 199 } 200 } 201 /// # Examples 202 /// 203 /// ``` 204 /// # #[cfg(all(feature = "bin", feature = "custom"))] 205 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 206 /// # use webauthn_rp::{ 207 /// # request::{auth::AllowedCredentials, PublicKeyCredentialDescriptor, Credentials}, 208 /// # response::{AuthTransports, CredentialId}, 209 /// # }; 210 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 211 /// /// from the database. 212 /// # #[cfg(all(feature = "bin", feature = "custom"))] 213 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 214 /// // ⋮ 215 /// # AuthTransports::decode(32) 216 /// } 217 /// let mut creds = AllowedCredentials::with_capacity(1); 218 /// assert!(creds.as_ref().is_empty()); 219 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 220 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 221 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 222 /// # #[cfg(all(feature = "bin", feature = "custom"))] 223 /// let id = CredentialId::try_from(vec![0; 16])?; 224 /// # #[cfg(all(feature = "bin", feature = "custom"))] 225 /// let transports = get_transports((&id).into())?; 226 /// # #[cfg(all(feature = "bin", feature = "custom"))] 227 /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into())); 228 /// # #[cfg(all(feature = "bin", feature = "custom"))] 229 /// let id_copy = CredentialId::try_from(vec![0; 16])?; 230 /// # #[cfg(all(feature = "bin", feature = "custom"))] 231 /// let transports_2 = AuthTransports::NONE; 232 /// // Duplicate `CredentialId`s don't get added. 233 /// # #[cfg(all(feature = "bin", feature = "custom"))] 234 /// assert!(!creds.push( 235 /// PublicKeyCredentialDescriptor { 236 /// id: id_copy, 237 /// transports: transports_2 238 /// } 239 /// .into() 240 /// )); 241 /// # Ok::<_, webauthn_rp::AggErr>(()) 242 /// ``` 243 #[expect( 244 clippy::arithmetic_side_effects, 245 reason = "comment explains how overflow is not possible" 246 )] 247 #[inline] 248 fn push(&mut self, cred: Self::Credential) -> bool { 249 self.creds 250 .iter() 251 .try_fold((), |(), c| { 252 if c.credential.id == cred.credential.id { 253 Err(()) 254 } else { 255 Ok(()) 256 } 257 }) 258 .is_ok_and(|()| { 259 // This can't overflow since `self.creds.push` would `panic` since 260 // `self.prf_count <= self.creds.len()`. 261 self.prf_count += usize::from(cred.extension.prf.is_some()); 262 self.creds.push(cred); 263 true 264 }) 265 } 266 #[inline] 267 fn len(&self) -> usize { 268 self.creds.len() 269 } 270 } 271 impl AsRef<[AllowedCredential]> for AllowedCredentials { 272 #[inline] 273 fn as_ref(&self) -> &[AllowedCredential] { 274 self.creds.as_slice() 275 } 276 } 277 impl From<&AllowedCredentials> for Vec<CredInfo> { 278 #[inline] 279 fn from(value: &AllowedCredentials) -> Self { 280 let len = value.creds.len(); 281 value 282 .creds 283 .iter() 284 .fold(Self::with_capacity(len), |mut creds, cred| { 285 creds.push(CredInfo { 286 id: cred.credential.id.clone(), 287 ext: (&cred.extension).into(), 288 }); 289 creds 290 }) 291 } 292 } 293 impl From<AllowedCredentials> for Vec<PublicKeyCredentialDescriptor<Vec<u8>>> { 294 #[inline] 295 fn from(value: AllowedCredentials) -> Self { 296 let mut creds = Self::with_capacity(value.creds.len()); 297 value.creds.into_iter().fold((), |(), cred| { 298 creds.push(cred.credential); 299 }); 300 creds 301 } 302 } 303 impl From<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>> for AllowedCredentials { 304 #[inline] 305 fn from(value: Vec<PublicKeyCredentialDescriptor<Vec<u8>>>) -> Self { 306 let mut creds = Self::with_capacity(value.len()); 307 value.into_iter().fold((), |(), credential| { 308 _ = creds.push(AllowedCredential { 309 credential, 310 extension: CredentialSpecificExtension { prf: None }, 311 }); 312 }); 313 creds 314 } 315 } 316 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions) 317 /// to send to the client when authenticating a discoverable credential. 318 /// 319 /// Upon saving the [`DiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send 320 /// [`DiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created 321 /// [`DiscoverableAuthentication`], it is validated using [`DiscoverableAuthenticationServerState::verify`]. 322 #[derive(Debug)] 323 pub struct DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 324 /// [`mediation`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement). 325 /// 326 /// Note if this is [`CredentialMediationRequirement::Conditional`], user agents are instructed to not 327 /// enforce any timeout; as result, one may want to set [`PublicKeyCredentialRequestOptions::timeout`] to 328 /// [`NonZeroU32::MAX`]. 329 pub mediation: CredentialMediationRequirement, 330 /// `public-key` [credential type](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry). 331 pub public_key: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>, 332 } 333 impl<'rp_id, 'prf_first, 'prf_second> 334 DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> 335 { 336 /// Creates a `DiscoverableCredentialRequestOptions` containing [`CredentialMediationRequirement::default`] and 337 /// [`PublicKeyCredentialRequestOptions::passkey`]. 338 /// 339 /// # Examples 340 /// 341 /// ``` 342 /// # use webauthn_rp::request::{auth::DiscoverableCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement}; 343 /// assert!(matches!( 344 /// DiscoverableCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).public_key.user_verification, 345 /// UserVerificationRequirement::Required 346 /// )); 347 /// # Ok::<_, webauthn_rp::AggErr>(()) 348 /// ``` 349 #[expect(single_use_lifetimes, reason = "false positive")] 350 #[inline] 351 #[must_use] 352 pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self { 353 Self { 354 mediation: CredentialMediationRequirement::default(), 355 public_key: PublicKeyCredentialRequestOptions::passkey(rp_id), 356 } 357 } 358 /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming 359 /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so 360 /// `DiscoverableAuthenticationClientState` MUST be sent ASAP. In order to complete authentication, the returned 361 /// `DiscoverableAuthenticationServerState` MUST be saved so that it can later be used to verify the credential 362 /// assertion with [`DiscoverableAuthenticationServerState::verify`]. 363 /// 364 /// # Errors 365 /// 366 /// Errors iff `self` contains incompatible configuration. 367 #[inline] 368 pub fn start_ceremony( 369 self, 370 ) -> Result< 371 ( 372 DiscoverableAuthenticationServerState, 373 DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>, 374 ), 375 InvalidTimeout, 376 > { 377 #[cfg(not(feature = "serializable_server_state"))] 378 let res = Instant::now(); 379 #[cfg(feature = "serializable_server_state")] 380 let res = SystemTime::now(); 381 res.checked_add(Duration::from_millis( 382 NonZeroU64::from(self.public_key.timeout).get(), 383 )) 384 .ok_or(InvalidTimeout) 385 .map(|expiration| { 386 ( 387 DiscoverableAuthenticationServerState(AuthenticationServerState { 388 challenge: SentChallenge(self.public_key.challenge.0), 389 user_verification: self.public_key.user_verification, 390 extensions: self.public_key.extensions.into(), 391 expiration, 392 }), 393 DiscoverableAuthenticationClientState(self), 394 ) 395 }) 396 } 397 /// Same as [`Self::start_ceremony`] except the raw challenge is returned instead of 398 /// [`DiscoverableAuthenticationClientState`]. 399 /// 400 /// Note this is useful when one configures the authentication ceremony client-side and only needs the 401 /// server-generated challenge. It's of course essential that `self` is configured exactly the same as 402 /// how it is configured client-side. See 403 /// [`challengeURL`](https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-challengeURL) for more 404 /// information. 405 /// 406 /// # Errors 407 /// 408 /// Read [`Self::start_ceremony`]. 409 #[inline] 410 pub fn start_ceremony_challenge_only( 411 self, 412 ) -> Result<(DiscoverableAuthenticationServerState, [u8; 16]), InvalidTimeout> { 413 self.start_ceremony() 414 .map(|(server, client)| (server, client.0.public_key.challenge.into_array())) 415 } 416 } 417 /// The [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions) 418 /// to send to the client when authenticating non-discoverable credentials. 419 /// 420 /// Upon saving the [`NonDiscoverableAuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send 421 /// [`NonDiscoverableAuthenticationClientState`] to the client ASAP. After receiving the newly created 422 /// [`NonDiscoverableAuthentication`], it is validated using [`NonDiscoverableAuthenticationServerState::verify`]. 423 #[derive(Debug)] 424 pub struct NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 425 /// [`mediation`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement). 426 pub mediation: CredentialMediationRequirement, 427 /// [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions). 428 pub options: PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>, 429 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 430 pub allow_credentials: AllowedCredentials, 431 } 432 impl<'rp_id, 'prf_first, 'prf_second> 433 NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> 434 { 435 /// Creates a `NonDiscoverableCredentialRequestOptions` containing 436 /// [`CredentialMediationRequirement::default`], 437 /// [`PublicKeyCredentialRequestOptions::second_factor`], and the passed [`AllowedCredentials`]. 438 /// 439 /// # Examples 440 /// 441 /// ``` 442 /// # #[cfg(all(feature = "bin", feature = "custom"))] 443 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 444 /// # use webauthn_rp::{ 445 /// # request::{ 446 /// # auth::{AllowedCredentials, NonDiscoverableCredentialRequestOptions}, 447 /// # AsciiDomain, RpId, PublicKeyCredentialDescriptor, Credentials 448 /// # }, 449 /// # response::{AuthTransports, CredentialId}, 450 /// # }; 451 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 452 /// /// from the database. 453 /// # #[cfg(all(feature = "bin", feature = "custom"))] 454 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 455 /// // ⋮ 456 /// # AuthTransports::decode(32) 457 /// } 458 /// let mut creds = AllowedCredentials::with_capacity(1); 459 /// assert!(creds.as_ref().is_empty()); 460 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 461 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 462 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 463 /// # #[cfg(all(feature = "bin", feature = "custom"))] 464 /// let id = CredentialId::try_from(vec![0; 16])?; 465 /// # #[cfg(all(feature = "bin", feature = "custom"))] 466 /// let transports = get_transports((&id).into())?; 467 /// # #[cfg(all(feature = "bin", feature = "custom"))] 468 /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into())); 469 /// # #[cfg(all(feature = "bin", feature = "custom"))] 470 /// assert_eq!( 471 /// NonDiscoverableCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), creds) 472 /// .allow_credentials 473 /// .len(), 474 /// 1 475 /// ); 476 /// # Ok::<_, webauthn_rp::AggErr>(()) 477 /// ``` 478 #[expect(single_use_lifetimes, reason = "false positive")] 479 #[inline] 480 #[must_use] 481 pub fn second_factor<'a: 'rp_id>( 482 rp_id: &'a RpId, 483 allow_credentials: AllowedCredentials, 484 ) -> Self { 485 Self { 486 mediation: CredentialMediationRequirement::default(), 487 options: PublicKeyCredentialRequestOptions::second_factor(rp_id), 488 allow_credentials, 489 } 490 } 491 /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming 492 /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `NonDiscoverableAuthenticationClientState` 493 /// MUST be sent ASAP. In order to complete authentication, the returned `NonDiscoverableAuthenticationServerState` 494 /// MUST be saved so that it can later be used to verify the credential assertion with 495 /// [`NonDiscoverableAuthenticationServerState::verify`]. 496 /// 497 /// # Errors 498 /// 499 /// Errors iff `self` contains incompatible configuration. 500 #[inline] 501 pub fn start_ceremony( 502 self, 503 ) -> Result< 504 ( 505 NonDiscoverableAuthenticationServerState, 506 NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>, 507 ), 508 NonDiscoverableCredentialRequestOptionsErr, 509 > { 510 if self.allow_credentials.is_empty() { 511 Err(NonDiscoverableCredentialRequestOptionsErr::EmptyAllowedCredentials) 512 } else if matches!(self.mediation, CredentialMediationRequirement::Conditional) { 513 Err(NonDiscoverableCredentialRequestOptionsErr::ConditionalMediationRequested) 514 } else { 515 #[cfg(not(feature = "serializable_server_state"))] 516 let res = Instant::now(); 517 #[cfg(feature = "serializable_server_state")] 518 let res = SystemTime::now(); 519 res.checked_add(Duration::from_millis( 520 NonZeroU64::from(self.options.timeout).get(), 521 )) 522 .ok_or(NonDiscoverableCredentialRequestOptionsErr::InvalidTimeout) 523 .map(|expiration| { 524 ( 525 NonDiscoverableAuthenticationServerState { 526 state: AuthenticationServerState { 527 challenge: SentChallenge(self.options.challenge.0), 528 user_verification: self.options.user_verification, 529 extensions: self.options.extensions.into(), 530 expiration, 531 }, 532 allow_credentials: Vec::from(&self.allow_credentials), 533 }, 534 NonDiscoverableAuthenticationClientState(self), 535 ) 536 }) 537 } 538 } 539 } 540 /// The [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions) 541 /// to send to the client when authenticating a credential. 542 /// 543 /// This does _not_ contain [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 544 #[derive(Debug)] 545 pub struct PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 546 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge). 547 pub challenge: Challenge, 548 /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-timeout). 549 /// 550 /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives 551 /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled 552 /// when authenticating credentials as no timeout would make out-of-memory (OOM) conditions more likely. 553 pub timeout: NonZeroU32, 554 /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-rpid). 555 /// 556 /// This MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when the credential was registered. 557 pub rp_id: &'rp_id RpId, 558 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification). 559 pub user_verification: UserVerificationRequirement, 560 /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints). 561 pub hints: Hint, 562 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions). 563 pub extensions: Extension<'prf_first, 'prf_second>, 564 } 565 impl<'rp_id> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> { 566 /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to 567 /// [`UserVerificationRequirement::Required`] and [`Self::timeout`] set to [`FIVE_MINUTES`]. 568 /// 569 /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the 570 /// credential was registered. 571 /// 572 /// # Examples 573 /// 574 /// ``` 575 /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement}; 576 /// assert!(matches!( 577 /// PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification, 578 /// UserVerificationRequirement::Required 579 /// )); 580 /// # Ok::<_, webauthn_rp::AggErr>(()) 581 /// ``` 582 #[expect(single_use_lifetimes, reason = "false positive")] 583 #[inline] 584 #[must_use] 585 pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self { 586 Self { 587 challenge: Challenge::new(), 588 timeout: FIVE_MINUTES, 589 rp_id, 590 user_verification: UserVerificationRequirement::Required, 591 hints: Hint::None, 592 extensions: Extension::default(), 593 } 594 } 595 /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to 596 /// [`UserVerificationRequirement::Discouraged`] and [`Self::timeout`] set to [`FIVE_MINUTES`]. 597 /// 598 /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the 599 /// credentials were registered. 600 /// 601 /// # Examples 602 /// 603 /// ``` 604 /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement}; 605 /// assert!(matches!( 606 /// PublicKeyCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification, 607 /// UserVerificationRequirement::Discouraged 608 /// )); 609 /// # Ok::<_, webauthn_rp::AggErr>(()) 610 /// ``` 611 #[expect(single_use_lifetimes, reason = "false positive")] 612 #[inline] 613 #[must_use] 614 pub fn second_factor<'a: 'rp_id>(rp_id: &'a RpId) -> Self { 615 let mut opts = Self::passkey(rp_id); 616 opts.user_verification = UserVerificationRequirement::Discouraged; 617 opts 618 } 619 } 620 /// Container of a [`DiscoverableCredentialRequestOptions`] that has been used to start the authentication ceremony. 621 /// This gets sent to the client ASAP. 622 #[derive(Debug)] 623 pub struct DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>( 624 DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>, 625 ); 626 impl<'rp_id, 'prf_first, 'prf_second> 627 DiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second> 628 { 629 /// Returns the `DiscoverableCredentialRequestOptions` that was used to start an authentication ceremony. 630 #[inline] 631 #[must_use] 632 pub const fn options( 633 &self, 634 ) -> &DiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 635 &self.0 636 } 637 } 638 /// Container of a [`NonDiscoverableCredentialRequestOptions`] that has been used to start the authentication 639 /// ceremony. This gets sent to the client ASAP. 640 #[derive(Debug)] 641 pub struct NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second>( 642 NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>, 643 ); 644 impl<'rp_id, 'prf_first, 'prf_second> 645 NonDiscoverableAuthenticationClientState<'rp_id, 'prf_first, 'prf_second> 646 { 647 /// Returns the `NonDiscoverableCredentialRequestOptions` that was used to start an authentication ceremony. 648 #[inline] 649 #[must_use] 650 pub const fn options( 651 &self, 652 ) -> &NonDiscoverableCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 653 &self.0 654 } 655 } 656 /// The possible combinations of an [`AuthenticatedCredential`]'s [`StaticState`]'s 657 /// `extensions.hmac_secret` and `client_extension_results.prf`. 658 /// 659 /// Note we ensure in `crate::verify_static_and_dynamic_state` that `hmac_secret` does not exist when 660 /// `prf` does not exist, `hmac_secret` does not exist or is `false` when `prf` is `false`, or 661 /// `hmac_secret` does not exist or is `true` when `prf` is `true`. 662 #[derive(Clone, Copy)] 663 enum CredPrf { 664 /// No `prf` or `hmac_secret`. 665 None, 666 /// `prf.enabled` is `false` but there is no `hmac_secret`. 667 FalseNoHmac, 668 /// `prf.enabled` and `hmac_secret` are `false`. 669 FalseFalseHmac, 670 /// `prf.enabled` is `true` but there is no `hmac_secret`. 671 TrueNoHmac, 672 /// `prf.enabled` and `hmac_secret` are `true`. 673 TrueTrueHmac, 674 } 675 /// `PrfInput` and `PrfInputOwned` without the actual data sent to reduce memory usage when storing 676 /// [`DiscoverableAuthenticationServerState`] in an in-memory collection. 677 #[derive(Clone, Copy, Debug)] 678 enum ServerPrfInfo { 679 /// No `PrfInput`. 680 None, 681 /// `PrfInput::second` was `None`. 682 One(ExtensionReq), 683 /// `PrfInput::second` was `Some`. 684 Two(ExtensionReq), 685 } 686 impl ServerPrfInfo { 687 /// Validates `val` based on the passed arguments. 688 /// 689 /// It's not possible to request the PRF extension without sending `UserVerificationRequirement::Required`; 690 /// thus `user_verified` will always be `true` when sending PRF; otherwise ceremony validation will error. 691 /// However when we _don't_ send the PRF extension _and_ we don't error on an unsolicited response, it's 692 /// possible to receive an `HmacSecret` without the user having been verified; thus we only ensure 693 /// `user_verified` is true when we don't error on unsolicted responses _and_ we didn't send the PRF extension. 694 const fn validate( 695 self, 696 user_verified: bool, 697 cred_prf: CredPrf, 698 hmac: HmacSecret, 699 err_unsolicited: bool, 700 ) -> Result<(), ExtensionErr> { 701 match hmac { 702 HmacSecret::None => match self { 703 Self::None => Ok(()), 704 Self::One(req) | Self::Two(req) => { 705 if matches!(req, ExtensionReq::Allow) 706 || !matches!(cred_prf, CredPrf::TrueTrueHmac) 707 { 708 Ok(()) 709 } else { 710 Err(ExtensionErr::MissingHmacSecret) 711 } 712 } 713 }, 714 HmacSecret::One => match self { 715 Self::None => { 716 if err_unsolicited { 717 Err(ExtensionErr::ForbiddenHmacSecret) 718 } else if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) { 719 if user_verified { 720 Ok(()) 721 } else { 722 Err(ExtensionErr::UserNotVerifiedHmacSecret) 723 } 724 } else { 725 Err(ExtensionErr::HmacSecretForNonHmacSecretCredential) 726 } 727 } 728 Self::One(_) => { 729 if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) { 730 Ok(()) 731 } else { 732 Err(ExtensionErr::HmacSecretForNonHmacSecretCredential) 733 } 734 } 735 Self::Two(_) => Err(ExtensionErr::InvalidHmacSecretValue( 736 OneOrTwo::Two, 737 OneOrTwo::One, 738 )), 739 }, 740 HmacSecret::Two => match self { 741 Self::None => { 742 if err_unsolicited { 743 Err(ExtensionErr::ForbiddenHmacSecret) 744 } else if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) { 745 if user_verified { 746 Ok(()) 747 } else { 748 Err(ExtensionErr::UserNotVerifiedHmacSecret) 749 } 750 } else { 751 Err(ExtensionErr::HmacSecretForNonHmacSecretCredential) 752 } 753 } 754 Self::One(_) => Err(ExtensionErr::InvalidHmacSecretValue( 755 OneOrTwo::One, 756 OneOrTwo::Two, 757 )), 758 Self::Two(_) => { 759 if matches!(cred_prf, CredPrf::None | CredPrf::TrueTrueHmac) { 760 Ok(()) 761 } else { 762 Err(ExtensionErr::HmacSecretForNonHmacSecretCredential) 763 } 764 } 765 }, 766 } 767 } 768 } 769 #[cfg(test)] 770 impl PartialEq for ServerPrfInfo { 771 fn eq(&self, other: &Self) -> bool { 772 match *self { 773 Self::None => matches!(*other, Self::None), 774 Self::One(req) => matches!(*other, Self::One(req2) if req == req2), 775 Self::Two(req) => matches!(*other, Self::Two(req2) if req == req2), 776 } 777 } 778 } 779 impl From<Option<(PrfInput<'_, '_>, ExtensionReq)>> for ServerPrfInfo { 780 fn from(value: Option<(PrfInput<'_, '_>, ExtensionReq)>) -> Self { 781 value.map_or(Self::None, |val| { 782 val.0 783 .second 784 .map_or_else(|| Self::One(val.1), |_| Self::Two(val.1)) 785 }) 786 } 787 } 788 impl From<&PrfInputOwned> for ServerPrfInfo { 789 fn from(value: &PrfInputOwned) -> Self { 790 value 791 .second 792 .as_ref() 793 .map_or_else(|| Self::One(value.ext_req), |_| Self::Two(value.ext_req)) 794 } 795 } 796 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 797 /// in an in-memory collection. 798 #[derive(Clone, Copy, Debug)] 799 struct ServerExtensionInfo { 800 /// `Extension::prf`. 801 prf: ServerPrfInfo, 802 } 803 impl From<Extension<'_, '_>> for ServerExtensionInfo { 804 fn from(value: Extension<'_, '_>) -> Self { 805 Self { 806 prf: value.prf.into(), 807 } 808 } 809 } 810 #[cfg(test)] 811 impl PartialEq for ServerExtensionInfo { 812 fn eq(&self, other: &Self) -> bool { 813 self.prf == other.prf 814 } 815 } 816 /// `CredentialSpecificExtension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 817 /// in an in-memory collection. 818 #[derive(Clone, Copy, Debug)] 819 struct ServerCredSpecificExtensionInfo { 820 /// `CredentialSpecificExtension::prf`. 821 prf: ServerPrfInfo, 822 } 823 #[cfg(test)] 824 impl PartialEq for ServerCredSpecificExtensionInfo { 825 fn eq(&self, other: &Self) -> bool { 826 self.prf == other.prf 827 } 828 } 829 impl From<&CredentialSpecificExtension> for ServerCredSpecificExtensionInfo { 830 fn from(value: &CredentialSpecificExtension) -> Self { 831 Self { 832 prf: value 833 .prf 834 .as_ref() 835 .map_or(ServerPrfInfo::None, ServerPrfInfo::from), 836 } 837 } 838 } 839 impl ServerExtensionInfo { 840 /// Validates the extensions. 841 /// 842 /// Note that this MUST only be called internally by `auth::validate_extensions`. 843 const fn validate_extensions( 844 self, 845 user_verified: bool, 846 auth_ext: AuthenticatorExtensionOutput, 847 error_unsolicited: bool, 848 cred_prf: CredPrf, 849 ) -> Result<(), ExtensionErr> { 850 ServerPrfInfo::validate( 851 self.prf, 852 user_verified, 853 cred_prf, 854 auth_ext.hmac_secret, 855 error_unsolicited, 856 ) 857 } 858 } 859 /// Validates the extensions. 860 fn validate_extensions( 861 ext: ServerExtensionInfo, 862 user_verified: bool, 863 cred_ext: Option<ServerCredSpecificExtensionInfo>, 864 auth_ext: AuthenticatorExtensionOutput, 865 error_unsolicited: bool, 866 cred_prf: CredPrf, 867 ) -> Result<(), ExtensionErr> { 868 cred_ext.map_or_else( 869 || { 870 // No credental-specific extensions, so we can simply focus on `ext`. 871 ext.validate_extensions(user_verified, auth_ext, error_unsolicited, cred_prf) 872 }, 873 |c_ext| { 874 // Must carefully process each extension based on overlap and which gets priority over the other. 875 if matches!(c_ext.prf, ServerPrfInfo::None) { 876 ext.prf.validate( 877 user_verified, 878 cred_prf, 879 auth_ext.hmac_secret, 880 error_unsolicited, 881 ) 882 } else { 883 c_ext.prf.validate( 884 user_verified, 885 cred_prf, 886 auth_ext.hmac_secret, 887 error_unsolicited, 888 ) 889 } 890 }, 891 ) 892 } 893 /// [`AllowedCredential`] with less data to reduce memory usage when storing [`AuthenticationServerState`] 894 /// in an in-memory collection. 895 #[derive(Debug)] 896 struct CredInfo { 897 /// The Credential ID. 898 id: CredentialId<Vec<u8>>, 899 /// Any credential-specific extensions. 900 ext: ServerCredSpecificExtensionInfo, 901 } 902 #[cfg(test)] 903 impl PartialEq for CredInfo { 904 fn eq(&self, other: &Self) -> bool { 905 self.id == other.id && self.ext == other.ext 906 } 907 } 908 /// Controls how to handle a change in [`DynamicState::authenticator_attachment`]. 909 /// 910 /// Note when `DynamicState::authenticator_attachment` is [`AuthenticatorAttachment::None`], then it will 911 /// be updated regardless. Similarly when [`Authentication::authenticator_attachment`] is 912 /// `AuthenticatorAttachment::None`, it will never update `DynamicState::authenticator_attachment`. 913 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 914 pub enum AuthenticatorAttachmentEnforcement { 915 /// Fail the authentication ceremony if [`AuthenticatorAttachment`] is not the same. 916 /// 917 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 918 Fail(bool), 919 /// Update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 920 /// 921 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 922 Update(bool), 923 /// Do not update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 924 /// 925 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 926 Ignore(bool), 927 } 928 impl AuthenticatorAttachmentEnforcement { 929 /// Validates `cur` based on `self` and `prev`. 930 const fn validate( 931 self, 932 prev: AuthenticatorAttachment, 933 cur: AuthenticatorAttachment, 934 ) -> Result<AuthenticatorAttachment, AuthCeremonyErr> { 935 match cur { 936 AuthenticatorAttachment::None => match self { 937 Self::Fail(require) | Self::Update(require) | Self::Ignore(require) => { 938 if require { 939 Err(AuthCeremonyErr::MissingAuthenticatorAttachment) 940 } else { 941 // We don't overwrite the previous one with [`AuthenticatorAttachment::None`]. 942 Ok(prev) 943 } 944 } 945 }, 946 AuthenticatorAttachment::Platform => match self { 947 Self::Fail(_) => { 948 if matches!(prev, AuthenticatorAttachment::CrossPlatform) { 949 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 950 } else { 951 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 952 Ok(cur) 953 } 954 } 955 Self::Update(_) => Ok(cur), 956 Self::Ignore(_) => { 957 if matches!(prev, AuthenticatorAttachment::None) { 958 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 959 Ok(cur) 960 } else { 961 Ok(prev) 962 } 963 } 964 }, 965 AuthenticatorAttachment::CrossPlatform => match self { 966 Self::Fail(_) => { 967 if matches!(prev, AuthenticatorAttachment::Platform) { 968 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 969 } else { 970 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 971 Ok(cur) 972 } 973 } 974 Self::Update(_) => Ok(cur), 975 Self::Ignore(_) => { 976 if matches!(prev, AuthenticatorAttachment::None) { 977 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 978 Ok(cur) 979 } else { 980 Ok(prev) 981 } 982 } 983 }, 984 } 985 } 986 } 987 impl Default for AuthenticatorAttachmentEnforcement { 988 /// Returns [`Self::Ignore`] containing `false`. 989 #[inline] 990 fn default() -> Self { 991 Self::Ignore(false) 992 } 993 } 994 /// Additional verification options to perform in [`DiscoverableAuthenticationServerState::verify`] and 995 /// [`NonDiscoverableAuthenticationServerState::verify`]. 996 #[derive(Clone, Copy, Debug)] 997 pub struct AuthenticationVerificationOptions<'origins, 'top_origins, O, T> { 998 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 999 /// 1000 /// When this is empty, the origin that will be used will be based on the [`RpId`] passed to 1001 /// [`DiscoverableAuthenticationServerState::verify`] or [`NonDiscoverableAuthenticationServerState::verify`]. 1002 /// If [`RpId::Domain`] or [`RpId::StaticDomain`], then the [`DomainOrigin`] returned from passing 1003 /// [`AsciiDomain::as_ref`] and [`AsciiDomainStatic::as_str`] to [`DomainOrigin::new`] respectively will be 1004 /// used; otherwise the [`Url`] in [`RpId::Url`] will be used. 1005 pub allowed_origins: &'origins [O], 1006 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 1007 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 1008 /// 1009 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 1010 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 1011 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 1012 pub allowed_top_origins: Option<&'top_origins [T]>, 1013 /// The required [`Backup`] state of the credential. 1014 /// 1015 /// Note that `None` is _not_ the same as `Some(BackupReq::None)` as the latter indicates that any [`Backup`] 1016 /// is allowed. This is rarely what you want; instead, `None` indicates that [`BackupReq::from`] applied to 1017 /// [`DynamicState::backup`] will be used in [`DiscoverableAuthenticationServerState::verify`] and 1018 /// [`NonDiscoverableAuthenticationServerState::verify`] and 1019 pub backup_requirement: Option<BackupReq>, 1020 /// Error when unsolicited extensions are sent back iff `true`. 1021 pub error_on_unsolicited_extensions: bool, 1022 /// Dictates what happens when [`Authentication::authenticator_attachment`] is not the same as 1023 /// [`DynamicState::authenticator_attachment`]. 1024 pub auth_attachment_enforcement: AuthenticatorAttachmentEnforcement, 1025 /// [`DynamicState::user_verified`] will be set to `true` iff [`Flag::user_verified`] when `true`. 1026 pub update_uv: bool, 1027 /// Dictates what happens when [`AuthenticatorData::sign_count`] is not updated to a strictly greater value. 1028 pub sig_counter_enforcement: SignatureCounterEnforcement, 1029 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 1030 #[cfg(feature = "serde_relaxed")] 1031 pub client_data_json_relaxed: bool, 1032 } 1033 impl<O, T> AuthenticationVerificationOptions<'_, '_, O, T> { 1034 /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`, 1035 /// [`Self::backup_requirement`] is `None`, [`Self::error_on_unsolicited_extensions`] is `true`, 1036 /// [`Self::auth_attachment_enforcement`] is [`AuthenticatorAttachmentEnforcement::default`], 1037 /// [`Self::update_uv`] is `false`, [`Self::sig_counter_enforcement`] is 1038 /// [`SignatureCounterEnforcement::default`], and [`Self::client_data_json_relaxed`] is `true`. 1039 /// 1040 /// Note `O` and `T` should implement `PartialEq<Origin<'_>>` (e.g., `&str`). 1041 #[inline] 1042 #[must_use] 1043 pub const fn new() -> Self { 1044 Self { 1045 allowed_origins: [].as_slice(), 1046 allowed_top_origins: None, 1047 backup_requirement: None, 1048 error_on_unsolicited_extensions: true, 1049 auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::Ignore(false), 1050 update_uv: false, 1051 sig_counter_enforcement: SignatureCounterEnforcement::Fail, 1052 #[cfg(feature = "serde_relaxed")] 1053 client_data_json_relaxed: true, 1054 } 1055 } 1056 } 1057 impl<O, T> Default for AuthenticationVerificationOptions<'_, '_, O, T> { 1058 /// Same as [`Self::new`]. 1059 #[inline] 1060 fn default() -> Self { 1061 Self::new() 1062 } 1063 } 1064 // This is essentially the `DiscoverableCredentialRequestOptions` used to create it; however to reduce 1065 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size 1066 // `x86_64-unknown-linux-gnu` platforms. 1067 // 1068 /// State needed to be saved when beginning the authentication ceremony. 1069 /// 1070 /// Saves the necessary information associated with the [`DiscoverableCredentialRequestOptions`] used to create it 1071 /// via [`DiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be 1072 /// performed with [`Self::verify`]. 1073 /// 1074 /// `DiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct 1075 /// `DiscoverableAuthenticationServerState` associated with a [`DiscoverableAuthentication`], one should use its 1076 /// corresponding [`DiscoverableAuthentication::challenge`]. 1077 #[derive(Debug)] 1078 pub struct DiscoverableAuthenticationServerState(AuthenticationServerState); 1079 impl DiscoverableAuthenticationServerState { 1080 /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true` 1081 /// iff `cred` was mutated. 1082 /// 1083 /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the 1084 /// ceremony. 1085 /// 1086 /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)` 1087 /// is returned. 1088 /// 1089 /// # Errors 1090 /// 1091 /// Errors iff `response` is not valid according to the 1092 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any 1093 /// of the settings in `options`. 1094 #[inline] 1095 pub fn verify< 1096 'a, 1097 const USER_LEN: usize, 1098 O: PartialEq<Origin<'a>>, 1099 T: PartialEq<Origin<'a>>, 1100 EdKey: AsRef<[u8]>, 1101 P256Key: AsRef<[u8]>, 1102 P384Key: AsRef<[u8]>, 1103 RsaKey: AsRef<[u8]>, 1104 >( 1105 self, 1106 rp_id: &RpId, 1107 response: &'a DiscoverableAuthentication<USER_LEN>, 1108 cred: &mut AuthenticatedCredential< 1109 '_, 1110 '_, 1111 USER_LEN, 1112 CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, 1113 >, 1114 options: &AuthenticationVerificationOptions<'_, '_, O, T>, 1115 ) -> Result<bool, AuthCeremonyErr> { 1116 // Step 6 item 2. 1117 if cred.user_id == response.response.user_handle() { 1118 // Step 6 item 2. 1119 if cred.id == response.raw_id { 1120 self.0.verify(rp_id, response, cred, options, None) 1121 } else { 1122 Err(AuthCeremonyErr::CredentialIdMismatch) 1123 } 1124 } else { 1125 Err(AuthCeremonyErr::UserHandleMismatch) 1126 } 1127 } 1128 #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))] 1129 fn is_eq(&self, other: &Self) -> bool { 1130 self.0.is_eq(&other.0) 1131 } 1132 } 1133 // This is essentially the `NonDiscoverableCredentialRequestOptions` used to create it; however to reduce 1134 // memory usage, we remove all unnecessary data making an instance of this as small as 80 bytes in size on 1135 // `x86_64-unknown-linux-gnu` platforms. This does not include the size of each `CredInfo` which should exist 1136 // elsewhere on the heap but obviously contributes memory overall. 1137 /// State needed to be saved when beginning the authentication ceremony. 1138 /// 1139 /// Saves the necessary information associated with the [`NonDiscoverableCredentialRequestOptions`] used to create 1140 /// it via [`NonDiscoverableCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be 1141 /// performed with [`Self::verify`]. 1142 /// 1143 /// `NonDiscoverableAuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the 1144 /// correct `NonDiscoverableAuthenticationServerState` associated with a [`NonDiscoverableAuthentication`], one 1145 /// should use its corresponding [`NonDiscoverableAuthentication::challenge`]. 1146 #[derive(Debug)] 1147 pub struct NonDiscoverableAuthenticationServerState { 1148 /// Most server state. 1149 state: AuthenticationServerState, 1150 /// The set of credentials that are allowed. 1151 allow_credentials: Vec<CredInfo>, 1152 } 1153 impl NonDiscoverableAuthenticationServerState { 1154 /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true` 1155 /// iff `cred` was mutated. 1156 /// 1157 /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the 1158 /// ceremony. 1159 /// 1160 /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)` 1161 /// is returned. 1162 /// 1163 /// # Errors 1164 /// 1165 /// Errors iff `response` is not valid according to the 1166 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any 1167 /// of the settings in `options`. 1168 #[inline] 1169 pub fn verify< 1170 'a, 1171 const USER_LEN: usize, 1172 O: PartialEq<Origin<'a>>, 1173 T: PartialEq<Origin<'a>>, 1174 EdKey: AsRef<[u8]>, 1175 P256Key: AsRef<[u8]>, 1176 P384Key: AsRef<[u8]>, 1177 RsaKey: AsRef<[u8]>, 1178 >( 1179 self, 1180 rp_id: &RpId, 1181 response: &'a NonDiscoverableAuthentication<USER_LEN>, 1182 cred: &mut AuthenticatedCredential< 1183 '_, 1184 '_, 1185 USER_LEN, 1186 CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, 1187 >, 1188 options: &AuthenticationVerificationOptions<'_, '_, O, T>, 1189 ) -> Result<bool, AuthCeremonyErr> { 1190 response 1191 .response 1192 .user_handle() 1193 .as_ref() 1194 .map_or(Ok(()), |user| { 1195 // Step 6 item 1. 1196 if *user == cred.user_id() { 1197 Ok(()) 1198 } else { 1199 Err(AuthCeremonyErr::UserHandleMismatch) 1200 } 1201 }) 1202 .and_then(|()| { 1203 self.allow_credentials 1204 .iter() 1205 // Step 5. 1206 .find(|c| c.id == response.raw_id) 1207 .ok_or(AuthCeremonyErr::NoMatchingAllowedCredential) 1208 .and_then(|c| { 1209 // Step 6 item 1. 1210 if c.id == cred.id { 1211 self.state 1212 .verify(rp_id, response, cred, options, Some(c.ext)) 1213 } else { 1214 Err(AuthCeremonyErr::CredentialIdMismatch) 1215 } 1216 }) 1217 }) 1218 } 1219 #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))] 1220 fn is_eq(&self, other: &Self) -> bool { 1221 self.state.is_eq(&other.state) && self.allow_credentials == other.allow_credentials 1222 } 1223 } 1224 // This is essentially the `PublicKeyCredentialRequestOptions` used to create it; however to reduce 1225 // memory usage, we remove all unnecessary data making an instance of this 48 bytes in size on 1226 // `x86_64-unknown-linux-gnu` platforms. 1227 /// Shared state used by [`DiscoverableAuthenticationServerState`] and [`NonDiscoverableAuthenticationServerState`]. 1228 #[derive(Debug)] 1229 struct AuthenticationServerState { 1230 // This is a `SentChallenge` since we need `AuthenticationServerState` to be fetchable after receiving the 1231 // response from the client. This response must obviously be constructable; thus its challenge is a 1232 // `SentChallenge`. 1233 // 1234 // This must never be mutated since we want to ensure it is actually a `Challenge` (which 1235 // can only be constructed via `Challenge::new`). This is guaranteed to be true iff 1236 // `serializable_server_state` is not enabled. 1237 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge). 1238 challenge: SentChallenge, 1239 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification). 1240 user_verification: UserVerificationRequirement, 1241 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions). 1242 extensions: ServerExtensionInfo, 1243 /// `Instant` the ceremony expires. 1244 #[cfg(not(feature = "serializable_server_state"))] 1245 expiration: Instant, 1246 /// `SystemTime` the ceremony expires. 1247 #[cfg(feature = "serializable_server_state")] 1248 expiration: SystemTime, 1249 } 1250 impl AuthenticationServerState { 1251 /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true` 1252 /// iff `cred` was mutated. 1253 /// 1254 /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the 1255 /// ceremony. 1256 /// 1257 /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)` 1258 /// is returned. 1259 /// 1260 /// # Errors 1261 /// 1262 /// Errors iff `response` is not valid according to the 1263 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any 1264 /// of the settings in `options`. 1265 fn verify< 1266 'a, 1267 const USER_LEN: usize, 1268 const DISCOVERABLE: bool, 1269 O: PartialEq<Origin<'a>>, 1270 T: PartialEq<Origin<'a>>, 1271 EdKey: AsRef<[u8]>, 1272 P256Key: AsRef<[u8]>, 1273 P384Key: AsRef<[u8]>, 1274 RsaKey: AsRef<[u8]>, 1275 >( 1276 self, 1277 rp_id: &RpId, 1278 response: &'a Authentication<USER_LEN, DISCOVERABLE>, 1279 cred: &mut AuthenticatedCredential< 1280 '_, 1281 '_, 1282 USER_LEN, 1283 CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, 1284 >, 1285 options: &AuthenticationVerificationOptions<'_, '_, O, T>, 1286 cred_ext: Option<ServerCredSpecificExtensionInfo>, 1287 ) -> Result<bool, AuthCeremonyErr> { 1288 // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) 1289 // is handled by: 1290 // 1291 // 1. Calling code. 1292 // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]). 1293 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]). 1294 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 1295 // 5. [`NonDiscoverableAuthenticationServerState::verify`]. 1296 // 6. [`DiscoverableAuthenticationServerState::verify`] and [`NonDiscoverableAuthenticationServerState::verify`]. 1297 // 7. Informative only in that it defines variables. 1298 // 8. [`Self::partial_validate`]. 1299 // 9. [`Self::partial_validate`]. 1300 // 10. [`Self::partial_validate`]. 1301 // 11. [`Self::partial_validate`]. 1302 // 12. [`Self::partial_validate`]. 1303 // 13. [`Self::partial_validate`]. 1304 // 14. [`Self::partial_validate`]. 1305 // 15. [`Self::partial_validate`]. 1306 // 16. [`Self::partial_validate`]. 1307 // 17. [`Self::partial_validate`]. 1308 // 18. [`Self::partial_validate`]. 1309 // 19. [`Self::partial_validate`]. 1310 // 20. [`Self::partial_validate`]. 1311 // 21. [`Self::partial_validate`]. 1312 // 22. Below. 1313 // 23. Below. 1314 // 24. Below. 1315 // 25. Below. 1316 1317 // Steps 8–21. 1318 self.partial_validate( 1319 rp_id, 1320 response, 1321 (&cred.static_state.credential_public_key).into(), 1322 &CeremonyOptions { 1323 allowed_origins: options.allowed_origins, 1324 allowed_top_origins: options.allowed_top_origins, 1325 backup_requirement: options 1326 .backup_requirement 1327 .unwrap_or_else(|| BackupReq::from(cred.dynamic_state.backup)), 1328 #[cfg(feature = "serde_relaxed")] 1329 client_data_json_relaxed: options.client_data_json_relaxed, 1330 }, 1331 ) 1332 .map_err(AuthCeremonyErr::from) 1333 .and_then(|auth_data| { 1334 options 1335 .auth_attachment_enforcement 1336 .validate( 1337 cred.dynamic_state.authenticator_attachment, 1338 response.authenticator_attachment, 1339 ) 1340 .and_then(|auth_attachment| { 1341 let flags = auth_data.flags(); 1342 // Step 23. 1343 validate_extensions( 1344 self.extensions, 1345 flags.user_verified, 1346 cred_ext, 1347 auth_data.extensions(), 1348 options.error_on_unsolicited_extensions, 1349 cred.static_state.client_extension_results.prf.map_or( 1350 CredPrf::None, 1351 |prf| { 1352 if prf.enabled { 1353 cred.static_state 1354 .extensions 1355 .hmac_secret 1356 .map_or(CredPrf::TrueNoHmac, |_| CredPrf::TrueTrueHmac) 1357 } else { 1358 cred.static_state 1359 .extensions 1360 .hmac_secret 1361 .map_or(CredPrf::FalseNoHmac, |_| CredPrf::FalseFalseHmac) 1362 } 1363 }, 1364 ), 1365 ) 1366 .map_err(AuthCeremonyErr::Extension) 1367 .and_then(|()| { 1368 // Step 22. 1369 options 1370 .sig_counter_enforcement 1371 .validate(cred.dynamic_state.sign_count, auth_data.sign_count()) 1372 .and_then(|sig_counter| { 1373 let prev_dyn_state = cred.dynamic_state; 1374 // Step 24 item 2. 1375 cred.dynamic_state.backup = flags.backup; 1376 if options.update_uv && flags.user_verified { 1377 // Step 24 item 3. 1378 cred.dynamic_state.user_verified = true; 1379 } 1380 // Step 24 item 1. 1381 cred.dynamic_state.sign_count = sig_counter; 1382 cred.dynamic_state.authenticator_attachment = auth_attachment; 1383 // Step 25. 1384 if flags.user_verified { 1385 Ok(()) 1386 } else { 1387 match cred.static_state.extensions.cred_protect { 1388 CredentialProtectionPolicy::None | CredentialProtectionPolicy::UserVerificationOptional => Ok(()), 1389 CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => { 1390 if DISCOVERABLE { 1391 Err(AuthCeremonyErr::DiscoverableCredProtectCredentialIdList) 1392 } else { 1393 Ok(()) 1394 } 1395 } 1396 CredentialProtectionPolicy::UserVerificationRequired => { 1397 Err(AuthCeremonyErr::UserNotVerifiedCredProtectRequired) 1398 } 1399 } 1400 }.inspect_err(|_| { 1401 cred.dynamic_state = prev_dyn_state; 1402 }).map(|()| { 1403 prev_dyn_state != cred.dynamic_state 1404 }) 1405 }) 1406 }) 1407 }) 1408 }) 1409 } 1410 #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))] 1411 fn is_eq(&self, other: &Self) -> bool { 1412 self.challenge == other.challenge 1413 && self.user_verification == other.user_verification 1414 && self.extensions == other.extensions 1415 && self.expiration == other.expiration 1416 } 1417 } 1418 impl TimedCeremony for AuthenticationServerState { 1419 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1420 #[inline] 1421 fn expiration(&self) -> Instant { 1422 self.expiration 1423 } 1424 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1425 #[inline] 1426 fn expiration(&self) -> SystemTime { 1427 self.expiration 1428 } 1429 } 1430 impl TimedCeremony for DiscoverableAuthenticationServerState { 1431 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1432 #[inline] 1433 fn expiration(&self) -> Instant { 1434 self.0.expiration() 1435 } 1436 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1437 #[inline] 1438 fn expiration(&self) -> SystemTime { 1439 self.0.expiration() 1440 } 1441 } 1442 impl TimedCeremony for NonDiscoverableAuthenticationServerState { 1443 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1444 #[inline] 1445 fn expiration(&self) -> Instant { 1446 self.state.expiration() 1447 } 1448 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1449 #[inline] 1450 fn expiration(&self) -> SystemTime { 1451 self.state.expiration() 1452 } 1453 } 1454 impl<const USER_LEN: usize, const DISCOVERABLE: bool> Ceremony<USER_LEN, DISCOVERABLE> 1455 for AuthenticationServerState 1456 { 1457 type R = Authentication<USER_LEN, DISCOVERABLE>; 1458 fn rand_challenge(&self) -> SentChallenge { 1459 self.challenge 1460 } 1461 #[cfg(not(feature = "serializable_server_state"))] 1462 fn expiry(&self) -> Instant { 1463 self.expiration 1464 } 1465 #[cfg(feature = "serializable_server_state")] 1466 fn expiry(&self) -> SystemTime { 1467 self.expiration 1468 } 1469 fn user_verification(&self) -> UserVerificationRequirement { 1470 self.user_verification 1471 } 1472 } 1473 impl Borrow<SentChallenge> for AuthenticationServerState { 1474 #[inline] 1475 fn borrow(&self) -> &SentChallenge { 1476 &self.challenge 1477 } 1478 } 1479 impl PartialEq for AuthenticationServerState { 1480 #[inline] 1481 fn eq(&self, other: &Self) -> bool { 1482 self.challenge == other.challenge 1483 } 1484 } 1485 impl PartialEq<&Self> for AuthenticationServerState { 1486 #[inline] 1487 fn eq(&self, other: &&Self) -> bool { 1488 *self == **other 1489 } 1490 } 1491 impl PartialEq<AuthenticationServerState> for &AuthenticationServerState { 1492 #[inline] 1493 fn eq(&self, other: &AuthenticationServerState) -> bool { 1494 **self == *other 1495 } 1496 } 1497 impl Eq for AuthenticationServerState {} 1498 impl Hash for AuthenticationServerState { 1499 #[inline] 1500 fn hash<H: Hasher>(&self, state: &mut H) { 1501 self.challenge.hash(state); 1502 } 1503 } 1504 impl PartialOrd for AuthenticationServerState { 1505 #[inline] 1506 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 1507 Some(self.cmp(other)) 1508 } 1509 } 1510 impl Ord for AuthenticationServerState { 1511 #[inline] 1512 fn cmp(&self, other: &Self) -> Ordering { 1513 self.challenge.cmp(&other.challenge) 1514 } 1515 } 1516 impl Borrow<SentChallenge> for DiscoverableAuthenticationServerState { 1517 #[inline] 1518 fn borrow(&self) -> &SentChallenge { 1519 self.0.borrow() 1520 } 1521 } 1522 impl PartialEq for DiscoverableAuthenticationServerState { 1523 #[inline] 1524 fn eq(&self, other: &Self) -> bool { 1525 self.0 == other.0 1526 } 1527 } 1528 impl PartialEq<&Self> for DiscoverableAuthenticationServerState { 1529 #[inline] 1530 fn eq(&self, other: &&Self) -> bool { 1531 self.0 == other.0 1532 } 1533 } 1534 impl PartialEq<DiscoverableAuthenticationServerState> for &DiscoverableAuthenticationServerState { 1535 #[inline] 1536 fn eq(&self, other: &DiscoverableAuthenticationServerState) -> bool { 1537 self.0 == other.0 1538 } 1539 } 1540 impl Eq for DiscoverableAuthenticationServerState {} 1541 impl Hash for DiscoverableAuthenticationServerState { 1542 #[inline] 1543 fn hash<H: Hasher>(&self, state: &mut H) { 1544 self.0.hash(state); 1545 } 1546 } 1547 impl PartialOrd for DiscoverableAuthenticationServerState { 1548 #[inline] 1549 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 1550 Some(self.cmp(other)) 1551 } 1552 } 1553 impl Ord for DiscoverableAuthenticationServerState { 1554 #[inline] 1555 fn cmp(&self, other: &Self) -> Ordering { 1556 self.0.cmp(&other.0) 1557 } 1558 } 1559 impl Borrow<SentChallenge> for NonDiscoverableAuthenticationServerState { 1560 #[inline] 1561 fn borrow(&self) -> &SentChallenge { 1562 self.state.borrow() 1563 } 1564 } 1565 impl PartialEq for NonDiscoverableAuthenticationServerState { 1566 #[inline] 1567 fn eq(&self, other: &Self) -> bool { 1568 self.state == other.state 1569 } 1570 } 1571 impl PartialEq<&Self> for NonDiscoverableAuthenticationServerState { 1572 #[inline] 1573 fn eq(&self, other: &&Self) -> bool { 1574 self.state == other.state 1575 } 1576 } 1577 impl PartialEq<NonDiscoverableAuthenticationServerState> 1578 for &NonDiscoverableAuthenticationServerState 1579 { 1580 #[inline] 1581 fn eq(&self, other: &NonDiscoverableAuthenticationServerState) -> bool { 1582 self.state == other.state 1583 } 1584 } 1585 impl Eq for NonDiscoverableAuthenticationServerState {} 1586 impl Hash for NonDiscoverableAuthenticationServerState { 1587 #[inline] 1588 fn hash<H: Hasher>(&self, state: &mut H) { 1589 self.state.hash(state); 1590 } 1591 } 1592 impl PartialOrd for NonDiscoverableAuthenticationServerState { 1593 #[inline] 1594 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 1595 Some(self.cmp(other)) 1596 } 1597 } 1598 impl Ord for NonDiscoverableAuthenticationServerState { 1599 #[inline] 1600 fn cmp(&self, other: &Self) -> Ordering { 1601 self.state.cmp(&other.state) 1602 } 1603 } 1604 #[cfg(test)] 1605 mod tests { 1606 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1607 use super::{ 1608 super::super::{ 1609 AuthenticatedCredential, DynamicState, StaticState, UserHandle, 1610 response::{ 1611 Backup, 1612 auth::{DiscoverableAuthenticatorAssertion, HmacSecret}, 1613 register::{ 1614 AuthenticationExtensionsPrfOutputs, AuthenticatorExtensionOutputStaticState, 1615 ClientExtensionsOutputsStaticState, CredentialProtectionPolicy, Ed25519PubKey, 1616 }, 1617 }, 1618 }, 1619 AuthCeremonyErr, AuthenticationVerificationOptions, AuthenticatorAttachment, 1620 AuthenticatorAttachmentEnforcement, CompressedPubKey, DiscoverableAuthentication, 1621 ExtensionErr, OneOrTwo, PrfInput, SignatureCounterEnforcement, 1622 }; 1623 #[cfg(all( 1624 feature = "custom", 1625 any( 1626 feature = "serializable_server_state", 1627 not(any(feature = "bin", feature = "serde")) 1628 ) 1629 ))] 1630 use super::{ 1631 super::{super::AggErr, Challenge, CredentialId, RpId, UserVerificationRequirement}, 1632 DiscoverableCredentialRequestOptions, ExtensionReq, 1633 }; 1634 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1635 use super::{ 1636 super::{ 1637 super::bin::{Decode as _, Encode as _}, 1638 AsciiDomain, AuthTransports, 1639 }, 1640 AllowedCredential, AllowedCredentials, CredentialSpecificExtension, Credentials as _, 1641 DiscoverableAuthenticationServerState, Extension, NonDiscoverableAuthenticationServerState, 1642 NonDiscoverableCredentialRequestOptions, PrfInputOwned, PublicKeyCredentialDescriptor, 1643 }; 1644 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1645 use ed25519_dalek::{Signer as _, SigningKey}; 1646 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1647 use rsa::sha2::{Digest as _, Sha256}; 1648 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1649 const CBOR_BYTES: u8 = 0b010_00000; 1650 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1651 const CBOR_TEXT: u8 = 0b011_00000; 1652 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1653 const CBOR_MAP: u8 = 0b101_00000; 1654 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 1655 #[test] 1656 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1657 fn eddsa_auth_ser() -> Result<(), AggErr> { 1658 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 1659 let mut creds = AllowedCredentials::with_capacity(1); 1660 _ = creds.push(AllowedCredential { 1661 credential: PublicKeyCredentialDescriptor { 1662 id: CredentialId::try_from(vec![0; 16])?, 1663 transports: AuthTransports::NONE, 1664 }, 1665 extension: CredentialSpecificExtension { 1666 prf: Some(PrfInputOwned { 1667 first: Vec::new(), 1668 second: Some(Vec::new()), 1669 ext_req: ExtensionReq::Require, 1670 }), 1671 }, 1672 }); 1673 let mut opts = NonDiscoverableCredentialRequestOptions::second_factor(&rp_id, creds); 1674 opts.options.user_verification = UserVerificationRequirement::Required; 1675 opts.options.challenge = Challenge(0); 1676 opts.options.extensions = Extension { prf: None }; 1677 let server = opts.start_ceremony()?.0; 1678 let enc_data = server.encode()?; 1679 assert_eq!(enc_data.capacity(), 16 + 2 + 1 + 1 + 12 + 2 + 128 + 1); 1680 assert_eq!(enc_data.len(), 16 + 2 + 1 + 1 + 12 + 2 + 16 + 2); 1681 assert!( 1682 server.is_eq(&NonDiscoverableAuthenticationServerState::decode( 1683 enc_data.as_slice() 1684 )?) 1685 ); 1686 let mut opts_2 = DiscoverableCredentialRequestOptions::passkey(&rp_id); 1687 opts_2.public_key.challenge = Challenge(0); 1688 opts_2.public_key.extensions = Extension { prf: None }; 1689 let server_2 = opts_2.start_ceremony()?.0; 1690 let enc_data_2 = server_2 1691 .encode() 1692 .map_err(AggErr::EncodeDiscoverableAuthenticationServerState)?; 1693 assert_eq!(enc_data_2.capacity(), enc_data_2.len()); 1694 assert_eq!(enc_data_2.len(), 16 + 1 + 1 + 12); 1695 assert!( 1696 server_2.is_eq(&DiscoverableAuthenticationServerState::decode( 1697 enc_data_2.as_slice() 1698 )?) 1699 ); 1700 Ok(()) 1701 } 1702 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1703 #[derive(Clone, Copy)] 1704 struct TestResponseOptions { 1705 user_verified: bool, 1706 hmac: HmacSecret, 1707 } 1708 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1709 #[derive(Clone, Copy)] 1710 enum PrfCredOptions { 1711 None, 1712 FalseNoHmac, 1713 FalseHmacFalse, 1714 TrueNoHmac, 1715 TrueHmacTrue, 1716 } 1717 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1718 #[derive(Clone, Copy)] 1719 struct TestCredOptions { 1720 cred_protect: CredentialProtectionPolicy, 1721 prf: PrfCredOptions, 1722 } 1723 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1724 #[derive(Clone, Copy)] 1725 enum PrfUvOptions { 1726 /// `true` iff `UserVerificationRequirement::Required` should be used; otherwise 1727 /// `UserVerificationRequirement::Preferred` is used. 1728 None(bool), 1729 Prf((PrfInput<'static, 'static>, ExtensionReq)), 1730 } 1731 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1732 #[derive(Clone, Copy)] 1733 struct TestRequestOptions { 1734 error_unsolicited: bool, 1735 prf_uv: PrfUvOptions, 1736 } 1737 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1738 #[derive(Clone, Copy)] 1739 struct TestOptions { 1740 request: TestRequestOptions, 1741 response: TestResponseOptions, 1742 cred: TestCredOptions, 1743 } 1744 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1745 fn generate_client_data_json() -> Vec<u8> { 1746 let mut json = Vec::with_capacity(256); 1747 json.extend_from_slice(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice()); 1748 json 1749 } 1750 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 1751 #[expect(clippy::type_complexity, reason = "fine")] 1752 #[expect(clippy::too_many_lines, reason = "a lot to test")] 1753 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1754 fn generate_authenticator_data_public_key_sig( 1755 opts: TestResponseOptions, 1756 ) -> ( 1757 Vec<u8>, 1758 CompressedPubKey<[u8; 32], [u8; 32], [u8; 48], Vec<u8>>, 1759 Vec<u8>, 1760 ) { 1761 let mut authenticator_data = Vec::with_capacity(256); 1762 authenticator_data.extend_from_slice( 1763 [ 1764 // RP ID HASH. 1765 // This will be overwritten later. 1766 0, 1767 0, 1768 0, 1769 0, 1770 0, 1771 0, 1772 0, 1773 0, 1774 0, 1775 0, 1776 0, 1777 0, 1778 0, 1779 0, 1780 0, 1781 0, 1782 0, 1783 0, 1784 0, 1785 0, 1786 0, 1787 0, 1788 0, 1789 0, 1790 0, 1791 0, 1792 0, 1793 0, 1794 0, 1795 0, 1796 0, 1797 0, 1798 // FLAGS. 1799 // UP, UV, AT, and ED (right-to-left). 1800 0b0000_0001 1801 | if opts.user_verified { 1802 0b0000_0100 1803 } else { 1804 0b0000_0000 1805 } 1806 | if matches!(opts.hmac, HmacSecret::None) { 1807 0 1808 } else { 1809 0b1000_0000 1810 }, 1811 // COUNTER. 1812 // 0 as 32-bit big endian. 1813 0, 1814 0, 1815 0, 1816 0, 1817 ] 1818 .as_slice(), 1819 ); 1820 authenticator_data[..32].copy_from_slice(&Sha256::digest(b"example.com")); 1821 match opts.hmac { 1822 HmacSecret::None => {} 1823 HmacSecret::One => { 1824 authenticator_data.extend_from_slice( 1825 [ 1826 CBOR_MAP | 1, 1827 // CBOR text of length 11. 1828 CBOR_TEXT | 11, 1829 b'h', 1830 b'm', 1831 b'a', 1832 b'c', 1833 b'-', 1834 b's', 1835 b'e', 1836 b'c', 1837 b'r', 1838 b'e', 1839 b't', 1840 CBOR_BYTES | 24, 1841 48, 1842 ] 1843 .as_slice(), 1844 ); 1845 authenticator_data.extend_from_slice([0; 48].as_slice()); 1846 } 1847 HmacSecret::Two => { 1848 authenticator_data.extend_from_slice( 1849 [ 1850 CBOR_MAP | 1, 1851 // CBOR text of length 11. 1852 CBOR_TEXT | 11, 1853 b'h', 1854 b'm', 1855 b'a', 1856 b'c', 1857 b'-', 1858 b's', 1859 b'e', 1860 b'c', 1861 b'r', 1862 b'e', 1863 b't', 1864 CBOR_BYTES | 24, 1865 80, 1866 ] 1867 .as_slice(), 1868 ); 1869 authenticator_data.extend_from_slice([0; 80].as_slice()); 1870 } 1871 } 1872 let len = authenticator_data.len(); 1873 authenticator_data 1874 .extend_from_slice(&Sha256::digest(generate_client_data_json().as_slice())); 1875 let sig_key = SigningKey::from_bytes(&[0; 32]); 1876 let sig = sig_key.sign(authenticator_data.as_slice()).to_vec(); 1877 authenticator_data.truncate(len); 1878 ( 1879 authenticator_data, 1880 CompressedPubKey::Ed25519(Ed25519PubKey::from(sig_key.verifying_key().to_bytes())), 1881 sig, 1882 ) 1883 } 1884 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1885 fn validate(options: TestOptions) -> Result<(), AggErr> { 1886 let rp_id = RpId::Domain("example.com".to_owned().try_into()?); 1887 let user = UserHandle::from([0; 1]); 1888 let (authenticator_data, credential_public_key, signature) = 1889 generate_authenticator_data_public_key_sig(options.response); 1890 let credential_id = CredentialId::try_from(vec![0; 16])?; 1891 let authentication = DiscoverableAuthentication::new( 1892 credential_id.clone(), 1893 DiscoverableAuthenticatorAssertion::new( 1894 generate_client_data_json(), 1895 authenticator_data, 1896 signature, 1897 user, 1898 ), 1899 AuthenticatorAttachment::None, 1900 ); 1901 let auth_opts = AuthenticationVerificationOptions::<'static, 'static, &str, &str> { 1902 allowed_origins: [].as_slice(), 1903 allowed_top_origins: None, 1904 auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::Update(false), 1905 backup_requirement: None, 1906 error_on_unsolicited_extensions: options.request.error_unsolicited, 1907 sig_counter_enforcement: SignatureCounterEnforcement::Fail, 1908 update_uv: false, 1909 #[cfg(feature = "serde_relaxed")] 1910 client_data_json_relaxed: false, 1911 }; 1912 let mut opts = DiscoverableCredentialRequestOptions::passkey(&rp_id); 1913 opts.public_key.challenge = Challenge(0); 1914 opts.public_key.user_verification = UserVerificationRequirement::Preferred; 1915 match options.request.prf_uv { 1916 PrfUvOptions::None(required) => { 1917 if required { 1918 opts.public_key.user_verification = UserVerificationRequirement::Required; 1919 } 1920 } 1921 PrfUvOptions::Prf(input) => { 1922 opts.public_key.user_verification = UserVerificationRequirement::Required; 1923 opts.public_key.extensions.prf = Some(input); 1924 } 1925 } 1926 let mut cred = AuthenticatedCredential::new( 1927 (&credential_id).into(), 1928 &user, 1929 StaticState { 1930 credential_public_key, 1931 extensions: AuthenticatorExtensionOutputStaticState { 1932 cred_protect: options.cred.cred_protect, 1933 hmac_secret: match options.cred.prf { 1934 PrfCredOptions::None 1935 | PrfCredOptions::FalseNoHmac 1936 | PrfCredOptions::TrueNoHmac => None, 1937 PrfCredOptions::FalseHmacFalse => Some(false), 1938 PrfCredOptions::TrueHmacTrue => Some(true), 1939 }, 1940 }, 1941 client_extension_results: ClientExtensionsOutputsStaticState { 1942 prf: match options.cred.prf { 1943 PrfCredOptions::None => None, 1944 PrfCredOptions::FalseNoHmac | PrfCredOptions::FalseHmacFalse => { 1945 Some(AuthenticationExtensionsPrfOutputs { enabled: false }) 1946 } 1947 PrfCredOptions::TrueNoHmac | PrfCredOptions::TrueHmacTrue => { 1948 Some(AuthenticationExtensionsPrfOutputs { enabled: true }) 1949 } 1950 }, 1951 }, 1952 }, 1953 DynamicState { 1954 user_verified: true, 1955 backup: Backup::NotEligible, 1956 sign_count: 0, 1957 authenticator_attachment: AuthenticatorAttachment::None, 1958 }, 1959 )?; 1960 opts.start_ceremony()? 1961 .0 1962 .verify(&rp_id, &authentication, &mut cred, &auth_opts) 1963 .map_err(AggErr::AuthCeremony) 1964 .map(|_| ()) 1965 } 1966 /// Test all, and only, possible `UserNotVerified` errors. 1967 /// 4 * 5 * 3 * 2 * 5 = 600 tests. 1968 /// We ignore this due to how long it takes (around 4 seconds or so). 1969 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 1970 #[ignore = "slow"] 1971 #[test] 1972 fn uv_required_err() { 1973 const ALL_CRED_PROTECT_OPTIONS: [CredentialProtectionPolicy; 4] = [ 1974 CredentialProtectionPolicy::None, 1975 CredentialProtectionPolicy::UserVerificationOptional, 1976 CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList, 1977 CredentialProtectionPolicy::UserVerificationRequired, 1978 ]; 1979 const ALL_PRF_CRED_OPTIONS: [PrfCredOptions; 5] = [ 1980 PrfCredOptions::None, 1981 PrfCredOptions::FalseNoHmac, 1982 PrfCredOptions::FalseHmacFalse, 1983 PrfCredOptions::TrueNoHmac, 1984 PrfCredOptions::TrueHmacTrue, 1985 ]; 1986 const ALL_HMAC_OPTIONS: [HmacSecret; 3] = 1987 [HmacSecret::None, HmacSecret::One, HmacSecret::Two]; 1988 const ALL_UNSOLICIT_OPTIONS: [bool; 2] = [false, true]; 1989 const ALL_NOT_FALSE_PRF_UV_OPTIONS: [PrfUvOptions; 5] = [ 1990 PrfUvOptions::None(true), 1991 PrfUvOptions::Prf(( 1992 PrfInput { 1993 first: [].as_slice(), 1994 second: None, 1995 }, 1996 ExtensionReq::Require, 1997 )), 1998 PrfUvOptions::Prf(( 1999 PrfInput { 2000 first: [].as_slice(), 2001 second: None, 2002 }, 2003 ExtensionReq::Allow, 2004 )), 2005 PrfUvOptions::Prf(( 2006 PrfInput { 2007 first: [].as_slice(), 2008 second: Some([].as_slice()), 2009 }, 2010 ExtensionReq::Require, 2011 )), 2012 PrfUvOptions::Prf(( 2013 PrfInput { 2014 first: [].as_slice(), 2015 second: Some([].as_slice()), 2016 }, 2017 ExtensionReq::Allow, 2018 )), 2019 ]; 2020 for cred_protect in ALL_CRED_PROTECT_OPTIONS { 2021 for prf in ALL_PRF_CRED_OPTIONS { 2022 for hmac in ALL_HMAC_OPTIONS { 2023 for error_unsolicited in ALL_UNSOLICIT_OPTIONS { 2024 for prf_uv in ALL_NOT_FALSE_PRF_UV_OPTIONS { 2025 assert!(validate(TestOptions { 2026 request: TestRequestOptions { 2027 error_unsolicited, 2028 prf_uv, 2029 }, 2030 response: TestResponseOptions { 2031 user_verified: false, 2032 hmac, 2033 }, 2034 cred: TestCredOptions { cred_protect, prf, }, 2035 }).is_err_and(|err| matches!(err, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::UserNotVerified)))); 2036 } 2037 } 2038 } 2039 } 2040 } 2041 } 2042 /// Test all, and only, possible `UserNotVerified` errors. 2043 /// 4 * 5 * 2 * 2 = 80 tests. 2044 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 2045 #[test] 2046 fn forbidden_hmac() { 2047 const ALL_CRED_PROTECT_OPTIONS: [CredentialProtectionPolicy; 4] = [ 2048 CredentialProtectionPolicy::None, 2049 CredentialProtectionPolicy::UserVerificationOptional, 2050 CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList, 2051 CredentialProtectionPolicy::UserVerificationRequired, 2052 ]; 2053 const ALL_PRF_CRED_OPTIONS: [PrfCredOptions; 5] = [ 2054 PrfCredOptions::None, 2055 PrfCredOptions::FalseNoHmac, 2056 PrfCredOptions::FalseHmacFalse, 2057 PrfCredOptions::TrueNoHmac, 2058 PrfCredOptions::TrueHmacTrue, 2059 ]; 2060 const ALL_HMAC_OPTIONS: [HmacSecret; 2] = [HmacSecret::One, HmacSecret::Two]; 2061 const ALL_UV_OPTIONS: [bool; 2] = [false, true]; 2062 for cred_protect in ALL_CRED_PROTECT_OPTIONS { 2063 for prf in ALL_PRF_CRED_OPTIONS { 2064 for hmac in ALL_HMAC_OPTIONS { 2065 for user_verified in ALL_UV_OPTIONS { 2066 assert!(validate(TestOptions { 2067 request: TestRequestOptions { 2068 error_unsolicited: true, 2069 prf_uv: PrfUvOptions::None(false), 2070 }, 2071 response: TestResponseOptions { 2072 user_verified, 2073 hmac, 2074 }, 2075 cred: TestCredOptions { cred_protect, prf, }, 2076 }).is_err_and(|err| matches!(err, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::ForbiddenHmacSecret))))); 2077 } 2078 } 2079 } 2080 } 2081 } 2082 #[expect(clippy::panic_in_result_fn, reason = "OK in tests")] 2083 #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] 2084 #[test] 2085 fn prf() -> Result<(), AggErr> { 2086 let mut opts = TestOptions { 2087 request: TestRequestOptions { 2088 error_unsolicited: false, 2089 prf_uv: PrfUvOptions::Prf(( 2090 PrfInput { 2091 first: [].as_slice(), 2092 second: None, 2093 }, 2094 ExtensionReq::Allow, 2095 )), 2096 }, 2097 response: TestResponseOptions { 2098 user_verified: true, 2099 hmac: HmacSecret::None, 2100 }, 2101 cred: TestCredOptions { 2102 cred_protect: CredentialProtectionPolicy::None, 2103 prf: PrfCredOptions::None, 2104 }, 2105 }; 2106 validate(opts)?; 2107 opts.request.prf_uv = PrfUvOptions::Prf(( 2108 PrfInput { 2109 first: [].as_slice(), 2110 second: None, 2111 }, 2112 ExtensionReq::Require, 2113 )); 2114 opts.cred.prf = PrfCredOptions::TrueHmacTrue; 2115 assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::MissingHmacSecret))))); 2116 opts.response.hmac = HmacSecret::One; 2117 opts.request.prf_uv = PrfUvOptions::Prf(( 2118 PrfInput { 2119 first: [].as_slice(), 2120 second: None, 2121 }, 2122 ExtensionReq::Allow, 2123 )); 2124 opts.cred.prf = PrfCredOptions::TrueNoHmac; 2125 assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::HmacSecretForNonHmacSecretCredential))))); 2126 opts.response.hmac = HmacSecret::Two; 2127 assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::InvalidHmacSecretValue(OneOrTwo::One, OneOrTwo::Two)))))); 2128 opts.response.hmac = HmacSecret::One; 2129 opts.cred.prf = PrfCredOptions::FalseNoHmac; 2130 assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::HmacSecretForNonHmacSecretCredential))))); 2131 opts.response.user_verified = false; 2132 opts.request.prf_uv = PrfUvOptions::None(false); 2133 opts.cred.prf = PrfCredOptions::TrueHmacTrue; 2134 assert!(validate(opts).is_err_and(|e| matches!(e, AggErr::AuthCeremony(auth_err) if matches!(auth_err, AuthCeremonyErr::Extension(ext_err) if matches!(ext_err, ExtensionErr::UserNotVerifiedHmacSecret))))); 2135 Ok(()) 2136 } 2137 }