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