webauthn_rp

WebAuthn Level 3 RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

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>;