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