auth.rs (57435B)
1 #[cfg(doc)] 2 use super::{ 3 super::response::{ 4 Backup, CollectedClientData, Flag, 5 auth::AuthenticatorData, 6 register::{DynamicState, StaticState}, 7 }, 8 AsciiDomain, DomainOrigin, Url, 9 register::{self, PublicKeyCredentialCreationOptions}, 10 }; 11 use super::{ 12 super::{ 13 AuthenticatedCredential, 14 request::register::User, 15 response::{ 16 AuthenticatorAttachment, 17 auth::{ 18 Authentication, AuthenticatorExtensionOutput, HmacSecret, 19 error::{AuthCeremonyErr, ExtensionErr, OneOrTwo}, 20 }, 21 register::CompressedPubKey, 22 }, 23 }, 24 BackupReq, Ceremony, CeremonyOptions, Challenge, CredentialId, Credentials, ExtensionReq, Hint, 25 Origin, PublicKeyCredentialDescriptor, RpId, SentChallenge, ServerState, 26 THREE_HUNDRED_THOUSAND, UserVerificationRequirement, 27 auth::error::{RequestOptionsErr, SecondFactorErr}, 28 }; 29 use core::{ 30 borrow::Borrow, 31 cmp::Ordering, 32 hash::{Hash, Hasher}, 33 num::{NonZeroU32, NonZeroU64}, 34 time::Duration, 35 }; 36 #[cfg(any(doc, not(feature = "serializable_server_state")))] 37 use std::time::Instant; 38 #[cfg(any(doc, feature = "serializable_server_state"))] 39 use std::time::SystemTime; 40 /// Contains error types. 41 pub mod error; 42 /// Contains functionality to serialize data to a client. 43 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 44 #[cfg(feature = "serde")] 45 mod ser; 46 /// Contains functionality to (de)serialize [`AuthenticationServerState`] to a data store. 47 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 48 #[cfg(feature = "serializable_server_state")] 49 pub mod ser_server_state; 50 /// Controls how [signature counter](https://www.w3.org/TR/webauthn-3/#signature-counter) is enforced. 51 /// 52 /// Note that if the previous signature counter is positive and the new counter is not strictly greater, then the 53 /// authenticator is likely a clone (i.e., there are at least two copies of the private key). 54 #[derive(Clone, Copy, Debug, Default)] 55 pub enum SignatureCounterEnforcement { 56 /// Fail the authentication ceremony if the counter is less than or equal to the previous value when the 57 /// previous value is positive. 58 #[default] 59 Fail, 60 /// When the counter is less than the previous value, don't fail and update the value. 61 /// 62 /// Note in the special case that the new signature counter is 0, [`DynamicState::sign_count`] _won't_ 63 /// be updated since that would allow an attacker to permanently disable the counter. 64 Update, 65 /// When the counter is less than the previous value, don't fail but don't update the value. 66 Ignore, 67 } 68 impl SignatureCounterEnforcement { 69 /// Validates the signature counter based on `self`. 70 const fn validate(self, prev: u32, cur: u32) -> Result<u32, AuthCeremonyErr> { 71 if prev == 0 || cur > prev { 72 Ok(cur) 73 } else { 74 match self { 75 Self::Fail => Err(AuthCeremonyErr::SignatureCounter), 76 // When the new counter is `0`, we use the previous counter to avoid an attacker from 77 // being able to permanently disable it. 78 Self::Update => Ok(if cur == 0 { prev } else { cur }), 79 Self::Ignore => Ok(prev), 80 } 81 } 82 } 83 } 84 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues). 85 /// 86 /// This is only applicable if 87 /// [`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) 88 /// is `true` when registering a new credential with [`register::Extension::prf`] and 89 /// [`PublicKeyCredentialRequestOptions::user_verification`] is [`UserVerificationRequirement::Required`]. 90 /// 91 /// Unlike the spec, it is forbidden for 92 /// [the decrypted outputs](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) to be 93 /// passed back in an effort to ensure sensitive data remains client-side. This means 94 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) must not exist, 95 /// be `null`, or be an 96 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 97 /// such that [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) does not exist, 98 /// is `null`, or is an 99 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) such 100 /// that [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is `null` and 101 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) does not exist or is `null`. 102 /// 103 /// For the owned analog, see [`PrfInputOwned`]. 104 /// 105 /// When relying on discoverable requests 106 /// (i.e., [`PublicKeyCredentialRequestOptions::allow_credentials`] is empty), 107 /// one will likely use a static PRF input for _all_ credentials since rolling over PRF inputs 108 /// is not feasible. One uses this type for such a thing. In other words, `'a` will likely 109 /// be `'static` and [`Self::second`] will likely be `None`. 110 #[derive(Clone, Copy, Debug)] 111 pub struct PrfInput<'a> { 112 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 113 pub first: &'a [u8], 114 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 115 pub second: Option<&'a [u8]>, 116 /// Response requirements. 117 pub ext_info: ExtensionReq, 118 } 119 /// Owned version of [`PrfInput`]. 120 /// 121 /// When relying on non-discoverable requests 122 /// (i.e., [`PublicKeyCredentialRequestOptions::allow_credentials`] is non-empty), 123 /// it's recommended to use credential-specific PRF inputs that are continuously rolled over. 124 /// One uses this type for such a thing. 125 #[derive(Debug)] 126 pub struct PrfInputOwned { 127 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first). 128 pub first: Vec<u8>, 129 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second). 130 pub second: Option<Vec<u8>>, 131 /// Response requirements. 132 pub ext_info: ExtensionReq, 133 } 134 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client. 135 #[derive(Clone, Copy, Debug, Default)] 136 pub struct Extension<'prf> { 137 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 138 /// 139 /// If both [`CredentialSpecificExtension::prf`] and this are [`Some`], then `CredentialSpecificExtension::prf` 140 /// takes priority. 141 pub prf: Option<PrfInput<'prf>>, 142 } 143 /// The [defined extensions](https://www.w3.org/TR/webauthn-3/#sctn-defined-extensions) to send to the client that 144 /// are credential-specific which among other things implies a non-discoverable request. 145 #[derive(Debug, Default)] 146 pub struct CredentialSpecificExtension { 147 /// [`prf`](https://www.w3.org/TR/webauthn-3/#prf-extension). 148 /// 149 /// If both [`Extension::prf`] and this are [`Some`], then this take priority. 150 pub prf: Option<PrfInputOwned>, 151 } 152 /// Registered credential used in 153 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 154 #[derive(Debug)] 155 pub struct AllowedCredential { 156 /// The registered credential. 157 pub credential: PublicKeyCredentialDescriptor<Vec<u8>>, 158 /// Credential-specific extensions. 159 pub extension: CredentialSpecificExtension, 160 } 161 impl From<PublicKeyCredentialDescriptor<Vec<u8>>> for AllowedCredential { 162 #[inline] 163 fn from(credential: PublicKeyCredentialDescriptor<Vec<u8>>) -> Self { 164 Self { 165 credential, 166 extension: CredentialSpecificExtension::default(), 167 } 168 } 169 } 170 /// Queue of unique [`AllowedCredential`]s. 171 #[derive(Debug, Default)] 172 pub struct AllowedCredentials { 173 /// Allowed credentials. 174 creds: Vec<AllowedCredential>, 175 /// Number of `AllowedCredential`s that have PRF inputs. 176 /// 177 /// Useful to help serialization. 178 prf_count: usize, 179 } 180 impl Credentials for AllowedCredentials { 181 type Credential = AllowedCredential; 182 /// # Examples 183 /// 184 /// ``` 185 /// # use webauthn_rp::request::{auth::AllowedCredentials, Credentials}; 186 /// assert!(AllowedCredentials::with_capacity(1).as_ref().is_empty()); 187 /// ``` 188 #[inline] 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<'rp_id, 'prf> AuthenticationClientState<'rp_id, 'prf> { 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<'rp_id, 'prf> { 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 #[cfg(test)] 617 impl PartialEq for ServerPrfInfo { 618 fn eq(&self, other: &Self) -> bool { 619 match *self { 620 Self::One(req) => matches!(*other, Self::One(req2) if req == req2), 621 Self::Two(req) => matches!(*other, Self::Two(req2) if req == req2), 622 } 623 } 624 } 625 impl From<PrfInput<'_>> for ServerPrfInfo { 626 fn from(value: PrfInput<'_>) -> Self { 627 value 628 .second 629 .map_or_else(|| Self::One(value.ext_info), |_| Self::Two(value.ext_info)) 630 } 631 } 632 impl From<&PrfInputOwned> for ServerPrfInfo { 633 fn from(value: &PrfInputOwned) -> Self { 634 value 635 .second 636 .as_ref() 637 .map_or_else(|| Self::One(value.ext_info), |_| Self::Two(value.ext_info)) 638 } 639 } 640 /// `Extension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 641 /// in an in-memory collection. 642 #[derive(Clone, Copy, Debug)] 643 struct ServerExtensionInfo { 644 /// `Extension::prf`. 645 prf: Option<ServerPrfInfo>, 646 } 647 impl From<Extension<'_>> for ServerExtensionInfo { 648 fn from(value: Extension<'_>) -> Self { 649 Self { 650 prf: value.prf.map(ServerPrfInfo::from), 651 } 652 } 653 } 654 #[cfg(test)] 655 impl PartialEq for ServerExtensionInfo { 656 fn eq(&self, other: &Self) -> bool { 657 self.prf == other.prf 658 } 659 } 660 /// `CredentialSpecificExtension` without the actual data sent to reduce memory usage when storing [`AuthenticationServerState`] 661 /// in an in-memory collection. 662 #[derive(Clone, Copy, Debug)] 663 struct ServerCredSpecificExtensionInfo { 664 /// `CredentialSpecificExtension::prf`. 665 prf: Option<ServerPrfInfo>, 666 } 667 #[cfg(test)] 668 impl PartialEq for ServerCredSpecificExtensionInfo { 669 fn eq(&self, other: &Self) -> bool { 670 self.prf == other.prf 671 } 672 } 673 impl From<&CredentialSpecificExtension> for ServerCredSpecificExtensionInfo { 674 fn from(value: &CredentialSpecificExtension) -> Self { 675 Self { 676 prf: value.prf.as_ref().map(ServerPrfInfo::from), 677 } 678 } 679 } 680 impl ServerExtensionInfo { 681 /// Validates the extensions. 682 /// 683 /// Note that this MUST only be called internally by `auth::validate_extensions`. 684 fn validate_extensions( 685 self, 686 auth_ext: AuthenticatorExtensionOutput, 687 error_unsolicited: bool, 688 prf_capable: bool, 689 ) -> Result<(), ExtensionErr> { 690 ServerPrfInfo::validate( 691 self.prf, 692 prf_capable, 693 auth_ext.hmac_secret, 694 error_unsolicited, 695 ) 696 } 697 } 698 /// Validates the extensions. 699 fn validate_extensions( 700 ext: ServerExtensionInfo, 701 cred_ext: Option<ServerCredSpecificExtensionInfo>, 702 auth_ext: AuthenticatorExtensionOutput, 703 error_unsolicited: bool, 704 prf_capable: bool, 705 ) -> Result<(), ExtensionErr> { 706 cred_ext.map_or_else( 707 || { 708 // No client-specific extensions, so we can simply focus on `ext`. 709 ext.validate_extensions(auth_ext, error_unsolicited, prf_capable) 710 }, 711 |c_ext| { 712 // Must carefully process each extension based on overlap and which gets priority over the other. 713 c_ext.prf.as_ref().map_or_else( 714 || { 715 ServerPrfInfo::validate( 716 ext.prf, 717 prf_capable, 718 auth_ext.hmac_secret, 719 error_unsolicited, 720 ) 721 }, 722 |_| { 723 ServerPrfInfo::validate( 724 c_ext.prf, 725 prf_capable, 726 auth_ext.hmac_secret, 727 error_unsolicited, 728 ) 729 }, 730 ) 731 }, 732 ) 733 } 734 /// [`AllowedCredential`] with less data to reduce memory usage when storing [`AuthenticationServerState`] 735 /// in an in-memory collection. 736 #[derive(Debug)] 737 struct CredInfo { 738 /// The Credential ID. 739 id: CredentialId<Vec<u8>>, 740 /// Any credential-specific extensions. 741 ext: ServerCredSpecificExtensionInfo, 742 } 743 #[cfg(test)] 744 impl PartialEq for CredInfo { 745 fn eq(&self, other: &Self) -> bool { 746 self.id == other.id && self.ext == other.ext 747 } 748 } 749 /// Controls how to handle a change in [`DynamicState::authenticator_attachment`]. 750 /// 751 /// Note when `DynamicState::authenticator_attachment` is [`AuthenticatorAttachment::None`], then it will 752 /// be updated regardless. Similarly when [`Authentication::authenticator_attachment`] is 753 /// `AuthenticatorAttachment::None`, it will never update `DynamicState::authenticator_attachment`. 754 #[derive(Clone, Copy, Debug)] 755 pub enum AuthenticatorAttachmentEnforcement { 756 /// Fail the authentication ceremony if [`AuthenticatorAttachment`] is not the same. 757 /// 758 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 759 Fail(bool), 760 /// Update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 761 /// 762 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 763 Update(bool), 764 /// Do not update [`DynamicState::authenticator_attachment`] to the sent [`AuthenticatorAttachment`]. 765 /// 766 /// The contained `bool` represents if `AuthenticatorAttachment` must be sent. 767 Ignore(bool), 768 } 769 impl AuthenticatorAttachmentEnforcement { 770 /// Validates `cur` based on `self` and `prev`. 771 const fn validate( 772 self, 773 prev: AuthenticatorAttachment, 774 cur: AuthenticatorAttachment, 775 ) -> Result<AuthenticatorAttachment, AuthCeremonyErr> { 776 match cur { 777 AuthenticatorAttachment::None => match self { 778 Self::Fail(require) | Self::Update(require) | Self::Ignore(require) => { 779 if require { 780 Err(AuthCeremonyErr::MissingAuthenticatorAttachment) 781 } else { 782 // We don't overwrite the previous one with [`AuthenticatorAttachment::None`]. 783 Ok(prev) 784 } 785 } 786 }, 787 AuthenticatorAttachment::Platform => match self { 788 Self::Fail(_) => { 789 if matches!(prev, AuthenticatorAttachment::CrossPlatform) { 790 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 791 } else { 792 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 793 Ok(cur) 794 } 795 } 796 Self::Update(_) => Ok(cur), 797 Self::Ignore(_) => { 798 if matches!(prev, AuthenticatorAttachment::None) { 799 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 800 Ok(cur) 801 } else { 802 Ok(prev) 803 } 804 } 805 }, 806 AuthenticatorAttachment::CrossPlatform => match self { 807 Self::Fail(_) => { 808 if matches!(prev, AuthenticatorAttachment::Platform) { 809 Err(AuthCeremonyErr::AuthenticatorAttachmentMismatch) 810 } else { 811 // We don't fail when we previously had [`AuthenticatorAttachment::None`]. 812 Ok(cur) 813 } 814 } 815 Self::Update(_) => Ok(cur), 816 Self::Ignore(_) => { 817 if matches!(prev, AuthenticatorAttachment::None) { 818 // We overwrite the previous one when it is [`AuthenticatorAttachment::None`]. 819 Ok(cur) 820 } else { 821 Ok(prev) 822 } 823 } 824 }, 825 } 826 } 827 } 828 impl Default for AuthenticatorAttachmentEnforcement { 829 /// Returns [`Self::Ignore`] containing `false`. 830 #[inline] 831 fn default() -> Self { 832 Self::Ignore(false) 833 } 834 } 835 /// Additional verification options to perform in [`AuthenticationServerState::verify`]. 836 #[derive(Clone, Copy, Debug)] 837 pub struct AuthenticationVerificationOptions<'origins, 'top_origins, O, T> { 838 /// Origins to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 839 /// 840 /// When this is empty, the origin that will be used will be based on 841 /// the [`RpId`] passed to [`AuthenticationServerState::verify`]. If [`RpId::Domain`], then the [`DomainOrigin`] returned from 842 /// passing [`AsciiDomain::as_ref`] to [`DomainOrigin::new`] will be used; otherwise the [`Url`] in 843 /// [`RpId::Url`] will be used. 844 pub allowed_origins: &'origins [O], 845 /// [Top-level origins](https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin) 846 /// to use for [origin validation](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). 847 /// 848 /// When this is `Some`, [`CollectedClientData::cross_origin`] is allowed to be `true`. When the contained 849 /// `slice` is empty, [`CollectedClientData::top_origin`] must be `None`. When this is `None`, 850 /// `CollectedClientData::cross_origin` must be `false` and `CollectedClientData::top_origin` must be `None`. 851 pub allowed_top_origins: Option<&'top_origins [T]>, 852 /// The required [`Backup`] state of the credential. 853 /// 854 /// Note that `None` is _not_ the same as `Some(BackupReq::None)` as the latter indicates that any [`Backup`] 855 /// is allowed. This is rarely what you want; instead, `None` indicates that [`BackupReq::from`] applied to 856 /// [`DynamicState::backup`] will be used in [`AuthenticationServerState::verify`]. 857 pub backup_requirement: Option<BackupReq>, 858 /// Error when unsolicited extensions are sent back iff `true`. 859 pub error_on_unsolicited_extensions: bool, 860 /// Dictates what happens when [`Authentication::authenticator_attachment`] is not the same as 861 /// [`DynamicState::authenticator_attachment`]. 862 pub auth_attachment_enforcement: AuthenticatorAttachmentEnforcement, 863 /// [`DynamicState::user_verified`] will be set to `true` iff [`Flag::user_verified`] when `true`. 864 pub update_uv: bool, 865 /// Dictates what happens when [`AuthenticatorData::sign_count`] is not updated to a strictly greater value. 866 pub sig_counter_enforcement: SignatureCounterEnforcement, 867 /// [`CollectedClientData::from_client_data_json_relaxed`] is used to extract [`CollectedClientData`] iff `true`. 868 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 869 #[cfg(feature = "serde_relaxed")] 870 pub client_data_json_relaxed: bool, 871 } 872 impl<O, T> Default for AuthenticationVerificationOptions<'_, '_, O, T> { 873 /// Returns `Self` such that [`Self::allowed_origins`] is empty, [`Self::allowed_top_origins`] is `None`, 874 /// [`Self::backup_requirement`] is `None`, [`Self::error_on_unsolicited_extensions`] is `true`, 875 /// [`Self::auth_attachment_enforcement`] is [`AuthenticatorAttachmentEnforcement::default`], 876 /// [`Self::update_uv`] is `false`, [`Self::sig_counter_enforcement`] is 877 /// [`SignatureCounterEnforcement::default`], and [`Self::client_data_json_relaxed`] is `true`. 878 #[inline] 879 fn default() -> Self { 880 Self { 881 allowed_origins: &[], 882 allowed_top_origins: None, 883 backup_requirement: None, 884 error_on_unsolicited_extensions: true, 885 auth_attachment_enforcement: AuthenticatorAttachmentEnforcement::default(), 886 update_uv: false, 887 sig_counter_enforcement: SignatureCounterEnforcement::default(), 888 #[cfg(feature = "serde_relaxed")] 889 client_data_json_relaxed: true, 890 } 891 } 892 } 893 // This is essentially the `PublicKeyCredentialRequestOptions` used to create it; however to reduce 894 // memory usage, we remove all unnecessary data making an instance of this 64 bytes in size when 895 // `Self::allow_credentials` is empty on `x86_64-unknown-linux-gnu` platforms. 896 // 897 // The total memory used is dependent on the number of `AllowedCredential`s and the size of each `CredentialId`. 898 // To be exact, it is the following: 899 // 64 + i(32 + 56n + Σj_k from k=0 to k=m-1) where i is 0 iff `AllowedCredentials` has capacity 0; otherwise 1, 900 // n is `AllowedCredentials` capacity, j_k is the kth `CredentialId` in `AllowedCredentials` and `m` is 901 // `AllowedCredentials::len`. 902 /// State needed to be saved when beginning the authentication ceremony. 903 /// 904 /// Saves the necessary information associated with the [`PublicKeyCredentialRequestOptions`] used to create it 905 /// via [`PublicKeyCredentialRequestOptions::start_ceremony`] so that authentication of a credential can be 906 /// performed with [`Self::verify`]. 907 /// 908 /// `AuthenticationServerState` implements [`Borrow`] of [`SentChallenge`]; thus to obtain the correct 909 /// `AuthenticationServerState` associated with an [`Authentication`], one should use its corresponding 910 /// [`Authentication::challenge`]. 911 #[derive(Debug)] 912 pub struct AuthenticationServerState { 913 // This is a `SentChallenge` since we need `AuthenticationServerState` to be fetchable after receiving the 914 // response from the client. This response must obviously be constructable; thus its challenge is a 915 // `SentChallenge`. 916 // 917 // This must never be mutated since we want to ensure it is actually a `Challenge` (which 918 // can only be constructed via `Challenge::new`). This is guaranteed to be true iff 919 // `serializable_server_state` is not enabled. 920 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge). 921 challenge: SentChallenge, 922 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials). 923 allow_credentials: Vec<CredInfo>, 924 /// [`userVerification`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification). 925 user_verification: UserVerificationRequirement, 926 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions). 927 extensions: ServerExtensionInfo, 928 /// `Instant` the ceremony expires. 929 #[cfg(not(feature = "serializable_server_state"))] 930 expiration: Instant, 931 /// `SystemTime` the ceremony expires. 932 #[cfg(feature = "serializable_server_state")] 933 expiration: SystemTime, 934 } 935 impl AuthenticationServerState { 936 /// Verifies `response` is valid based on `self` consuming `self` and updating `cred`. Returns `true` 937 /// iff `cred` was mutated. 938 /// 939 /// `rp_id` MUST be the same as the [`PublicKeyCredentialRequestOptions::rp_id`] used when starting the 940 /// ceremony. 941 /// 942 /// It is _essential_ to save [`AuthenticatedCredential::dynamic_state`] overwriting the original value iff `Ok(true)` 943 /// is returned. 944 /// 945 /// # Errors 946 /// 947 /// Errors iff `response` is not valid according to the 948 /// [authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) or violates any 949 /// of the settings in `options`. 950 #[inline] 951 pub fn verify< 952 'a, 953 'user, 954 O: PartialEq<Origin<'a>>, 955 T: PartialEq<Origin<'a>>, 956 EdKey: AsRef<[u8]>, 957 P256Key: AsRef<[u8]>, 958 P384Key: AsRef<[u8]>, 959 RsaKey: AsRef<[u8]>, 960 U: User, 961 >( 962 self, 963 rp_id: &RpId, 964 response: &'a Authentication<U>, 965 cred: &mut AuthenticatedCredential< 966 'a, 967 'user, 968 CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, 969 >, 970 options: &AuthenticationVerificationOptions<'_, '_, O, T>, 971 ) -> Result<bool, AuthCeremonyErr> { 972 // [Authentication ceremony](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion) 973 // is handled by: 974 // 975 // 1. Calling code. 976 // 2. Client code and the construction of `resp` (hopefully via [`Authentication::deserialize`]). 977 // 3. Client code and the construction of `resp` (hopefully via [`AuthenticatorAssertion::deserialize`]). 978 // 4. Client code and the construction of `resp` (hopefully via [`ClientExtensionsOutputs::deserialize`]). 979 // 5. Below. 980 // 6. Below. 981 // 7. Informative only in that it defines variables. 982 // 8. [`Self::partial_validate`]. 983 // 9. [`Self::partial_validate`]. 984 // 10. [`Self::partial_validate`]. 985 // 11. [`Self::partial_validate`]. 986 // 12. [`Self::partial_validate`]. 987 // 13. [`Self::partial_validate`]. 988 // 14. [`Self::partial_validate`]. 989 // 15. [`Self::partial_validate`]. 990 // 16. [`Self::partial_validate`]. 991 // 17. [`Self::partial_validate`]. 992 // 18. [`Self::partial_validate`]. 993 // 19. [`Self::partial_validate`]. 994 // 20. [`Self::partial_validate`]. 995 // 21. [`Self::partial_validate`]. 996 // 22. Below. 997 // 23. Below. 998 // 24. Below. 999 // 25. Below. 1000 1001 if self.allow_credentials.is_empty() { 1002 // Step 6. 1003 response 1004 .response 1005 .user_handle() 1006 .is_same(cred.user_id) 1007 .ok_or(AuthCeremonyErr::MissingUserHandle) 1008 .and_then(|same| { 1009 if same { 1010 if cred.id == response.raw_id { 1011 Ok(None) 1012 } else { 1013 Err(AuthCeremonyErr::CredentialIdMismatch) 1014 } 1015 } else { 1016 Err(AuthCeremonyErr::UserHandleMismatch) 1017 } 1018 }) 1019 } else { 1020 // Steps 5–6. 1021 self.verify_nondiscoverable(response, cred) 1022 } 1023 .and_then(|ext| { 1024 // Steps 8–21. 1025 self.partial_validate( 1026 rp_id, 1027 response, 1028 (&cred.static_state.credential_public_key).into(), 1029 &CeremonyOptions { 1030 allowed_origins: options.allowed_origins, 1031 allowed_top_origins: options.allowed_top_origins, 1032 backup_requirement: options 1033 .backup_requirement 1034 .unwrap_or_else(|| BackupReq::from(cred.dynamic_state.backup)), 1035 #[cfg(feature = "serde_relaxed")] 1036 client_data_json_relaxed: options.client_data_json_relaxed, 1037 }, 1038 ) 1039 .map_err(AuthCeremonyErr::from) 1040 .and_then(|auth_data| { 1041 options 1042 .auth_attachment_enforcement 1043 .validate( 1044 cred.dynamic_state.authenticator_attachment, 1045 response.authenticator_attachment, 1046 ) 1047 .and_then(|auth_attachment| { 1048 // Step 23. 1049 validate_extensions( 1050 self.extensions, 1051 ext, 1052 auth_data.extensions(), 1053 options.error_on_unsolicited_extensions, 1054 cred.static_state.extensions.hmac_secret.unwrap_or_default(), 1055 ) 1056 .map_err(AuthCeremonyErr::Extension) 1057 .and_then(|()| { 1058 // Step 22. 1059 options 1060 .sig_counter_enforcement 1061 .validate(cred.dynamic_state.sign_count, auth_data.sign_count()) 1062 .and_then(|sig_counter| { 1063 let flags = auth_data.flags(); 1064 let prev_dyn_state = cred.dynamic_state; 1065 // Step 24 item 2. 1066 cred.dynamic_state.backup = flags.backup; 1067 if options.update_uv && flags.user_verified { 1068 // Step 24 item 3. 1069 cred.dynamic_state.user_verified = true; 1070 } 1071 // Step 24 item 1. 1072 cred.dynamic_state.sign_count = sig_counter; 1073 cred.dynamic_state.authenticator_attachment = auth_attachment; 1074 // Step 25. 1075 crate::verify_static_and_dynamic_state( 1076 &cred.static_state, 1077 cred.dynamic_state, 1078 ) 1079 .map_err(|e| { 1080 cred.dynamic_state = prev_dyn_state; 1081 AuthCeremonyErr::Credential(e) 1082 }) 1083 .map(|()| prev_dyn_state != cred.dynamic_state) 1084 }) 1085 }) 1086 }) 1087 }) 1088 }) 1089 } 1090 /// Retrieves the corresponding [`CredInfo`] used for a non-discoverable request that corresponds to 1091 /// `response`. Since this is a non-discoverable request, one must have an external way of identifying the 1092 /// `UserHandle`. 1093 /// 1094 /// This MUST be called iff a non-discoverable request was sent to the client (e.g., 1095 /// [`PublicKeyCredentialRequestOptions::second_factor`]). 1096 /// 1097 /// # Errors 1098 /// 1099 /// Errors iff [`AuthenticatedCredential::user_handle`] does not match [`Authentication::user_handle`] or 1100 /// [`PublicKeyCredentialRequestOptions::allow_credentials`] does not have a [`CredInfo`] such that 1101 /// [`CredInfo::id`] matches [`Authentication::raw_id`]. 1102 fn verify_nondiscoverable<'a, PublicKey, U: User>( 1103 &self, 1104 response: &'a Authentication<U>, 1105 cred: &AuthenticatedCredential<'a, '_, PublicKey>, 1106 ) -> Result<Option<ServerCredSpecificExtensionInfo>, AuthCeremonyErr> { 1107 response 1108 .response 1109 .user_handle() 1110 .is_same(cred.user_id()) 1111 .map_or(Ok(()), |same| { 1112 if same { 1113 Ok(()) 1114 } else { 1115 Err(AuthCeremonyErr::UserHandleMismatch) 1116 } 1117 }) 1118 .and_then(|()| { 1119 self.allow_credentials 1120 .iter() 1121 .find(|c| c.id == response.raw_id) 1122 .ok_or(AuthCeremonyErr::NoMatchingAllowedCredential) 1123 .map(|c| Some(c.ext)) 1124 }) 1125 } 1126 #[cfg(all(test, feature = "custom", feature = "serializable_server_state"))] 1127 fn is_eq(&self, other: &Self) -> bool { 1128 self.challenge == other.challenge 1129 && self.allow_credentials == other.allow_credentials 1130 && self.user_verification == other.user_verification 1131 && self.extensions == other.extensions 1132 && self.expiration == other.expiration 1133 } 1134 } 1135 impl ServerState for AuthenticationServerState { 1136 #[cfg(any(doc, not(feature = "serializable_server_state")))] 1137 #[inline] 1138 fn expiration(&self) -> Instant { 1139 self.expiration 1140 } 1141 #[cfg(all(not(doc), feature = "serializable_server_state"))] 1142 #[inline] 1143 fn expiration(&self) -> SystemTime { 1144 self.expiration 1145 } 1146 #[inline] 1147 fn sent_challenge(&self) -> SentChallenge { 1148 self.challenge 1149 } 1150 } 1151 impl<User> Ceremony<User> for AuthenticationServerState { 1152 type R = Authentication<User>; 1153 fn rand_challenge(&self) -> SentChallenge { 1154 self.challenge 1155 } 1156 #[cfg(not(feature = "serializable_server_state"))] 1157 fn expiry(&self) -> Instant { 1158 self.expiration 1159 } 1160 #[cfg(feature = "serializable_server_state")] 1161 fn expiry(&self) -> SystemTime { 1162 self.expiration 1163 } 1164 fn user_verification(&self) -> UserVerificationRequirement { 1165 self.user_verification 1166 } 1167 } 1168 impl Borrow<SentChallenge> for AuthenticationServerState { 1169 #[inline] 1170 fn borrow(&self) -> &SentChallenge { 1171 &self.challenge 1172 } 1173 } 1174 impl PartialEq for AuthenticationServerState { 1175 #[inline] 1176 fn eq(&self, other: &Self) -> bool { 1177 self.challenge == other.challenge 1178 } 1179 } 1180 impl PartialEq<&Self> for AuthenticationServerState { 1181 #[inline] 1182 fn eq(&self, other: &&Self) -> bool { 1183 *self == **other 1184 } 1185 } 1186 impl PartialEq<AuthenticationServerState> for &AuthenticationServerState { 1187 #[inline] 1188 fn eq(&self, other: &AuthenticationServerState) -> bool { 1189 **self == *other 1190 } 1191 } 1192 impl Eq for AuthenticationServerState {} 1193 impl Hash for AuthenticationServerState { 1194 #[inline] 1195 fn hash<H: Hasher>(&self, state: &mut H) { 1196 self.challenge.hash(state); 1197 } 1198 } 1199 impl PartialOrd for AuthenticationServerState { 1200 #[inline] 1201 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 1202 Some(self.cmp(other)) 1203 } 1204 } 1205 impl Ord for AuthenticationServerState { 1206 #[inline] 1207 fn cmp(&self, other: &Self) -> Ordering { 1208 self.challenge.cmp(&other.challenge) 1209 } 1210 } 1211 #[cfg(test)] 1212 mod tests { 1213 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1214 use super::{ 1215 super::{ 1216 super::{ 1217 AggErr, 1218 bin::{Decode as _, Encode as _}, 1219 }, 1220 AsciiDomain, AuthTransports, 1221 }, 1222 AllowedCredential, AllowedCredentials, AuthenticationServerState, Challenge, CredentialId, 1223 CredentialSpecificExtension, Credentials as _, Extension, ExtensionReq, PrfInputOwned, 1224 PublicKeyCredentialDescriptor, PublicKeyCredentialRequestOptions, RpId, 1225 UserVerificationRequirement, 1226 }; 1227 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1228 use rsa::sha2::{Digest as _, Sha256}; 1229 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1230 const CBOR_BYTES: u8 = 0b010_00000; 1231 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1232 const CBOR_TEXT: u8 = 0b011_00000; 1233 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1234 const CBOR_MAP: u8 = 0b101_00000; 1235 #[test] 1236 #[cfg(all(feature = "custom", feature = "serializable_server_state"))] 1237 fn ed25519_auth_ser() -> Result<(), AggErr> { 1238 let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 1239 let mut creds = AllowedCredentials::with_capacity(1); 1240 creds.push(AllowedCredential { 1241 credential: PublicKeyCredentialDescriptor { 1242 id: CredentialId::try_from(vec![0; 16])?, 1243 transports: AuthTransports::NONE, 1244 }, 1245 extension: CredentialSpecificExtension { 1246 prf: Some(PrfInputOwned { 1247 first: Vec::new(), 1248 second: Some(Vec::new()), 1249 ext_info: ExtensionReq::Require, 1250 }), 1251 }, 1252 }); 1253 let mut opts = PublicKeyCredentialRequestOptions::second_factor(&rp_id, creds)?; 1254 opts.user_verification = UserVerificationRequirement::Required; 1255 opts.challenge = Challenge(0); 1256 opts.extensions = Extension { prf: None }; 1257 let client_data_json = br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.to_vec(); 1258 // We over-allocate by 32 bytes. See [`AuthenticatorAssertion::new`] for more information. 1259 let mut authenticator_data = Vec::with_capacity(164); 1260 authenticator_data.extend_from_slice( 1261 [ 1262 // rpIdHash. 1263 // This will be overwritten later. 1264 0, 1265 0, 1266 0, 1267 0, 1268 0, 1269 0, 1270 0, 1271 0, 1272 0, 1273 0, 1274 0, 1275 0, 1276 0, 1277 0, 1278 0, 1279 0, 1280 0, 1281 0, 1282 0, 1283 0, 1284 0, 1285 0, 1286 0, 1287 0, 1288 0, 1289 0, 1290 0, 1291 0, 1292 0, 1293 0, 1294 0, 1295 0, 1296 // flags. 1297 // UP, UV, and ED (right-to-left). 1298 0b1000_0101, 1299 // signCount. 1300 // 0 as 32-bit big endian. 1301 0, 1302 0, 1303 0, 1304 0, 1305 CBOR_MAP | 1, 1306 CBOR_TEXT | 11, 1307 b'h', 1308 b'm', 1309 b'a', 1310 b'c', 1311 b'-', 1312 b's', 1313 b'e', 1314 b'c', 1315 b'r', 1316 b'e', 1317 b't', 1318 CBOR_BYTES | 24, 1319 // Length is 80. 1320 80, 1321 // Two HMAC outputs concatenated and encrypted. 1322 0, 1323 0, 1324 0, 1325 0, 1326 0, 1327 0, 1328 0, 1329 0, 1330 0, 1331 0, 1332 0, 1333 0, 1334 0, 1335 0, 1336 0, 1337 0, 1338 0, 1339 0, 1340 0, 1341 0, 1342 0, 1343 0, 1344 0, 1345 0, 1346 0, 1347 0, 1348 0, 1349 0, 1350 0, 1351 0, 1352 0, 1353 0, 1354 0, 1355 0, 1356 0, 1357 0, 1358 0, 1359 0, 1360 0, 1361 0, 1362 0, 1363 0, 1364 0, 1365 0, 1366 0, 1367 0, 1368 0, 1369 0, 1370 0, 1371 0, 1372 0, 1373 0, 1374 0, 1375 0, 1376 0, 1377 0, 1378 0, 1379 0, 1380 0, 1381 0, 1382 0, 1383 0, 1384 0, 1385 0, 1386 0, 1387 0, 1388 0, 1389 0, 1390 0, 1391 0, 1392 0, 1393 0, 1394 0, 1395 0, 1396 0, 1397 0, 1398 0, 1399 0, 1400 0, 1401 0, 1402 ] 1403 .as_slice(), 1404 ); 1405 authenticator_data[..32] 1406 .copy_from_slice(Sha256::digest(rp_id.as_ref().as_bytes()).as_slice()); 1407 authenticator_data 1408 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 1409 authenticator_data.truncate(132); 1410 let server = opts.start_ceremony()?.0; 1411 assert!(server.is_eq(&AuthenticationServerState::decode( 1412 server.encode()?.as_slice() 1413 )?)); 1414 Ok(()) 1415 } 1416 }