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