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