auth.rs (27120B)
1 #[cfg(feature = "serde_relaxed")] 2 use self::{ 3 super::{ 4 super::request::register::User, 5 ser_relaxed::{RelaxedClientDataJsonParser, SerdeJsonErr}, 6 }, 7 ser_relaxed::{AuthenticationRelaxed, CustomAuthentication}, 8 }; 9 #[cfg(all(doc, feature = "serde_relaxed"))] 10 use super::super::request::FixedCapHashSet; 11 #[cfg(doc)] 12 use super::super::{ 13 AuthenticatedCredential, RegisteredCredential, StaticState, 14 request::{ 15 Challenge, 16 auth::{AuthenticationServerState, PublicKeyCredentialRequestOptions}, 17 }, 18 }; 19 use super::{ 20 super::{ 21 UserHandle, 22 request::register::{UserHandle16, UserHandle64}, 23 }, 24 AuthData, AuthDataContainer, AuthExtOutput, AuthRespErr, AuthResponse, AuthenticatorAttachment, 25 CborSuccess, ClientDataJsonParser as _, CollectedClientData, CredentialId, Flag, FromCbor, 26 LimitedVerificationParser, ParsedAuthData, Response, SentChallenge, 27 auth::error::{AuthenticatorDataErr, AuthenticatorExtensionOutputErr}, 28 cbor, 29 error::CollectedClientDataErr, 30 register::CompressedPubKey, 31 }; 32 use core::convert::Infallible; 33 use ed25519_dalek::{Signature, Verifier as _}; 34 use p256::ecdsa::DerSignature as P256DerSig; 35 use p384::ecdsa::DerSignature as P384DerSig; 36 use rsa::{ 37 pkcs1v15, 38 sha2::{Sha256, digest::Digest as _}, 39 }; 40 #[cfg(feature = "serde_relaxed")] 41 use serde::Deserialize; 42 /// Contains error types. 43 pub mod error; 44 /// Contains functionality to deserialize data from a client. 45 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 46 #[cfg(feature = "serde")] 47 pub(super) mod ser; 48 /// Contains functionality to deserialize data from a client in a "relaxed" way. 49 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 50 #[cfg(feature = "serde_relaxed")] 51 pub mod ser_relaxed; 52 /// [`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). 53 #[derive(Clone, Copy, Debug)] 54 pub enum HmacSecret { 55 /// No `hmac-secret` response. 56 None, 57 /// One encrypted `hmac-secret`. 58 One, 59 /// Two encrypted `hmac-secret`s. 60 Two, 61 } 62 impl FromCbor<'_> for HmacSecret { 63 type Err = AuthenticatorExtensionOutputErr; 64 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 65 /// AES block size. 66 const AES_BLOCK_SIZE: usize = 16; 67 /// HMAC-SHA-256 output length. 68 const HMAC_SHA_256_LEN: usize = 32; 69 /// Length of two HMAC-SHA-256 outputs concatenated together. 70 const TWO_HMAC_SHA_256_LEN: usize = HMAC_SHA_256_LEN << 1; 71 // We need the smallest multiple of `AES_BLOCK_SIZE` that 72 // is strictly greater than `HMAC_SHA_256_LEN`. 73 /// AES-256 output length on a 32-byte input. 74 #[expect( 75 clippy::integer_division_remainder_used, 76 reason = "doesn't need to be constant time" 77 )] 78 const ONE_SECRET_LEN: usize = 79 HMAC_SHA_256_LEN + (AES_BLOCK_SIZE - (HMAC_SHA_256_LEN % AES_BLOCK_SIZE)); 80 // We need the smallest multiple of `AES_BLOCK_SIZE` that 81 // is strictly greater than `TWO_HMAC_SHA_256_LEN`. 82 /// AES-256 output length on a 64-byte input. 83 #[expect( 84 clippy::integer_division_remainder_used, 85 reason = "doesn't need to be constant time" 86 )] 87 const TWO_SECRET_LEN: usize = 88 TWO_HMAC_SHA_256_LEN + (AES_BLOCK_SIZE - (TWO_HMAC_SHA_256_LEN % AES_BLOCK_SIZE)); 89 cbor.split_at_checked(cbor::HMAC_SECRET.len()) 90 .ok_or(AuthenticatorExtensionOutputErr::Len) 91 .and_then(|(key, key_rem)| { 92 if key == cbor::HMAC_SECRET { 93 key_rem 94 .split_first() 95 .ok_or(AuthenticatorExtensionOutputErr::Len) 96 .and_then(|(bytes, bytes_rem)| { 97 if *bytes == cbor::BYTES_INFO_24 { 98 bytes_rem 99 .split_first() 100 .ok_or(AuthenticatorExtensionOutputErr::Len) 101 .and_then(|(&len, len_rem)| { 102 len_rem.split_at_checked(usize::from(len)).ok_or(AuthenticatorExtensionOutputErr::Len).and_then(|(_, remaining)| { 103 match usize::from(len) { 104 ONE_SECRET_LEN => { 105 Ok(CborSuccess { 106 value: Self::One, 107 remaining, 108 }) 109 } 110 TWO_SECRET_LEN => { 111 Ok(CborSuccess { 112 value: Self::Two, 113 remaining, 114 }) 115 } 116 _ => Err(AuthenticatorExtensionOutputErr::HmacSecretValue), 117 } 118 }) 119 }) 120 } else { 121 Err(AuthenticatorExtensionOutputErr::HmacSecretType) 122 } 123 }) 124 } else { 125 Err(AuthenticatorExtensionOutputErr::Unsupported) 126 } 127 }) 128 } 129 } 130 /// [Authenticator extension output](https://www.w3.org/TR/webauthn-3/#authenticator-extension-output). 131 #[derive(Clone, Copy, Debug)] 132 pub struct AuthenticatorExtensionOutput { 133 /// [`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). 134 pub hmac_secret: HmacSecret, 135 } 136 impl AuthExtOutput for AuthenticatorExtensionOutput { 137 fn missing(self) -> bool { 138 matches!(self.hmac_secret, HmacSecret::None) 139 } 140 } 141 impl FromCbor<'_> for AuthenticatorExtensionOutput { 142 type Err = AuthenticatorExtensionOutputErr; 143 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 144 // We don't allow unsupported extensions; thus the only possibilities is any ordered element of 145 // the power set of {"hmac-secret":<HmacSecret>}. 146 cbor.split_first().map_or_else( 147 || { 148 Ok(CborSuccess { 149 value: Self { 150 hmac_secret: HmacSecret::None, 151 }, 152 remaining: cbor, 153 }) 154 }, 155 |(map, map_rem)| { 156 if *map == cbor::MAP_1 { 157 HmacSecret::from_cbor(map_rem).map(|success| CborSuccess { 158 value: Self { 159 hmac_secret: success.value, 160 }, 161 remaining: success.remaining, 162 }) 163 } else { 164 Err(AuthenticatorExtensionOutputErr::CborHeader) 165 } 166 }, 167 ) 168 } 169 } 170 /// Unit type for `AuthData::CredData`. 171 pub(crate) struct NoCred; 172 impl FromCbor<'_> for NoCred { 173 type Err = Infallible; 174 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 175 Ok(CborSuccess { 176 value: Self, 177 remaining: cbor, 178 }) 179 } 180 } 181 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data). 182 #[derive(Clone, Copy, Debug)] 183 pub struct AuthenticatorData<'a> { 184 /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash). 185 rp_id_hash: &'a [u8], 186 /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags). 187 flags: Flag, 188 /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount). 189 sign_count: u32, 190 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions). 191 extensions: AuthenticatorExtensionOutput, 192 } 193 impl<'a> AuthenticatorData<'a> { 194 /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash). 195 #[inline] 196 #[must_use] 197 pub const fn rp_id_hash(&self) -> &'a [u8] { 198 self.rp_id_hash 199 } 200 /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags). 201 #[inline] 202 #[must_use] 203 pub const fn flags(&self) -> Flag { 204 self.flags 205 } 206 /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount). 207 #[inline] 208 #[must_use] 209 pub const fn sign_count(&self) -> u32 { 210 self.sign_count 211 } 212 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions). 213 #[inline] 214 #[must_use] 215 pub const fn extensions(&self) -> AuthenticatorExtensionOutput { 216 self.extensions 217 } 218 } 219 impl<'a> AuthData<'a> for AuthenticatorData<'a> { 220 type UpBitErr = (); 221 type CredData = NoCred; 222 type Ext = AuthenticatorExtensionOutput; 223 fn contains_at_bit() -> bool { 224 false 225 } 226 fn user_is_not_present() -> Result<(), Self::UpBitErr> { 227 Err(()) 228 } 229 fn new( 230 rp_id_hash: &'a [u8], 231 flags: Flag, 232 sign_count: u32, 233 _: Self::CredData, 234 extensions: Self::Ext, 235 ) -> Self { 236 Self { 237 rp_id_hash, 238 flags, 239 sign_count, 240 extensions, 241 } 242 } 243 fn rp_hash(&self) -> &'a [u8] { 244 self.rp_id_hash 245 } 246 fn flag(&self) -> Flag { 247 self.flags 248 } 249 } 250 impl<'a> AuthDataContainer<'a> for AuthenticatorData<'a> { 251 type Auth = Self; 252 type Err = AuthenticatorDataErr; 253 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 254 #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")] 255 fn from_data(data: &'a [u8]) -> Result<ParsedAuthData<'a, Self>, Self::Err> { 256 // `data.len().checked_sub(Sha256::output_size()).unwrap()` is less than `data.len()`, 257 // so indexing is fine. 258 Self::try_from(&data[..data.len().checked_sub(Sha256::output_size()).unwrap_or_else(|| unreachable!("AuthenticatorData::from_data must be passed a slice with 32 bytes of trailing data"))]).map(|auth_data| ParsedAuthData { data: auth_data, auth_data_and_32_trailing_bytes: data, }) 259 } 260 fn authenticator_data(&self) -> &Self::Auth { 261 self 262 } 263 } 264 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AuthenticatorData<'b> { 265 type Error = AuthenticatorDataErr; 266 /// Deserializes `value` based on the 267 /// [authenticator data structure](https://www.w3.org/TR/webauthn-3/#table-authData). 268 #[expect( 269 clippy::panic_in_result_fn, 270 reason = "we want to crash when there is a bug" 271 )] 272 #[inline] 273 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 274 Self::from_cbor(value) 275 .map_err(AuthenticatorDataErr::from) 276 .map(|auth_data| { 277 assert!( 278 auth_data.remaining.is_empty(), 279 "there is a bug in AuthenticatorData::from_cbor" 280 ); 281 auth_data.value 282 }) 283 } 284 } 285 /// [`AuthenticatorAssertionResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse). 286 #[derive(Debug)] 287 pub struct AuthenticatorAssertion<U> { 288 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson). 289 client_data_json: Vec<u8>, 290 /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-authenticatordata) 291 /// followed by the SHA-256 hash of [`Self::client_data_json`]. 292 authenticator_data_and_c_data_hash: Vec<u8>, 293 /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-signature). 294 signature: Vec<u8>, 295 /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-userhandle). 296 user_handle: U, 297 } 298 impl<U> AuthenticatorAssertion<U> { 299 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson). 300 #[inline] 301 #[must_use] 302 pub fn client_data_json(&self) -> &[u8] { 303 self.client_data_json.as_slice() 304 } 305 /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-authenticatordata). 306 #[expect( 307 clippy::arithmetic_side_effects, 308 clippy::indexing_slicing, 309 reason = "comment justifies their correctness" 310 )] 311 #[inline] 312 #[must_use] 313 pub fn authenticator_data(&self) -> &[u8] { 314 // We only allow creation via [`Self::new`] which creates [`Self::authenticator_data_and_c_data_hash`] 315 // by appending the SHA-256 hash of [`Self::client_data_json`] to the authenticator data that was passed; 316 // thus indexing is fine and subtraction won't cause underflow. 317 &self.authenticator_data_and_c_data_hash 318 [..self.authenticator_data_and_c_data_hash.len() - Sha256::output_size()] 319 } 320 /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-signature). 321 #[inline] 322 #[must_use] 323 pub fn signature(&self) -> &[u8] { 324 self.signature.as_slice() 325 } 326 /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-userhandle). 327 #[inline] 328 #[must_use] 329 pub const fn user_handle(&self) -> &U { 330 &self.user_handle 331 } 332 /// Constructs an instance of `Self` with the contained data. 333 /// 334 /// Note calling code is encouraged to ensure `authenticator_data` has at least 32 bytes 335 /// of available capacity; if not, a reallocation will occur. 336 fn new_inner( 337 client_data_json: Vec<u8>, 338 mut authenticator_data: Vec<u8>, 339 signature: Vec<u8>, 340 user_handle: U, 341 ) -> Self { 342 authenticator_data 343 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 344 Self { 345 client_data_json, 346 authenticator_data_and_c_data_hash: authenticator_data, 347 signature, 348 user_handle, 349 } 350 } 351 } 352 impl<T> AuthenticatorAssertion<Option<UserHandle<T>>> { 353 /// Constructs an instance of `Self` with the contained data. 354 /// 355 /// Note calling code is encouraged to ensure `authenticator_data` has at least 32 bytes 356 /// of available capacity; if not, a reallocation will occur. 357 #[inline] 358 #[must_use] 359 pub fn with_optional_user( 360 client_data_json: Vec<u8>, 361 authenticator_data: Vec<u8>, 362 signature: Vec<u8>, 363 user_handle: Option<UserHandle<T>>, 364 ) -> Self { 365 Self::new_inner(client_data_json, authenticator_data, signature, user_handle) 366 } 367 /// Same as [`Self::with_optional_user`] with `None` used for `user_handle`. 368 #[inline] 369 #[must_use] 370 pub fn without_user( 371 client_data_json: Vec<u8>, 372 authenticator_data: Vec<u8>, 373 signature: Vec<u8>, 374 ) -> Self { 375 Self::with_optional_user(client_data_json, authenticator_data, signature, None) 376 } 377 /// Same as [`Self::with_optional_user`] with `Some(user_handle)` used for `user_handle`. 378 #[inline] 379 #[must_use] 380 pub fn with_user( 381 client_data_json: Vec<u8>, 382 authenticator_data: Vec<u8>, 383 signature: Vec<u8>, 384 user_handle: UserHandle<T>, 385 ) -> Self { 386 Self::with_optional_user( 387 client_data_json, 388 authenticator_data, 389 signature, 390 Some(user_handle), 391 ) 392 } 393 } 394 impl<T> AuthenticatorAssertion<UserHandle<T>> { 395 /// Constructs an instance of `Self` with the contained data. 396 /// 397 /// Note calling code is encouraged to ensure `authenticator_data` has at least 32 bytes 398 /// of available capacity; if not, a reallocation will occur. 399 #[inline] 400 #[must_use] 401 pub fn new( 402 client_data_json: Vec<u8>, 403 authenticator_data: Vec<u8>, 404 signature: Vec<u8>, 405 user_handle: UserHandle<T>, 406 ) -> Self { 407 Self::new_inner(client_data_json, authenticator_data, signature, user_handle) 408 } 409 } 410 impl<U> AuthResponse for AuthenticatorAssertion<U> { 411 type Auth<'a> 412 = AuthenticatorData<'a> 413 where 414 Self: 'a; 415 type CredKey<'a> = CompressedPubKey<&'a [u8], &'a [u8], &'a [u8], &'a [u8]>; 416 fn parse_data_and_verify_sig( 417 &self, 418 key: Self::CredKey<'_>, 419 relaxed: bool, 420 ) -> Result< 421 (CollectedClientData<'_>, Self::Auth<'_>), 422 AuthRespErr<<Self::Auth<'_> as AuthDataContainer<'_>>::Err>, 423 > { 424 /// Always `panic`s. 425 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 426 #[expect( 427 clippy::extra_unused_type_parameters, 428 reason = "same function signature as when serde_relaxed is enabled" 429 )] 430 #[cfg(not(feature = "serde_relaxed"))] 431 fn get_client_collected_data<'b, U: 'b>(_: &'b [u8]) -> ! { 432 unreachable!( 433 "AuthenticatorAssertion::parse_data_and_verify_sig must be passed false when serde_relaxed is not enabled" 434 ); 435 } 436 /// Parses `data` using `CollectedClientData::from_client_data_json_relaxed::<false>`. 437 #[cfg(feature = "serde_relaxed")] 438 fn get_client_collected_data<'b, U: 'b>( 439 data: &'b [u8], 440 ) -> Result< 441 CollectedClientData<'b>, 442 AuthRespErr< 443 <<AuthenticatorAssertion<U> as AuthResponse>::Auth<'b> as AuthDataContainer<'b>>::Err, 444 >, 445 >{ 446 CollectedClientData::from_client_data_json_relaxed::<false>(data) 447 .map_err(AuthRespErr::CollectedClientDataRelaxed) 448 } 449 if relaxed { 450 get_client_collected_data::<'_, U>(self.client_data_json.as_slice()) 451 } else { 452 CollectedClientData::from_client_data_json::<false>(self.client_data_json.as_slice()) 453 .map_err(AuthRespErr::CollectedClientData) 454 } 455 .and_then(|client_data_json| { 456 Self::Auth::from_data(self.authenticator_data_and_c_data_hash.as_slice()) 457 .map_err(AuthRespErr::Auth) 458 .and_then(|val| { 459 match key { 460 CompressedPubKey::Ed25519(k) => k 461 .into_ver_key() 462 .map_err(AuthRespErr::PubKey) 463 .and_then(|ver_key| { 464 Signature::from_slice(self.signature.as_slice()) 465 .and_then(|sig| { 466 ver_key.verify( 467 self.authenticator_data_and_c_data_hash.as_slice(), 468 &sig, 469 ) 470 }) 471 .map_err(|_e| AuthRespErr::Signature) 472 }), 473 CompressedPubKey::P256(k) => k 474 .into_ver_key() 475 .map_err(AuthRespErr::PubKey) 476 .and_then(|ver_key| { 477 P256DerSig::from_bytes(self.signature.as_slice()) 478 .and_then(|sig| { 479 ver_key.verify( 480 self.authenticator_data_and_c_data_hash.as_slice(), 481 &sig, 482 ) 483 }) 484 .map_err(|_e| AuthRespErr::Signature) 485 }), 486 CompressedPubKey::P384(k) => k 487 .into_ver_key() 488 .map_err(AuthRespErr::PubKey) 489 .and_then(|ver_key| { 490 P384DerSig::from_bytes(self.signature.as_slice()) 491 .and_then(|sig| { 492 ver_key.verify( 493 self.authenticator_data_and_c_data_hash.as_slice(), 494 &sig, 495 ) 496 }) 497 .map_err(|_e| AuthRespErr::Signature) 498 }), 499 CompressedPubKey::Rsa(k) => k 500 .as_ver_key() 501 .map_err(AuthRespErr::PubKey) 502 .and_then(|ver_key| { 503 pkcs1v15::Signature::try_from(self.signature.as_slice()) 504 .and_then(|sig| { 505 ver_key.verify( 506 self.authenticator_data_and_c_data_hash.as_slice(), 507 &sig, 508 ) 509 }) 510 .map_err(|_e| AuthRespErr::Signature) 511 }), 512 } 513 .map(|()| (client_data_json, val.data)) 514 }) 515 }) 516 } 517 } 518 /// `AuthenticatorAssertion` with a required `UserHandle`. 519 pub type PasskeyAuthenticatorAssertion<T> = AuthenticatorAssertion<UserHandle<T>>; 520 /// [`PublicKeyCredential`](https://www.w3.org/TR/webauthn-3/#iface-pkcredential) for authentication ceremonies. 521 #[expect( 522 clippy::field_scoped_visibility_modifiers, 523 reason = "no invariants to uphold" 524 )] 525 #[derive(Debug)] 526 pub struct Authentication<User> { 527 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-rawid). 528 pub(crate) raw_id: CredentialId<Vec<u8>>, 529 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response) 530 pub(crate) response: AuthenticatorAssertion<User>, 531 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 532 pub(crate) authenticator_attachment: AuthenticatorAttachment, 533 } 534 impl<U> Authentication<U> { 535 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-rawid). 536 #[inline] 537 #[must_use] 538 pub fn raw_id(&self) -> CredentialId<&[u8]> { 539 (&self.raw_id).into() 540 } 541 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response). 542 #[inline] 543 #[must_use] 544 pub const fn response(&self) -> &AuthenticatorAssertion<U> { 545 &self.response 546 } 547 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 548 #[inline] 549 #[must_use] 550 pub const fn authenticator_attachment(&self) -> AuthenticatorAttachment { 551 self.authenticator_attachment 552 } 553 /// Constructs an `Authentication`. 554 #[cfg_attr(docsrs, doc(cfg(feature = "custom")))] 555 #[cfg(feature = "custom")] 556 #[inline] 557 #[must_use] 558 pub const fn new( 559 raw_id: CredentialId<Vec<u8>>, 560 response: AuthenticatorAssertion<U>, 561 authenticator_attachment: AuthenticatorAttachment, 562 ) -> Self { 563 Self { 564 raw_id, 565 response, 566 authenticator_attachment, 567 } 568 } 569 /// Returns the associated `SentChallenge`. 570 /// 571 /// This is useful when wanting to extract the corresponding [`AuthenticationServerState`] from 572 /// an in-memory collection (e.g., [`FixedCapHashSet`]) or storage. 573 /// 574 /// Note if [`CollectedClientData::from_client_data_json`] returns `Ok`, then this will return `Ok` 575 /// containing the same value as [`CollectedClientData::challenge`]; however the converse is _not_ true. 576 /// This is because this function parses the minimal amount of data possible. 577 /// 578 /// # Errors 579 /// 580 /// Errors iff [`AuthenticatorAssertion::client_data_json`] does not contain a base64url-encoded 581 /// [`Challenge`] in the required position. 582 #[inline] 583 pub fn challenge(&self) -> Result<SentChallenge, CollectedClientDataErr> { 584 LimitedVerificationParser::<false>::get_sent_challenge( 585 self.response.client_data_json.as_slice(), 586 ) 587 } 588 /// Returns the associated `SentChallenge`. 589 /// 590 /// This is useful when wanting to extract the corresponding [`AuthenticationServerState`] from 591 /// an in-memory collection (e.g., [`FixedCapHashSet`]) or storage. 592 /// 593 /// Note if [`CollectedClientData::from_client_data_json_relaxed`] returns `Ok`, then this will return 594 /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse 595 /// is _not_ true. This is because this function attempts to reduce the amount of data parsed. 596 /// 597 /// # Errors 598 /// 599 /// Errors iff [`AuthenticatorAssertion::client_data_json`] is invalid JSON _after_ ignoring 600 /// a leading U+FEFF and replacing any sequences of invalid UTF-8 code units with U+FFFD or 601 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge) does not exist 602 /// or is not a base64url-encoded [`Challenge`]. 603 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 604 #[cfg(feature = "serde_relaxed")] 605 #[inline] 606 pub fn challenge_relaxed(&self) -> Result<SentChallenge, SerdeJsonErr> { 607 RelaxedClientDataJsonParser::<false>::get_sent_challenge( 608 self.response.client_data_json.as_slice(), 609 ) 610 } 611 /// Convenience function for [`AuthenticationRelaxed::deserialize`]. 612 /// 613 /// # Errors 614 /// 615 /// Errors iff [`AuthenticationRelaxed::deserialize`] does. 616 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 617 #[cfg(feature = "serde_relaxed")] 618 #[inline] 619 pub fn from_json_relaxed<'a>(json: &'a [u8]) -> Result<Self, SerdeJsonErr> 620 where 621 U: Deserialize<'a> + User + Default, 622 { 623 serde_json::from_slice::<AuthenticationRelaxed<U>>(json).map(|val| val.0) 624 } 625 /// Convenience function for [`CustomAuthentication::deserialize`]. 626 /// 627 /// # Errors 628 /// 629 /// Errors iff [`CustomAuthentication::deserialize`] does. 630 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 631 #[cfg(feature = "serde_relaxed")] 632 #[inline] 633 pub fn from_json_custom<'a>(json: &'a [u8]) -> Result<Self, SerdeJsonErr> 634 where 635 U: Deserialize<'a> + User + Default, 636 { 637 serde_json::from_slice::<CustomAuthentication<U>>(json).map(|val| val.0) 638 } 639 } 640 impl<U> Response for Authentication<U> { 641 type Auth = AuthenticatorAssertion<U>; 642 fn auth(&self) -> &Self::Auth { 643 &self.response 644 } 645 } 646 /// `Authentication` with a required `UserHandle`. 647 pub type PasskeyAuthentication<T> = Authentication<UserHandle<T>>; 648 /// `Authentication` with a required `UserHandle64`. 649 pub type PasskeyAuthentication64 = Authentication<UserHandle64>; 650 /// `Authentication` with a required `UserHandle16`. 651 pub type PasskeyAuthentication16 = Authentication<UserHandle16>;