auth.rs (50316B)
1 #[cfg(doc)] 2 use super::{ 3 super::response::{ 4 auth::AuthenticatorData, 5 register::{DynamicState, StaticState}, 6 Backup, CollectedClientData, Flag, 7 }, 8 register::{self, PublicKeyCredentialCreationOptions}, 9 AsciiDomain, DomainOrigin, Url, 10 }; 11 use super::{ 12 super::{ 13 response::{ 14 auth::{ 15 error::{AuthCeremonyErr, ExtensionErr, OneOrTwo}, 16 Authentication, AuthenticatorExtensionOutput, HmacSecret, 17 }, 18 register::CompressedPubKey, 19 AuthenticatorAttachment, 20 }, 21 AuthenticatedCredential, 22 }, 23 auth::error::{RequestOptionsErr, SecondFactorErr}, 24 BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId, Credentials, ExtensionReq, Hint, 25 Origin, PublicKeyCredentialDescriptor, RpId, SentChallenge, ServerState, 26 UserVerificationRequirement, THREE_HUNDRED_THOUSAND, 27 }; 28 use core::{ 29 borrow::Borrow, 30 cmp::Ordering, 31 hash::{Hash, Hasher}, 32 num::{NonZeroU32, NonZeroU64}, 33 time::Duration, 34 }; 35 #[cfg(any(doc, not(feature = "serializable_server_state")))] 36 use std::time::Instant; 37 #[cfg(any(doc, feature = "serializable_server_state"))] 38 use std::time::SystemTime; 39 /// Contains error types. 40 pub mod error; 41 /// Contains functionality to serialize data to a client. 42 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 43 #[cfg(feature = "serde")] 44 mod ser; 45 /// Contains functionality to (de)serialize [`AuthenticationServerState`] to a data store. 46 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 47 #[cfg(feature = "serializable_server_state")] 48 pub mod ser_server_state; 49 /// Controls how [signature counter](https://www.w3.org/TR/webauthn-3/#signature-counter) is enforced. 50 /// 51 /// Note that if the previous signature counter is positive and the new counter is not strictly greater, then the 52 /// authenticator is likely a clone (i.e., there are at least two copies of the private key). 53 #[derive(Clone, Copy, Debug, Default)] 54 pub enum SignatureCounterEnforcement { 55 /// Fail the authentication ceremony if the counter is less than or equal to the previous value when the 56 /// previous value is positive. 57 #[default] 58 Fail, 59 /// When the counter is less than the previous value, don't fail and update the value. 60 /// 61 /// Note in the special case that the new signature counter is 0, [`DynamicState::sign_count`] _won't_ 62 /// be updated since that would allow an attacker to permanently disable the counter. 63 Update, 64 /// When the counter is less than the previous value, don't fail but don't update the value. 65 Ignore, 66 } 67 impl SignatureCounterEnforcement { 68 /// Validates the signature counter based on `self`. 69 const fn validate(self, prev: u32, cur: u32) -> Result<u32, AuthCeremonyErr> { 70 if prev == 0 || cur > prev { 71 Ok(cur) 72 } else { 73 match self { 74 Self::Fail => Err(AuthCeremonyErr::SignatureCounter), 75 // When the new counter is `0`, we use the previous counter to avoid an attacker from 76 // being able to permanently disable it. 77 Self::Update => Ok(if cur == 0 { prev } else { cur }), 78 Self::Ignore => Ok(prev), 79 } 80 } 81 } 82 } 83 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues). 84 /// 85 /// This is only applicable if 86 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension) 87 /// is `true` when registering a new credential with [`register::Extension::prf`] and 88 /// [`PublicKeyCredentialRequestOptions::user_verification`] is [`UserVerificationRequirement::Required`]. 89 /// 90 /// Unlike the spec, it is forbidden for 91 /// [the decrypted outputs](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) to be 92 /// passed back in an effort to ensure sensitive data remains client-side. This means 93 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) must not exist, 94 /// be `null`, or be an 95 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 96 /// such that [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) does not exist, 97 /// is `null`, or is an 98 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) such 99 /// that [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is `null` and 100 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) does not exist or is `null`. 101 /// 102 /// For the owned analog, see [`PrfInputOwned`]. 103 /// 104 /// When relying on discoverable requests 105 /// (i.e., [`PublicKeyCredentialRequestOptions::allow_credentials`] is empty), 106 /// one will likely use a static PRF input for _all_ credentials since rolling over PRF inputs 107 /// is not feasible. One uses this type for such a thing. In other words, `'a` will likely 108 /// be `'static` and [`Self::second`] will likely be `None`. 109 #[derive(Clone, Copy, Debug)] 110 pub struct PrfInput<'a> { 111 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 112 pub first: &'a [u8], 113 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 114 pub second: Option<&'a [u8]>, 115 /// Response requirements. 116 pub ext_info: ExtensionReq, 117 } 118 /// Owned version of [`PrfInput`]. 119 /// 120 /// When relying on non-discoverable requests 121 /// (i.e., [`PublicKeyCredentialRequestOptions::allow_credentials`] is non-empty), 122 /// it's recommended to use credential-specific PRF inputs that are continuously rolled over. 123 /// One uses this type for such a thing. 124 #[derive(Debug)] 125 pub struct PrfInputOwned { 126 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 127 pub first: Vec<u8>, 128 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 129 pub second: Option<Vec<u8>>, 130 /// Response requirements. 131 pub ext_info: ExtensionReq, 132 } 133 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client. 134 #[derive(Clone, Copy, Debug, Default)] 135 pub struct Extension<'prf> { 136 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 137 /// 138 /// If both [`CredentialSpecificExtension::prf`] and this are [`Some`], then `CredentialSpecificExtension::prf` 139 /// takes priority. 140 pub prf: Option<PrfInput<'prf>>, 141 } 142 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client that 143 /// are credential-specific which among other things implies a non-discoverable request. 144 #[derive(Debug, Default)] 145 pub struct CredentialSpecificExtension { 146 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 147 /// 148 /// If both [`Extension::prf`] and this are [`Some`], then this take priority. 149 pub prf: Option<PrfInputOwned>, 150 } 151 /// Registered credential used in 152 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 153 #[derive(Debug)] 154 pub struct AllowedCredential { 155 /// The registered credential. 156 pub credential: PublicKeyCredentialDescriptor<Vec<u8>>, 157 /// Credential-specific extensions. 158 pub extension: CredentialSpecificExtension, 159 } 160 impl From<PublicKeyCredentialDescriptor<Vec<u8>>> for AllowedCredential { 161 #[inline] 162 fn from(credential: PublicKeyCredentialDescriptor<Vec<u8>>) -> Self { 163 Self { 164 credential, 165 extension: CredentialSpecificExtension::default(), 166 } 167 } 168 } 169 /// Queue of unique [`AllowedCredential`]s. 170 #[derive(Debug, Default)] 171 pub struct AllowedCredentials { 172 /// Allowed credentials. 173 creds: Vec<AllowedCredential>, 174 /// Number of `AllowedCredential`s that have PRF inputs. 175 /// 176 /// Useful to help serialization. 177 prf_count: usize, 178 } 179 impl Credentials for AllowedCredentials { 180 type Credential = AllowedCredential; 181 /// # Examples 182 /// 183 /// ``` 184 /// # use webauthn_rp::request::{auth::AllowedCredentials, Credentials}; 185 /// assert!(AllowedCredentials::with_capacity(1).as_ref().is_empty()); 186 /// ``` 187 #[inline] 188 #[must_use] 189 fn with_capacity(capacity: usize) -> Self { 190 Self { 191 creds: Vec::with_capacity(capacity), 192 prf_count: 0, 193 } 194 } 195 /// # Examples 196 /// 197 /// ``` 198 /// # #[cfg(all(feature = "bin", feature = "custom"))] 199 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 200 /// # use webauthn_rp::{ 201 /// # request::{auth::AllowedCredentials, PublicKeyCredentialDescriptor, Credentials}, 202 /// # response::{AuthTransports, CredentialId}, 203 /// # }; 204 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 205 /// /// from the database. 206 /// # #[cfg(all(feature = "bin", feature = "custom"))] 207 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 208 /// // ⋮ 209 /// # AuthTransports::decode(32) 210 /// } 211 /// let mut creds = AllowedCredentials::with_capacity(1); 212 /// assert!(creds.as_ref().is_empty()); 213 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 214 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 215 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 216 /// # #[cfg(all(feature = "bin", feature = "custom"))] 217 /// let id = CredentialId::try_from(vec![0; 16])?; 218 /// # #[cfg(all(feature = "bin", feature = "custom"))] 219 /// let transports = get_transports((&id).into())?; 220 /// # #[cfg(all(feature = "bin", feature = "custom"))] 221 /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into())); 222 /// # #[cfg(all(feature = "bin", feature = "custom"))] 223 /// let id_copy = CredentialId::try_from(vec![0; 16])?; 224 /// # #[cfg(all(feature = "bin", feature = "custom"))] 225 /// let transports_2 = AuthTransports::NONE; 226 /// // Duplicate `CredentialId`s don't get added. 227 /// # #[cfg(all(feature = "bin", feature = "custom"))] 228 /// assert!(!creds.push( 229 /// PublicKeyCredentialDescriptor { 230 /// id: id_copy, 231 /// transports: transports_2 232 /// } 233 /// .into() 234 /// )); 235 /// # Ok::<_, webauthn_rp::AggErr>(()) 236 /// ``` 237 #[expect( 238 clippy::arithmetic_side_effects, 239 reason = "comment explains how overflow is not possible" 240 )] 241 #[inline] 242 fn push(&mut self, cred: Self::Credential) -> bool { 243 self.creds 244 .iter() 245 .try_fold((), |(), c| { 246 if c.credential.id == cred.credential.id { 247 Err(()) 248 } else { 249 Ok(()) 250 } 251 }) 252 .is_ok_and(|()| { 253 // This can't overflow since `self.creds.push` would `panic` since 254 // `self.prf_count <= self.creds.len()`. 255 self.prf_count += usize::from(cred.extension.prf.is_some()); 256 self.creds.push(cred); 257 true 258 }) 259 } 260 #[inline] 261 fn len(&self) -> usize { 262 self.creds.len() 263 } 264 } 265 impl AsRef<[AllowedCredential]> for AllowedCredentials { 266 #[inline] 267 fn as_ref(&self) -> &[AllowedCredential] { 268 self.creds.as_slice() 269 } 270 } 271 impl From<&AllowedCredentials> for Vec<CredInfo> { 272 #[inline] 273 fn from(value: &AllowedCredentials) -> Self { 274 let len = value.creds.len(); 275 value 276 .creds 277 .iter() 278 .fold(Self::with_capacity(len), |mut creds, cred| { 279 creds.push(CredInfo { 280 id: cred.credential.id.clone(), 281 ext: (&cred.extension).into(), 282 }); 283 creds 284 }) 285 } 286 } 287 /// Helper that verifies the overlap of [`PublicKeyCredentialRequestOptions::start_ceremony`] and 288 /// [`AuthenticationServerState::decode`]. 289 fn validate_options_helper( 290 ext: ServerExtensionInfo, 291 uv: UserVerificationRequirement, 292 creds: &[CredInfo], 293 ) -> Result<(), RequestOptionsErr> { 294 // If PRF is set, the user has to verify themselves. 295 ext.prf 296 .as_ref() 297 .map_or(Ok(()), |_| { 298 if matches!(uv, UserVerificationRequirement::Required) { 299 Ok(()) 300 } else { 301 Err(RequestOptionsErr::PrfWithoutUserVerification) 302 } 303 }) 304 .and_then(|()| { 305 creds.iter().try_fold((), |(), cred| { 306 // If PRF is set, the user has to verify themselves. 307 cred.ext.prf.as_ref().map_or(Ok(()), |_| { 308 if matches!(uv, UserVerificationRequirement::Required) { 309 Ok(()) 310 } else { 311 Err(RequestOptionsErr::PrfWithoutUserVerification) 312 } 313 }) 314 }) 315 }) 316 } 317 /// The [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions) 318 /// to send to the client when authenticating a credential. 319 /// 320 /// Upon saving the [`AuthenticationServerState`] returned from [`Self::start_ceremony`], one MUST send 321 /// [`AuthenticationClientState`] to the client ASAP. After receiving the newly created [`Authentication`], it 322 /// is validated using [`AuthenticationServerState::verify`]. 323 #[derive(Debug)] 324 pub struct PublicKeyCredentialRequestOptions<'rp_id, 'prf> { 325 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge). 326 pub challenge: Challenge, 327 /// [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-timeout). 328 /// 329 /// Note we require a positive value despite the spec allowing an optional nonnegative value. This jives 330 /// with the fact that in-memory storage is required when `serializable_server_state` is not enabled 331 /// when authenticating credentials as no timeout would make out-of-memory (OOM) conditions more likely. 332 pub timeout: NonZeroU32, 333 /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-rpid). 334 /// 335 /// This MUST be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] used when the credential was registered. 336 pub rp_id: &'rp_id RpId, 337 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 338 pub allow_credentials: AllowedCredentials, 339 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification). 340 pub user_verification: UserVerificationRequirement, 341 /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints). 342 pub hints: Hint, 343 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions). 344 pub extensions: Extension<'prf>, 345 } 346 impl<'rp_id, 'prf> PublicKeyCredentialRequestOptions<'rp_id, 'prf> { 347 /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to 348 /// [`UserVerificationRequirement::Required`] and [`Self::timeout`] set to 5 minutes, 349 /// 350 /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the 351 /// credential was registered. 352 /// 353 /// # Examples 354 /// 355 /// ``` 356 /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId, UserVerificationRequirement}; 357 /// assert!(matches!( 358 /// PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)).user_verification, 359 /// UserVerificationRequirement::Required 360 /// )); 361 /// # Ok::<_, webauthn_rp::AggErr>(()) 362 /// ``` 363 #[inline] 364 #[must_use] 365 pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self { 366 Self { 367 challenge: Challenge::new(), 368 timeout: THREE_HUNDRED_THOUSAND, 369 rp_id, 370 allow_credentials: AllowedCredentials::new(), 371 user_verification: UserVerificationRequirement::Required, 372 hints: Hint::None, 373 extensions: Extension::default(), 374 } 375 } 376 /// Creates a `PublicKeyCredentialRequestOptions` with [`Self::user_verification`] set to 377 /// [`UserVerificationRequirement::Discouraged`] and [`Self::timeout`] set to 5 minutes. 378 /// 379 /// Note `rp_id` _must_ be the same as the [`PublicKeyCredentialCreationOptions::rp_id`] when the 380 /// [`AllowedCredential`]s were registered. 381 /// 382 /// # Errors 383 /// 384 /// Errors iff [`AllowedCredentials`] is empty. 385 /// 386 /// # Examples 387 /// 388 /// ``` 389 /// # #[cfg(all(feature = "bin", feature = "custom"))] 390 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 391 /// # use webauthn_rp::{ 392 /// # request::{ 393 /// # auth::{AllowedCredentials, PublicKeyCredentialRequestOptions}, 394 /// # AsciiDomain, RpId, PublicKeyCredentialDescriptor, Credentials 395 /// # }, 396 /// # response::{AuthTransports, CredentialId}, 397 /// # }; 398 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 399 /// /// from the database. 400 /// # #[cfg(all(feature = "bin", feature = "custom"))] 401 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 402 /// // ⋮ 403 /// # AuthTransports::decode(32) 404 /// } 405 /// let mut creds = AllowedCredentials::with_capacity(1); 406 /// assert!(creds.as_ref().is_empty()); 407 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 408 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 409 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 410 /// # #[cfg(all(feature = "bin", feature = "custom"))] 411 /// let id = CredentialId::try_from(vec![0; 16])?; 412 /// # #[cfg(all(feature = "bin", feature = "custom"))] 413 /// let transports = get_transports((&id).into())?; 414 /// # #[cfg(all(feature = "bin", feature = "custom"))] 415 /// assert!(creds.push(PublicKeyCredentialDescriptor { id, transports }.into())); 416 /// # #[cfg(all(feature = "bin", feature = "custom"))] 417 /// assert_eq!( 418 /// PublicKeyCredentialRequestOptions::second_factor(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), creds)? 419 /// .allow_credentials 420 /// .as_ref() 421 /// .len(), 422 /// 1 423 /// ); 424 /// # Ok::<_, webauthn_rp::AggErr>(()) 425 /// ``` 426 #[inline] 427 pub fn second_factor<'a: 'rp_id>( 428 rp_id: &'a RpId, 429 creds: AllowedCredentials, 430 ) -> Result<Self, SecondFactorErr> { 431 if creds.as_ref().is_empty() { 432 Err(SecondFactorErr) 433 } else { 434 let mut opts = Self::passkey(rp_id); 435 opts.allow_credentials = creds; 436 opts.user_verification = UserVerificationRequirement::Discouraged; 437 Ok(opts) 438 } 439 } 440 /// Begins the [authentication ceremony](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) consuming 441 /// `self`. Note that the expiration [`Instant`]/[`SystemTime`] is saved, so `AuthenticationClientState` MUST be 442 /// sent ASAP. In order to complete authentication, the returned `AuthenticationServerState` MUST be saved so 443 /// that it can later be used to verify the credential assertion with [`AuthenticationServerState::verify`]. 444 /// 445 /// # Errors 446 /// 447 /// Errors iff `self` contains incompatible configuration. 448 /// 449 /// # Examples 450 /// 451 /// ``` 452 /// # #[cfg(not(feature = "serializable_server_state"))] 453 /// # use std::time::Instant; 454 /// # #[cfg(not(feature = "serializable_server_state"))] 455 /// # use webauthn_rp::request::ServerState; 456 /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId}; 457 /// # #[cfg(not(feature = "serializable_server_state"))] 458 /// assert!( 459 /// PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)) 460 /// .start_ceremony()? 461 /// .0 462 /// .expiration() > Instant::now() 463 /// ); 464 /// # Ok::<_, webauthn_rp::AggErr>(()) 465 /// ``` 466 #[inline] 467 pub fn start_ceremony( 468 self, 469 ) -> Result< 470 ( 471 AuthenticationServerState, 472 AuthenticationClientState<'rp_id, 'prf>, 473 ), 474 RequestOptionsErr, 475 > { 476 let extensions = self.extensions.into(); 477 let allow_credentials = Vec::from(&self.allow_credentials); 478 validate_options_helper(extensions, self.user_verification, &allow_credentials).and_then( 479 |()| { 480 #[cfg(not(feature = "serializable_server_state"))] 481 let res = Instant::now(); 482 #[cfg(feature = "serializable_server_state")] 483 let res = SystemTime::now(); 484 res.checked_add(Duration::from_millis(NonZeroU64::from(self.timeout).get())) 485 .ok_or(RequestOptionsErr::InvalidTimeout) 486 .map(|expiration| { 487 ( 488 AuthenticationServerState { 489 challenge: SentChallenge(self.challenge.0), 490 allow_credentials, 491 user_verification: self.user_verification, 492 extensions, 493 expiration, 494 }, 495 AuthenticationClientState(self), 496 ) 497 }) 498 }, 499 ) 500 } 501 } 502 /// Container of a [`PublicKeyCredentialRequestOptions`] that has been used to start the authentication ceremony. 503 /// This gets sent to the client ASAP. 504 #[derive(Debug)] 505 pub struct AuthenticationClientState<'rp_id, 'prf>(PublicKeyCredentialRequestOptions<'rp_id, 'prf>); 506 impl AuthenticationClientState<'_, '_> { 507 /// Returns the `PublicKeyCredentialRequestOptions` that was used to start an authentication ceremony. 508 /// 509 /// # Examples 510 /// 511 /// ``` 512 /// # use webauthn_rp::request::{auth::PublicKeyCredentialRequestOptions, AsciiDomain, RpId}; 513 /// assert!( 514 /// PublicKeyCredentialRequestOptions::passkey(&RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?)) 515 /// .start_ceremony()? 516 /// .1 517 /// .options() 518 /// .allow_credentials 519 /// .as_ref() 520 /// .is_empty() 521 /// ); 522 /// # Ok::<_, webauthn_rp::AggErr>(()) 523 /// ``` 524 #[inline] 525 #[must_use] 526 pub const fn options(&self) -> &PublicKeyCredentialRequestOptions<'_, '_> { 527 &self.0 528 } 529 } 530 /// `PrfInput` and `PrfInputOwned` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 531 /// in an in-memory collection. 532 #[derive(Clone, Copy, Debug)] 533 enum ServerPrfInfo { 534 /// `PrfInput::second` was `None`. 535 One(ExtensionReq), 536 /// `PrfInput::second` was `Some`. 537 Two(ExtensionReq), 538 } 539 impl ServerPrfInfo { 540 /// Returns the `ExtensionReq` sent to the client. 541 const fn ext_info(self) -> ExtensionReq { 542 match self { 543 Self::One(info) | Self::Two(info) => info, 544 } 545 } 546 /// Validates `val` based on the passed arguments. 547 fn validate( 548 val: Option<Self>, 549 prf_capable: bool, 550 hmac: HmacSecret, 551 err_unsolicited: bool, 552 ) -> Result<(), ExtensionErr> { 553 match hmac { 554 HmacSecret::None => { 555 if prf_capable { 556 val.map_or(Ok(()), |input| { 557 if matches!(input.ext_info(), ExtensionReq::Allow) { 558 Ok(()) 559 } else { 560 Err(ExtensionErr::MissingHmacSecret) 561 } 562 }) 563 } else { 564 // We check if the PRF extension was requested on an incapable credential; 565 // if so, we error. 566 val.map_or(Ok(()), |_| Err(ExtensionErr::HmacSecretForPrfIncapableCred)) 567 } 568 } 569 HmacSecret::One => { 570 if prf_capable { 571 val.map_or_else( 572 || { 573 if err_unsolicited { 574 Err(ExtensionErr::ForbiddenHmacSecret) 575 } else { 576 Ok(()) 577 } 578 }, 579 |input| match input { 580 Self::One(_) => Ok(()), 581 Self::Two(_) => Err(ExtensionErr::InvalidHmacSecretValue( 582 OneOrTwo::Two, 583 OneOrTwo::One, 584 )), 585 }, 586 ) 587 } else { 588 Err(ExtensionErr::HmacSecretForPrfIncapableCred) 589 } 590 } 591 HmacSecret::Two => { 592 if prf_capable { 593 val.map_or_else( 594 || { 595 if err_unsolicited { 596 Err(ExtensionErr::ForbiddenHmacSecret) 597 } else { 598 Ok(()) 599 } 600 }, 601 |input| match input { 602 Self::One(_) => Err(ExtensionErr::InvalidHmacSecretValue( 603 OneOrTwo::One, 604 OneOrTwo::Two, 605 )), 606 Self::Two(_) => Ok(()), 607 }, 608 ) 609 } else { 610 Err(ExtensionErr::HmacSecretForPrfIncapableCred) 611 } 612 } 613 } 614 } 615 } 616 impl From<PrfInput<'_>> for ServerPrfInfo { 617 fn from(value: PrfInput<'_>) -> Self { 618 value 619 .second 620 .map_or_else(|| Self::One(value.ext_info), |_| Self::Two(value.ext_info)) 621 } 622 } 623 impl From<&PrfInputOwned> for ServerPrfInfo { 624 fn from(value: &PrfInputOwned) -> Self { 625 value 626 .second 627 .as_ref() 628 .map_or_else(|| Self::One(value.ext_info), |_| Self::Two(value.ext_info)) 629 } 630 } 631 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 632 /// in an in-memory collection. 633 #[derive(Clone, Copy, Debug)] 634 struct ServerExtensionInfo { 635 /// `Extension::prf`. 636 prf: Option<ServerPrfInfo>, 637 } 638 impl From<Extension<'_>> for ServerExtensionInfo { 639 fn from(value: Extension<'_>) -> Self { 640 Self { 641 prf: value.prf.map(ServerPrfInfo::from), 642 } 643 } 644 } 645 /// `CredentialSpecificExtension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 646 /// in an in-memory collection. 647 #[derive(Clone, Copy, Debug)] 648 struct ServerCredSpecificExtensionInfo { 649 /// `CredentialSpecificExtension::prf`. 650 prf: Option<ServerPrfInfo>, 651 } 652 impl From<&CredentialSpecificExtension> for ServerCredSpecificExtensionInfo { 653 fn from(value: &CredentialSpecificExtension) -> Self { 654 Self { 655 prf: value.prf.as_ref().map(ServerPrfInfo::from), 656 } 657 } 658 } 659 impl ServerExtensionInfo { 660 /// Validates the extensions. 661 /// 662 /// Note that this MUST only be called internally by `auth::validate_extensions`. 663 fn validate_extensions( 664 self, 665 auth_ext: AuthenticatorExtensionOutput, 666 error_unsolicited: bool, 667 prf_capable: bool, 668 ) -> Result<(), ExtensionErr> { 669 ServerPrfInfo::validate( 670 self.prf, 671 prf_capable, 672 auth_ext.hmac_secret, 673 error_unsolicited, 674 ) 675 } 676 } 677 /// Validates the extensions. 678 fn validate_extensions( 679 ext: ServerExtensionInfo, 680 cred_ext: Option<ServerCredSpecificExtensionInfo>, 681 auth_ext: AuthenticatorExtensionOutput, 682 error_unsolicited: bool, 683 prf_capable: bool, 684 ) -> Result<(), ExtensionErr> { 685 cred_ext.map_or_else( 686 || { 687 // No client-specific extensions, so we can simply focus on `ext`. 688 ext.validate_extensions(auth_ext, error_unsolicited, prf_capable) 689 }, 690 |c_ext| { 691 // Must carefully process each extension based on overlap and which gets priority over the other. 692 c_ext.prf.as_ref().map_or_else( 693 || { 694 ServerPrfInfo::validate( 695 ext.prf, 696 prf_capable, 697 auth_ext.hmac_secret, 698 error_unsolicited, 699 ) 700 }, 701 |_| { 702 ServerPrfInfo::validate( 703 c_ext.prf, 704 prf_capable, 705 auth_ext.hmac_secret, 706 error_unsolicited, 707 ) 708 }, 709 ) 710 }, 711 ) 712 } 713 /// [`AllowedCredential`] with less data to reduce memory usage when storing [`AuthenticationServerState`] 714 /// in an in-memory collection. 715 #[derive(Debug)] 716 struct CredInfo { 717 /// The Credential ID. 718 id: CredentialId<Vec<u8>>, 719 /// Any credential-specific extensions. 720 ext: ServerCredSpecificExtensionInfo, 721 } 722 /// Controls how to handle a change in [`DynamicState::authenticator_attachment`]. 723 /// 724 /// Note when `DynamicState::authenticator_attachment` is [`AuthenticatorAttachment::None`], then it will 725 /// be updated regardless. Similarly when [`Authentication::authenticator_attachment`] is 726 /// `AuthenticatorAttachment::None`, it will never update `DynamicState::authenticator_attachment`. 727 #[derive(Clone, Copy, Debug)] 728 pub enum AuthenticatorAttachmentEnforcement { 729 /// Fail the authentication ceremony if [`AuthenticatorAttachment`] is not the same. 730 /// 731 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 732 Fail(bool), 733 /// Update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 734 /// 735 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 736 Update(bool), 737 /// Do not update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 738 /// 739 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 740 Ignore(bool), 741 } 742 impl AuthenticatorAttachmentEnforcement { 743 /// Validates `cur` based on `self` and `prev`. 744 const fn validate( 745 self, 746 prev: AuthenticatorAttachment, 747 cur: AuthenticatorAttachment, 748 ) -> Result<AuthenticatorAttachment, AuthCeremonyErr> { 749 match cur { 750 AuthenticatorAttachment::None => match self { 751 Self::Fail(require) | Self::Update(require) | Self::Ignore(require) => { 752 if require { 753 Err(AuthCeremonyErr::MissingAuthenticatorAttachment) 754 } else { 755 // We don't overwrite the previous one with [`AuthenticatorAttachment::None`]. 756 Ok(prev) 757 } 758 } 759 }, 760 AuthenticatorAttachment::Platform => match self { 761 Self::Fail(_) => { 762 if matches!(prev, AuthenticatorAttachment::CrossPlatform) { 763 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 764 } else { 765 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 766 Ok(cur) 767 } 768 } 769 Self::Update(_) => Ok(cur), 770 Self::Ignore(_) => { 771 if matches!(prev, AuthenticatorAttachment::None) { 772 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 773 Ok(cur) 774 } else { 775 Ok(prev) 776 } 777 } 778 }, 779 AuthenticatorAttachment::CrossPlatform => match self { 780 Self::Fail(_) => { 781 if matches!(prev, AuthenticatorAttachment::Platform) { 782 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 783 } else { 784 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 785 Ok(cur) 786 } 787 } 788 Self::Update(_) => Ok(cur), 789 Self::Ignore(_) => { 790 if matches!(prev, AuthenticatorAttachment::None) { 791 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 792 Ok(cur) 793 } else { 794 Ok(prev) 795 } 796 } 797 }, 798 } 799 } 800 } 801 impl Default for AuthenticatorAttachmentEnforcement { 802 /// Returns [`Self::Ignore`] containing `false`. 803 #[inline] 804 fn default() -> Self { 805 Self::Ignore(false) 806 } 807 } 808 /// Additional verification options to perform in [`AuthenticationServerState::verify`]. 809 #[derive(Clone, Copy, Debug)] 810 pub struct AuthenticationVerificationOptions<'origins, 'top_origins, O, T> { 811 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 812 /// 813 /// When this is empty, the origin that will be used will be based on 814 /// the [`RpId`] passed to [`AuthenticationServerState::verify`]. If [`RpId::Domain`], then the [`DomainOrigin`] returned from 815 /// passing [`AsciiDomain::as_ref`] to [`DomainOrigin::new`] will be used; otherwise the [`Url`] in 816 /// [`RpId::Url`] will be used. 817 pub allowed_origins: &'origins [O], 818 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 819 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 820 /// 821 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 822 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 823 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 824 pub allowed_top_origins: Option<&'top_origins [T]>, 825 /// The required [`Backup`] state of the credential. 826 /// 827 /// Note that `None` is _not_ the same as `Some(BackupReq::None)` as the latter indicates that any [`Backup`] 828 /// is allowed. This is rarely what you want; instead, `None` indicates that [`BackupReq::from`] applied to 829 /// [`DynamicState::backup`] will be used in [`AuthenticationServerState::verify`]. 830 pub backup_requirement: Option<BackupReq>, 831 /// Error when unsolicited extensions are sent back iff `true`. 832 pub error_on_unsolicited_extensions: bool, 833 /// Dictates what happens when [`Authentication::authenticator_attachment`] is not the same as 834 /// [`DynamicState::authenticator_attachment`]. 835 pub auth_attachment_enforcement: AuthenticatorAttachmentEnforcement, 836 /// [`DynamicState::user_verified`] will be set to `true` iff [`Flag::user_verified`] when `true`. 837 pub update_uv: bool, 838 /// Dictates what happens when [`AuthenticatorData::sign_count`] is not updated to a strictly greater value. 839 pub sig_counter_enforcement: SignatureCounterEnforcement, 840 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 841 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 842 #[cfg(feature = "serde_relaxed")] 843 pub client_data_json_relaxed: bool, 844 } 845 impl<O, T> Default for AuthenticationVerificationOptions<'_, '_, O, T> { 846 /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`, 847 /// [`Self::backup_requirement`] is `None`, [`Self::error_on_unsolicited_extensions`] is `true`, 848 /// [`Self::auth_attachment_enforcement`] is [`AuthenticatorAttachmentEnforcement::default`], 849 /// [`Self::update_uv`] is `false`, [`Self::sig_counter_enforcement`] is 850 /// [`SignatureCounterEnforcement::Fail`], and [`Self::client_data_json_relaxed`] is `true`. 851 #[inline] 852 fn default() -> Self { 853 Self { 854 allowed_origins: &[], 855 allowed_top_origins: None, 856 backup_requirement: None, 857 error_on_unsolicited_extensions: true, 858 auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::default(), 859 update_uv: false, 860 sig_counter_enforcement: SignatureCounterEnforcement::default(), 861 #[cfg(feature = "serde_relaxed")] 862 client_data_json_relaxed: true, 863 } 864 } 865 } 866 // This is essentially the `PublicKeyCredentialRequestOptions` used to create it; however to reduce 867 // memory usage, we remove all unnecessary data making an instance of this 64 bytes in size when 868 // `Self::allow_credentials` is empty on `x86_64-unknown-linux-gnu` platforms. 869 // 870 // The total memory used is dependent on the number of `AllowedCredential`s and the size of each `CredentialId`. 871 // To be exact, it is the following: 872 // 64 + i(32 + 56n + Σj_k from k=0 to k=m-1) where i is 0 iff `AllowedCredentials` has capacity 0; otherwise 1, 873 // n is `AllowedCredentials` capacity, j_k is the kth `CredentialId` in `AllowedCredentials` and `m` is 874 // `AllowedCredentials::len`. 875 /// State needed to be saved when beginning the authentication ceremony. 876 /// 877 /// Saves the necessary information associated with the [`PublicKeyCredentialRequestOptions`] used to create it 878 /// via [`PublicKeyCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be 879 /// performed with [`Self::verify`]. 880 /// 881 /// `AuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct 882 /// `AuthenticationServerState` associated with an [`Authentication`], one should use its corresponding 883 /// [`Authentication::challenge`]. 884 #[derive(Debug)] 885 pub struct AuthenticationServerState { 886 // This is a `SentChallenge` since we need `AuthenticationServerState` to be fetchable after receiving the 887 // response from the client. This response must obviously be constructable; thus its challenge is a 888 // `SentChallenge`. 889 // 890 // This must never be mutated since we want to ensure it is actually a `Challenge` (which 891 // can only be constructed via `Challenge::new`). This is guaranteed to be true iff 892 // `serializable_server_state` is not enabled. 893 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge). 894 challenge: SentChallenge, 895 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 896 allow_credentials: Vec<CredInfo>, 897 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification). 898 user_verification: UserVerificationRequirement, 899 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions). 900 extensions: ServerExtensionInfo, 901 /// `Instant` the ceremony expires. 902 #[cfg(not(feature = "serializable_server_state"))] 903 expiration: Instant, 904 /// `SystemTime` the ceremony expires. 905 #[cfg(feature = "serializable_server_state")] 906 expiration: SystemTime, 907 } 908 impl AuthenticationServerState { 909 /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true` 910 /// iff `cred` was mutated. 911 /// 912 /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the 913 /// ceremony. 914 /// 915 /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)` 916 /// is returned. 917 /// 918 /// # Errors 919 /// 920 /// Errors iff `response` is not valid according to the 921 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any 922 /// of the settings in `options`. 923 #[inline] 924 pub fn verify< 925 'a, 926 'user, 927 O: PartialEq<Origin<'a>>, 928 T: PartialEq<Origin<'a>>, 929 EdKey: AsRef<[u8]>, 930 P256Key: AsRef<[u8]>, 931 P384Key: AsRef<[u8]>, 932 RsaKey: AsRef<[u8]>, 933 >( 934 self, 935 rp_id: &RpId, 936 response: &'a Authentication, 937 cred: &mut AuthenticatedCredential< 938 'a, 939 'user, 940 CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, 941 >, 942 options: &AuthenticationVerificationOptions<'_, '_, O, T>, 943 ) -> Result<bool, AuthCeremonyErr> { 944 // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) 945 // is handled by: 946 // 947 // 1. Calling code. 948 // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]). 949 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]). 950 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 951 // 5. Below. 952 // 6. Below. 953 // 7. Informative only in that it defines variables. 954 // 8. [`Self::partial_validate`]. 955 // 9. [`Self::partial_validate`]. 956 // 10. [`Self::partial_validate`]. 957 // 11. [`Self::partial_validate`]. 958 // 12. [`Self::partial_validate`]. 959 // 13. [`Self::partial_validate`]. 960 // 14. [`Self::partial_validate`]. 961 // 15. [`Self::partial_validate`]. 962 // 16. [`Self::partial_validate`]. 963 // 17. [`Self::partial_validate`]. 964 // 18. [`Self::partial_validate`]. 965 // 19. [`Self::partial_validate`]. 966 // 20. [`Self::partial_validate`]. 967 // 21. [`Self::partial_validate`]. 968 // 22. Below. 969 // 23. Below. 970 // 24. Below. 971 // 25. Below. 972 973 if self.allow_credentials.is_empty() { 974 // Step 6. 975 response 976 .response 977 .user_handle() 978 .as_ref() 979 .ok_or(AuthCeremonyErr::MissingUserHandle) 980 .and_then(|user| { 981 if cred.user_id() == user { 982 if cred.id == response.raw_id { 983 Ok(None) 984 } else { 985 Err(AuthCeremonyErr::CredentialIdMismatch) 986 } 987 } else { 988 Err(AuthCeremonyErr::UserHandleMismatch) 989 } 990 }) 991 } else { 992 // Steps 5–6. 993 self.verify_nondiscoverable(response, cred) 994 } 995 .and_then(|ext| { 996 // Steps 8–21. 997 self.partial_validate( 998 rp_id, 999 response, 1000 (&cred.static_state.credential_public_key).into(), 1001 &CeremonyOptions { 1002 allowed_origins: options.allowed_origins, 1003 allowed_top_origins: options.allowed_top_origins, 1004 backup_requirement: options 1005 .backup_requirement 1006 .unwrap_or_else(|| BackupReq::from(cred.dynamic_state.backup)), 1007 #[cfg(feature = "serde_relaxed")] 1008 client_data_json_relaxed: options.client_data_json_relaxed, 1009 }, 1010 ) 1011 .map_err(AuthCeremonyErr::from) 1012 .and_then(|auth_data| { 1013 options 1014 .auth_attachment_enforcement 1015 .validate( 1016 cred.dynamic_state.authenticator_attachment, 1017 response.authenticator_attachment, 1018 ) 1019 .and_then(|auth_attachment| { 1020 // Step 23. 1021 validate_extensions( 1022 self.extensions, 1023 ext, 1024 auth_data.extensions(), 1025 options.error_on_unsolicited_extensions, 1026 cred.static_state.extensions.hmac_secret.unwrap_or_default(), 1027 ) 1028 .map_err(AuthCeremonyErr::Extension) 1029 .and_then(|()| { 1030 // Step 22. 1031 options 1032 .sig_counter_enforcement 1033 .validate(cred.dynamic_state.sign_count, auth_data.sign_count()) 1034 .and_then(|sig_counter| { 1035 let flags = auth_data.flags(); 1036 let prev_dyn_state = cred.dynamic_state; 1037 // Step 24 item 2. 1038 cred.dynamic_state.backup = flags.backup; 1039 if options.update_uv && flags.user_verified { 1040 // Step 24 item 3. 1041 cred.dynamic_state.user_verified = true; 1042 } 1043 // Step 24 item 1. 1044 cred.dynamic_state.sign_count = sig_counter; 1045 cred.dynamic_state.authenticator_attachment = auth_attachment; 1046 // Step 25. 1047 crate::verify_static_and_dynamic_state( 1048 &cred.static_state, 1049 cred.dynamic_state, 1050 ) 1051 .map_err(|e| { 1052 cred.dynamic_state = prev_dyn_state; 1053 AuthCeremonyErr::Credential(e) 1054 }) 1055 .map(|()| prev_dyn_state != cred.dynamic_state) 1056 }) 1057 }) 1058 }) 1059 }) 1060 }) 1061 } 1062 /// Retrieves the corresponding [`CredInfo`] used for a non-discoverable request that corresponds to 1063 /// `response`. Since this is a non-discoverable request, one must have an external way of identifying the 1064 /// `UserHandle`. 1065 /// 1066 /// This MUST be called iff a non-discoverable request was sent to the client (e.g., 1067 /// [`PublicKeyCredentialRequestOptions::second_factor`]). 1068 /// 1069 /// # Errors 1070 /// 1071 /// Errors iff [`AuthenticatedCredential::user_handle`] does not match [`Authentication::user_handle`] or 1072 /// [`PublicKeyCredentialRequestOptions::allow_credentials`] does not have a [`CredInfo`] such that 1073 /// [`CredInfo::id`] matches [`Authentication::raw_id`]. 1074 fn verify_nondiscoverable<'a, PublicKey>( 1075 &self, 1076 response: &'a Authentication, 1077 cred: &AuthenticatedCredential<'a, '_, PublicKey>, 1078 ) -> Result<Option<ServerCredSpecificExtensionInfo>, AuthCeremonyErr> { 1079 response 1080 .response 1081 .user_handle() 1082 .as_ref() 1083 .map_or(Ok(()), |user| { 1084 if user == cred.user_id() { 1085 Ok(()) 1086 } else { 1087 Err(AuthCeremonyErr::UserHandleMismatch) 1088 } 1089 }) 1090 .and_then(|()| { 1091 self.allow_credentials 1092 .iter() 1093 .find(|c| c.id == response.raw_id) 1094 .ok_or(AuthCeremonyErr::NoMatchingAllowedCredential) 1095 .map(|c| Some(c.ext)) 1096 }) 1097 } 1098 } 1099 impl ServerState for AuthenticationServerState { 1100 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1101 #[inline] 1102 fn expiration(&self) -> Instant { 1103 self.expiration 1104 } 1105 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1106 #[inline] 1107 fn expiration(&self) -> SystemTime { 1108 self.expiration 1109 } 1110 #[inline] 1111 fn sent_challenge(&self) -> SentChallenge { 1112 self.challenge 1113 } 1114 } 1115 impl Ceremony for AuthenticationServerState { 1116 type R = Authentication; 1117 fn rand_challenge(&self) -> SentChallenge { 1118 self.challenge 1119 } 1120 #[cfg(not(feature = "serializable_server_state"))] 1121 fn expiry(&self) -> Instant { 1122 self.expiration 1123 } 1124 #[cfg(feature = "serializable_server_state")] 1125 fn expiry(&self) -> SystemTime { 1126 self.expiration 1127 } 1128 fn user_verification(&self) -> UserVerificationRequirement { 1129 self.user_verification 1130 } 1131 } 1132 impl Borrow<SentChallenge> for AuthenticationServerState { 1133 #[inline] 1134 fn borrow(&self) -> &SentChallenge { 1135 &self.challenge 1136 } 1137 } 1138 impl PartialEq for AuthenticationServerState { 1139 #[inline] 1140 fn eq(&self, other: &Self) -> bool { 1141 self.challenge == other.challenge 1142 } 1143 } 1144 impl PartialEq<&Self> for AuthenticationServerState { 1145 #[inline] 1146 fn eq(&self, other: &&Self) -> bool { 1147 *self == **other 1148 } 1149 } 1150 impl PartialEq<AuthenticationServerState> for &AuthenticationServerState { 1151 #[inline] 1152 fn eq(&self, other: &AuthenticationServerState) -> bool { 1153 **self == *other 1154 } 1155 } 1156 impl Eq for AuthenticationServerState {} 1157 impl Hash for AuthenticationServerState { 1158 #[inline] 1159 fn hash<H: Hasher>(&self, state: &mut H) { 1160 self.challenge.hash(state); 1161 } 1162 } 1163 impl PartialOrd for AuthenticationServerState { 1164 #[inline] 1165 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 1166 Some(self.cmp(other)) 1167 } 1168 } 1169 impl Ord for AuthenticationServerState { 1170 #[inline] 1171 fn cmp(&self, other: &Self) -> Ordering { 1172 self.challenge.cmp(&other.challenge) 1173 } 1174 }