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