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