webauthn_rp

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

ser.rs (19105B)


      1 #[cfg(test)]
      2 mod tests;
      3 use super::{
      4     super::{
      5         super::response::ser::{Base64DecodedVal, PublicKeyCredential},
      6         ser::{
      7             AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues,
      8             ClientExtensions,
      9         },
     10     },
     11     Authentication, AuthenticatorAssertion, UserHandle,
     12     error::UnknownCredentialOptions,
     13 };
     14 #[cfg(doc)]
     15 use super::{AuthenticatorAttachment, CredentialId};
     16 use core::{
     17     fmt::{self, Formatter},
     18     marker::PhantomData,
     19     str,
     20 };
     21 use rsa::sha2::{Sha256, digest::OutputSizeUser as _};
     22 use serde::{
     23     de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor},
     24     ser::{Serialize, SerializeStruct as _, Serializer},
     25 };
     26 /// Authenticator data.
     27 pub(super) struct AuthData(pub Vec<u8>);
     28 impl<'e> Deserialize<'e> for AuthData {
     29     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     30     where
     31         D: Deserializer<'e>,
     32     {
     33         /// `Visitor` for `AuthData`.
     34         struct AuthDataVisitor;
     35         impl Visitor<'_> for AuthDataVisitor {
     36             type Value = AuthData;
     37             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     38                 formatter.write_str("AuthenticatorData")
     39             }
     40             #[expect(
     41                 clippy::arithmetic_side_effects,
     42                 reason = "comment justifies its correctness"
     43             )]
     44             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
     45             where
     46                 E: Error,
     47             {
     48                 base64url_nopad::decode_len(v.len())
     49                     .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value"))
     50                     .and_then(|len| {
     51                         // The decoded length is 3/4 of the encoded length, so overflow could only occur
     52                         // if usize::MAX / 4 < 32 => usize::MAX < 128 < u16::MAX; thus overflow is not
     53                         // possible.
     54                         // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the
     55                         // raw authenticator data by `AuthenticatorDataAssertion::new`.
     56                         let mut auth_data = vec![0; len + Sha256::output_size()];
     57                         auth_data.truncate(len);
     58                         base64url_nopad::decode_buffer_exact(v.as_bytes(), auth_data.as_mut_slice())
     59                             .map_err(E::custom)
     60                             .map(|()| AuthData(auth_data))
     61                     })
     62             }
     63         }
     64         deserializer.deserialize_str(AuthDataVisitor)
     65     }
     66 }
     67 /// `Visitor` for `AuthenticatorAssertion`.
     68 ///
     69 /// Unknown fields are ignored and only `clientDataJSON`, `authenticatorData`, and `signature` are required iff
     70 /// `RELAXED`.
     71 pub(super) struct AuthenticatorAssertionVisitor<
     72     const RELAXED: bool,
     73     const USER_LEN: usize,
     74     const DISCOVERABLE: bool,
     75 >;
     76 impl<'d, const R: bool, const LEN: usize, const DISC: bool> Visitor<'d>
     77     for AuthenticatorAssertionVisitor<R, LEN, DISC>
     78 where
     79     UserHandle<LEN>: Deserialize<'d>,
     80 {
     81     type Value = AuthenticatorAssertion<LEN, DISC>;
     82     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     83         formatter.write_str("AuthenticatorAssertion")
     84     }
     85     #[expect(clippy::too_many_lines, reason = "107 lines is fine")]
     86     fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
     87     where
     88         A: MapAccess<'d>,
     89     {
     90         /// Fields in `AuthenticatorAssertionResponseJSON`.
     91         enum Field<const IGNORE_UNKNOWN: bool> {
     92             /// `clientDataJSON`.
     93             ClientDataJson,
     94             /// `authenticatorData`.
     95             AuthenticatorData,
     96             /// `signature`.
     97             Signature,
     98             /// `userHandle`.
     99             UserHandle,
    100             /// Unknown field.
    101             Other,
    102         }
    103         impl<'e, const I: bool> Deserialize<'e> for Field<I> {
    104             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    105             where
    106                 D: Deserializer<'e>,
    107             {
    108                 /// `Visitor` for `Field`.
    109                 struct FieldVisitor<const IGNORE_UNKNOWN: bool>;
    110                 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> {
    111                     type Value = Field<IG>;
    112                     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    113                         write!(
    114                             formatter,
    115                             "'{CLIENT_DATA_JSON}', '{AUTHENTICATOR_DATA}', '{SIGNATURE}', or '{USER_HANDLE}'"
    116                         )
    117                     }
    118                     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    119                     where
    120                         E: Error,
    121                     {
    122                         match v {
    123                             CLIENT_DATA_JSON => Ok(Field::ClientDataJson),
    124                             AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData),
    125                             SIGNATURE => Ok(Field::Signature),
    126                             USER_HANDLE => Ok(Field::UserHandle),
    127                             _ => {
    128                                 if IG {
    129                                     Ok(Field::Other)
    130                                 } else {
    131                                     Err(E::unknown_field(v, AUTH_ASSERT_FIELDS))
    132                                 }
    133                             }
    134                         }
    135                     }
    136                 }
    137                 deserializer.deserialize_identifier(FieldVisitor::<I>)
    138             }
    139         }
    140         let mut client_data = None;
    141         let mut auth = None;
    142         let mut sig = None;
    143         let mut user_handle = None;
    144         while let Some(key) = map.next_key::<Field<R>>()? {
    145             match key {
    146                 Field::ClientDataJson => {
    147                     if client_data.is_some() {
    148                         return Err(Error::duplicate_field(CLIENT_DATA_JSON));
    149                     }
    150                     client_data = map
    151                         .next_value::<Base64DecodedVal>()
    152                         .map(|c_data| c_data.0)
    153                         .map(Some)?;
    154                 }
    155                 Field::AuthenticatorData => {
    156                     if auth.is_some() {
    157                         return Err(Error::duplicate_field(AUTHENTICATOR_DATA));
    158                     }
    159                     auth = map
    160                         .next_value::<AuthData>()
    161                         .map(|auth_data| Some(auth_data.0))?;
    162                 }
    163                 Field::Signature => {
    164                     if sig.is_some() {
    165                         return Err(Error::duplicate_field(SIGNATURE));
    166                     }
    167                     sig = map
    168                         .next_value::<Base64DecodedVal>()
    169                         .map(|signature| signature.0)
    170                         .map(Some)?;
    171                 }
    172                 Field::UserHandle => {
    173                     if user_handle.is_some() {
    174                         return Err(Error::duplicate_field(USER_HANDLE));
    175                     }
    176                     user_handle = map.next_value().map(Some)?;
    177                 }
    178                 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?,
    179             }
    180         }
    181         client_data
    182             .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON))
    183             .and_then(|client_data_json| {
    184                 auth.ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA))
    185                     .and_then(|authenticator_data| {
    186                         sig.ok_or_else(|| Error::missing_field(SIGNATURE))
    187                             .and_then(|signature| {
    188                                 if DISC {
    189                                     user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE))
    190                                 } else {
    191                                     user_handle.map_or_else(|| Ok(None), Ok)
    192                                 }
    193                                 .map(|user| {
    194                                     AuthenticatorAssertion::new_inner(
    195                                         client_data_json,
    196                                         authenticator_data,
    197                                         signature,
    198                                         user,
    199                                     )
    200                                 })
    201                             })
    202                     })
    203             })
    204     }
    205 }
    206 /// `"clientDataJSON"`.
    207 const CLIENT_DATA_JSON: &str = "clientDataJSON";
    208 /// `"authenticatorData"`.
    209 const AUTHENTICATOR_DATA: &str = "authenticatorData";
    210 /// `"signature"`.
    211 const SIGNATURE: &str = "signature";
    212 /// `"userHandle"`.
    213 const USER_HANDLE: &str = "userHandle";
    214 /// Fields in `AuthenticatorAssertionResponseJSON`.
    215 pub(super) const AUTH_ASSERT_FIELDS: &[&str; 4] =
    216     &[CLIENT_DATA_JSON, AUTHENTICATOR_DATA, SIGNATURE, USER_HANDLE];
    217 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    218     for AuthenticatorAssertion<USER_LEN, DISCOVERABLE>
    219 where
    220     UserHandle<USER_LEN>: Deserialize<'de>,
    221 {
    222     /// Deserializes a `struct` based on
    223     /// [`AuthenticatorAssertionResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorassertionresponsejson).
    224     ///
    225     /// Note unknown keys and duplicate keys are forbidden;
    226     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-clientdatajson),
    227     /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-authenticatordata),
    228     /// and
    229     /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-signature) are
    230     /// base64url-decoded;
    231     /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle) is
    232     /// required and must not be `null` iff `DISCOVERABLE`. When it exists and is not `null`, it is deserialized
    233     /// via [`UserHandle::deserialize`]. All `required` fields in the `AuthenticatorAssertionResponseJSON` Web IDL
    234     /// `dictionary` exist (and are not `null`).
    235     #[inline]
    236     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    237     where
    238         D: Deserializer<'de>,
    239     {
    240         deserializer.deserialize_struct(
    241             "AuthenticatorAssertion",
    242             AUTH_ASSERT_FIELDS,
    243             AuthenticatorAssertionVisitor::<false, USER_LEN, DISCOVERABLE>,
    244         )
    245     }
    246 }
    247 /// Empty map of client extensions.
    248 pub(super) struct ClientExtensionsOutputs;
    249 /// `Visitor` for `ClientExtensionsOutputs`.
    250 ///
    251 /// Unknown fields are ignored iff `RELAXED`.
    252 pub(super) struct ClientExtensionsOutputsVisitor<const RELAXED: bool, PRF>(
    253     pub PhantomData<fn() -> PRF>,
    254 );
    255 impl<'d, const R: bool, P> Visitor<'d> for ClientExtensionsOutputsVisitor<R, P>
    256 where
    257     P: for<'a> Deserialize<'a>,
    258 {
    259     type Value = ClientExtensionsOutputs;
    260     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    261         formatter.write_str("ClientExtensionsOutputs")
    262     }
    263     fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    264     where
    265         A: MapAccess<'d>,
    266     {
    267         /// Allowed fields.
    268         enum Field<const IGNORE_UNKNOWN: bool> {
    269             /// `prf`.
    270             Prf,
    271             /// Unknown field.
    272             Other,
    273         }
    274         impl<'e, const I: bool> Deserialize<'e> for Field<I> {
    275             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    276             where
    277                 D: Deserializer<'e>,
    278             {
    279                 /// `Visitor` for `Field`.
    280                 ///
    281                 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`.
    282                 struct FieldVisitor<const IGNORE_UNKNOWN: bool>;
    283                 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> {
    284                     type Value = Field<IG>;
    285                     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    286                         write!(formatter, "'{PRF}'")
    287                     }
    288                     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    289                     where
    290                         E: Error,
    291                     {
    292                         match v {
    293                             PRF => Ok(Field::Prf),
    294                             _ => {
    295                                 if IG {
    296                                     Ok(Field::Other)
    297                                 } else {
    298                                     Err(E::unknown_field(v, EXT_FIELDS))
    299                                 }
    300                             }
    301                         }
    302                     }
    303                 }
    304                 deserializer.deserialize_identifier(FieldVisitor::<I>)
    305             }
    306         }
    307         let mut prf = None;
    308         while let Some(key) = map.next_key::<Field<R>>()? {
    309             match key {
    310                 Field::Prf => {
    311                     if prf.is_some() {
    312                         return Err(Error::duplicate_field(PRF));
    313                     }
    314                     prf = map.next_value::<Option<P>>().map(Some)?;
    315                 }
    316                 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?,
    317             }
    318         }
    319         Ok(ClientExtensionsOutputs)
    320     }
    321 }
    322 impl ClientExtensions for ClientExtensionsOutputs {
    323     fn empty() -> Self {
    324         Self
    325     }
    326 }
    327 /// `"prf"`
    328 const PRF: &str = "prf";
    329 /// `AuthenticationExtensionsClientOutputsJSON` fields.
    330 pub(super) const EXT_FIELDS: &[&str; 1] = &[PRF];
    331 impl<'de> Deserialize<'de> for ClientExtensionsOutputs {
    332     /// Deserializes a `struct` based on
    333     /// [`AuthenticationExtensionsClientOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputsjson).
    334     ///
    335     /// Note that unknown and duplicate keys are forbidden and
    336     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null`
    337     /// or deserialized via [`AuthenticationExtensionsPrfOutputs::deserialize`].
    338     #[inline]
    339     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    340     where
    341         D: Deserializer<'de>,
    342     {
    343         deserializer.deserialize_struct(
    344             "ClientExtensionsOutputs",
    345             EXT_FIELDS,
    346             ClientExtensionsOutputsVisitor::<
    347                 false,
    348                 AuthenticationExtensionsPrfOutputsHelper<
    349                     false,
    350                     false,
    351                     AuthenticationExtensionsPrfValues,
    352                 >,
    353             >(PhantomData),
    354         )
    355     }
    356 }
    357 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    358     for Authentication<USER_LEN, DISCOVERABLE>
    359 where
    360     UserHandle<USER_LEN>: Deserialize<'de>,
    361 {
    362     /// Deserializes a `struct` based on
    363     /// [`AuthenticationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson).
    364     ///
    365     /// Note that unknown and duplicate keys are forbidden;
    366     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and
    367     /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-rawid) are deserialized
    368     /// via [`CredentialId::deserialize`];
    369     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized
    370     /// via [`AuthenticatorAssertion::deserialize`];
    371     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-authenticatorattachment)
    372     /// is `null` or deserialized via [`AuthenticatorAttachment::deserialize`];
    373     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults)
    374     /// is deserialized such that it is an empty map or a map that only contains
    375     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) which additionally must be
    376     /// `null` or an
    377     /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs)
    378     /// such that unknown and duplicate keys are forbidden,
    379     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled)
    380     /// is forbidden (including being assigned `null`),
    381     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
    382     /// be `null`, or be an
    383     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
    384     /// with no unknown or duplicate keys,
    385     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) must exist but be
    386     /// `null`, and
    387     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
    388     /// must be `null` if so; all `required` fields in the `AuthenticationResponseJSON` Web IDL `dictionary` exist
    389     /// (and are not `null`); [`type`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-type) is
    390     /// `"public-key"`; and the decoded `id` and decoded `rawId` are the same.
    391     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
    392     #[inline]
    393     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    394     where
    395         D: Deserializer<'de>,
    396     {
    397         PublicKeyCredential::<
    398             false,
    399             false,
    400             AuthenticatorAssertion<USER_LEN, DISCOVERABLE>,
    401             ClientExtensionsOutputs,
    402         >::deserialize(deserializer)
    403         .map(|cred| Self {
    404             raw_id: cred.id.unwrap_or_else(|| {
    405                 unreachable!("there is a bug in PublicKeyCredential::deserialize")
    406             }),
    407             response: cred.response,
    408             authenticator_attachment: cred.authenticator_attachment,
    409         })
    410     }
    411 }
    412 impl Serialize for UnknownCredentialOptions<'_, '_> {
    413     /// Serializes `self` to conform with
    414     /// [`UnknownCredentialOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-unknowncredentialoptions).
    415     ///
    416     /// # Examples
    417     ///
    418     /// ```
    419     /// # use core::str::FromStr;
    420     /// # use webauthn_rp::{request::{AsciiDomain, RpId}, response::{auth::error::UnknownCredentialOptions, CredentialId}};
    421     /// # #[cfg(feature = "custom")]
    422     /// let credential_id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?;
    423     /// # #[cfg(feature = "custom")]
    424     /// assert_eq!(
    425     ///     serde_json::to_string(&UnknownCredentialOptions {
    426     ///         rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?),
    427     ///         credential_id: (&credential_id).into(),
    428     ///     })
    429     ///     .unwrap(),
    430     ///     r#"{"rpId":"example.com","credentialId":"AAAAAAAAAAAAAAAAAAAAAA"}"#
    431     /// );
    432     /// # Ok::<_, webauthn_rp::AggErr>(())
    433     /// ```
    434     #[inline]
    435     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    436     where
    437         S: Serializer,
    438     {
    439         serializer
    440             .serialize_struct("UnknownCredentialOptions", 2)
    441             .and_then(|mut ser| {
    442                 ser.serialize_field("rpId", self.rp_id).and_then(|()| {
    443                     ser.serialize_field("credentialId", &self.credential_id)
    444                         .and_then(|()| ser.end())
    445                 })
    446             })
    447     }
    448 }