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