webauthn_rp

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

ser_relaxed.rs (22739B)


      1 #[cfg(test)]
      2 mod tests;
      3 #[cfg(doc)]
      4 use super::super::{Challenge, CredentialId};
      5 use super::{
      6     super::{
      7         super::request::register::{USER_HANDLE_MAX_LEN, UserHandle},
      8         auth::ser::{
      9             AUTH_ASSERT_FIELDS, AuthData, AuthenticatorAssertionVisitor, ClientExtensionsOutputs,
     10             ClientExtensionsOutputsVisitor, EXT_FIELDS,
     11         },
     12         ser::{
     13             AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions,
     14             PublicKeyCredential, Type,
     15         },
     16         ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed,
     17     },
     18     Authentication, AuthenticatorAssertion, AuthenticatorAttachment,
     19 };
     20 use core::{
     21     fmt::{self, Formatter},
     22     marker::PhantomData,
     23 };
     24 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor};
     25 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation.
     26 struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs);
     27 impl ClientExtensions for ClientExtensionsOutputsRelaxed {
     28     fn empty() -> Self {
     29         Self(ClientExtensionsOutputs::empty())
     30     }
     31 }
     32 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed {
     33     /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored.
     34     ///
     35     /// Note that duplicate keys are still forbidden.
     36     #[inline]
     37     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     38     where
     39         D: Deserializer<'de>,
     40     {
     41         deserializer
     42             .deserialize_struct(
     43                 "ClientExtensionsOutputsRelaxed",
     44                 EXT_FIELDS,
     45                 ClientExtensionsOutputsVisitor::<
     46                     true,
     47                     AuthenticationExtensionsPrfOutputsHelper<
     48                         true,
     49                         false,
     50                         AuthenticationExtensionsPrfValuesRelaxed,
     51                     >,
     52                 >(PhantomData),
     53             )
     54             .map(Self)
     55     }
     56 }
     57 /// `newtype` around `AuthenticatorAssertion` with a "relaxed" [`Self::deserialize`] implementation.
     58 #[derive(Debug)]
     59 pub struct AuthenticatorAssertionRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>(
     60     pub AuthenticatorAssertion<USER_LEN, DISCOVERABLE>,
     61 );
     62 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
     63     for AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE>
     64 where
     65     UserHandle<USER_LEN>: Deserialize<'de>,
     66 {
     67     /// Same as [`AuthenticatorAssertion::deserialize`] except unknown keys are ignored.
     68     ///
     69     /// Note that duplicate keys are still forbidden.
     70     #[inline]
     71     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     72     where
     73         D: Deserializer<'de>,
     74     {
     75         deserializer
     76             .deserialize_struct(
     77                 "AuthenticatorAssertionRelaxed",
     78                 AUTH_ASSERT_FIELDS,
     79                 AuthenticatorAssertionVisitor::<true, USER_LEN, DISCOVERABLE>,
     80             )
     81             .map(Self)
     82     }
     83 }
     84 /// `newtype` around `Authentication` with a "relaxed" [`Self::deserialize`] implementation.
     85 #[derive(Debug)]
     86 pub struct AuthenticationRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>(
     87     pub Authentication<USER_LEN, DISCOVERABLE>,
     88 );
     89 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
     90     for AuthenticationRelaxed<USER_LEN, DISCOVERABLE>
     91 where
     92     UserHandle<USER_LEN>: Deserialize<'de>,
     93 {
     94     /// Same as [`Authentication::deserialize`] except unknown keys are ignored;
     95     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized
     96     /// via [`AuthenticatorAssertionRelaxed::deserialize`];
     97     /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults)
     98     /// is deserialized such unknown keys are ignored but duplicate keys are forbidden,
     99     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` or an
    100     /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs)
    101     /// such that unknown keys are allowed but duplicate keys are forbidden,
    102     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled)
    103     /// is forbidden (including being assigned `null`),
    104     /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist,
    105     /// be `null`, or be an
    106     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues)
    107     /// where unknown keys are ignored, duplicate keys are forbidden,
    108     /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but
    109     /// if it exists it must be `null`, and
    110     /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but
    111     /// must be `null` if so; and only
    112     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and `response` are required.
    113     /// `rawId` and `type` and allowed to not exist. For the other fields, they are allowed to not exist or be `null`.
    114     ///
    115     /// Note that duplicate keys are still forbidden, and data matching still applies when applicable.
    116     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
    117     #[inline]
    118     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    119     where
    120         D: Deserializer<'de>,
    121     {
    122         PublicKeyCredential::<
    123             true,
    124             false,
    125             AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE>,
    126             ClientExtensionsOutputsRelaxed,
    127         >::deserialize(deserializer)
    128         .map(|cred| {
    129             Self(Authentication {
    130                 raw_id: cred.id.unwrap_or_else(|| {
    131                     unreachable!("there is a bug in PublicKeyCredential::deserialize")
    132                 }),
    133                 response: cred.response.0,
    134                 authenticator_attachment: cred.authenticator_attachment,
    135             })
    136         })
    137     }
    138 }
    139 /// `AuthenticationRelaxed` with a required `UserHandle`.
    140 pub type DiscoverableAuthenticationRelaxed<const USER_LEN: usize> =
    141     AuthenticationRelaxed<USER_LEN, true>;
    142 /// `AuthenticationRelaxed` with a required `UserHandle64`.
    143 pub type DiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, true>;
    144 /// `AuthenticationRelaxed` with a required `UserHandle16`.
    145 pub type DiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, true>;
    146 /// `AuthenticationRelaxed` with an optional `UserHandle`.
    147 pub type NonDiscoverableAuthenticationRelaxed<const USER_LEN: usize> =
    148     AuthenticationRelaxed<USER_LEN, false>;
    149 /// `AuthenticationRelaxed` with an optional `UserHandle64`.
    150 pub type NonDiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, false>;
    151 /// `AuthenticationRelaxed` with an optional `UserHandle16`.
    152 pub type NonDiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, false>;
    153 /// `newtype` around `Authentication` with a custom [`Self::deserialize`] implementation.
    154 #[derive(Debug)]
    155 pub struct CustomAuthentication<const USER_LEN: usize, const DISCOVERABLE: bool>(
    156     pub Authentication<USER_LEN, DISCOVERABLE>,
    157 );
    158 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de>
    159     for CustomAuthentication<USER_LEN, DISCOVERABLE>
    160 where
    161     UserHandle<USER_LEN>: Deserialize<'de>,
    162 {
    163     /// Despite the spec having a
    164     /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson) that clients
    165     /// can follow, the downside is the superfluous data it contains.
    166     ///
    167     /// There simply is no reason to send the [`CredentialId`] twice. This redundant data puts RPs in
    168     /// a position where they either ignore the data or parse the data to ensure no contradictions exist
    169     /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)).
    170     ///
    171     /// While [`Authentication::deserialize`] _strictly_ adheres to the JSON definition, this implementation
    172     /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate
    173     /// and unknown keys are disallowed:
    174     ///
    175     /// ```json
    176     /// {
    177     ///   "authenticatorAttachment": null | "platform" | "cross-platform",
    178     ///   "authenticatorData": <base64url string>,
    179     ///   "clientDataJSON": <base64url string>,
    180     ///   "clientExtensionResults": {
    181     ///     "prf": null | PRFJSON
    182     ///   },
    183     ///   "id": <see CredentialId::deserialize>,
    184     ///   "signature": <base64url string>,
    185     ///   "type": "public-key",
    186     ///   "userHandle": null | <see UserHandle::deserialize>
    187     /// }
    188     /// // PRFJSON:
    189     /// {
    190     ///   "results": null | PRFOutputsJSON
    191     /// }
    192     /// // PRFOutputsJSON:
    193     /// {
    194     ///   "first": null,
    195     ///   "second": null
    196     /// }
    197     /// ```
    198     ///
    199     /// `"userHandle"` is required to exist and not be `null` iff `DISCOVERABLE`. When it does exist and
    200     /// is not `null`, then it is deserialized via [`UserHandle::deserialize`]. All of the remaining keys are
    201     /// required with the exceptions of `"authenticatorAttachment"` and `"type"`. `"prf"` is not required in the
    202     /// `clientExtensionResults` object, `"results"` is required in the `PRFJSON` object, and `"first"`
    203     /// (but not `"second"`) is required in `PRFOutputsJSON`.
    204     ///
    205     /// # Examples
    206     ///
    207     /// ```
    208     /// # use webauthn_rp::{request::register::{UserHandle, USER_HANDLE_MIN_LEN}, response::auth::ser_relaxed::CustomAuthentication};
    209     /// assert!(
    210     ///     // The below payload is technically valid, but `AuthenticationServerState::verify` will fail
    211     ///     // since the authenticatorData is not valid. This is true for `Authentication::deserialize`
    212     ///     // as well since authenticatorData parsing is always deferred.
    213     ///     serde_json::from_str::<CustomAuthentication<USER_HANDLE_MIN_LEN, true>>(
    214     ///         r#"{
    215     ///             "authenticatorData": "AA",
    216     ///             "authenticatorAttachment": "cross-platform",
    217     ///             "clientExtensionResults": {},
    218     ///             "clientDataJSON": "AA",
    219     ///             "id": "AAAAAAAAAAAAAAAAAAAAAA",
    220     ///             "signature": "AA",
    221     ///             "type": "public-key",
    222     ///             "userHandle": "AA"
    223     ///         }"#
    224     ///     ).is_ok());
    225     /// ```
    226     #[expect(
    227         clippy::too_many_lines,
    228         reason = "want to hide; thus don't put in outer scope"
    229     )]
    230     #[inline]
    231     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    232     where
    233         D: Deserializer<'de>,
    234     {
    235         /// `Visitor` for `CustomAuthentication`.
    236         struct CustomAuthenticationVisitor<const LEN: usize, const DISC: bool>;
    237         impl<'d, const LEN: usize, const DISC: bool> Visitor<'d> for CustomAuthenticationVisitor<LEN, DISC>
    238         where
    239             UserHandle<LEN>: Deserialize<'d>,
    240         {
    241             type Value = CustomAuthentication<LEN, DISC>;
    242             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    243                 formatter.write_str("CustomAuthentication")
    244             }
    245             #[expect(
    246                 clippy::too_many_lines,
    247                 reason = "want to hide; thus don't put in outer scope"
    248             )]
    249             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    250             where
    251                 A: MapAccess<'d>,
    252             {
    253                 /// Fields in the JSON.
    254                 enum Field {
    255                     /// `authenticatorAttachment` key.
    256                     AuthenticatorAttachment,
    257                     /// `authenticatorData` key.
    258                     AuthenticatorData,
    259                     /// `clientDataJSON` key.
    260                     ClientDataJson,
    261                     /// `clientExtensionResults` key.
    262                     ClientExtensionResults,
    263                     /// `id` key.
    264                     Id,
    265                     /// `signature` key.
    266                     Signature,
    267                     /// `type` key.
    268                     Type,
    269                     /// `userHandle` key.
    270                     UserHandle,
    271                 }
    272                 impl<'e> Deserialize<'e> for Field {
    273                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    274                     where
    275                         D: Deserializer<'e>,
    276                     {
    277                         /// `Visitor` for `Field`.
    278                         struct FieldVisitor;
    279                         impl Visitor<'_> for FieldVisitor {
    280                             type Value = Field;
    281                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    282                                 write!(
    283                                     formatter,
    284                                     "'{AUTHENTICATOR_ATTACHMENT}', '{AUTHENTICATOR_DATA}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{ID}', '{SIGNATURE}', '{TYPE}', or '{USER_HANDLE}'"
    285                                 )
    286                             }
    287                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    288                             where
    289                                 E: Error,
    290                             {
    291                                 match v {
    292                                     AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment),
    293                                     AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData),
    294                                     CLIENT_DATA_JSON => Ok(Field::ClientDataJson),
    295                                     CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults),
    296                                     ID => Ok(Field::Id),
    297                                     SIGNATURE => Ok(Field::Signature),
    298                                     TYPE => Ok(Field::Type),
    299                                     USER_HANDLE => Ok(Field::UserHandle),
    300                                     _ => Err(E::unknown_field(v, FIELDS)),
    301                                 }
    302                             }
    303                         }
    304                         deserializer.deserialize_identifier(FieldVisitor)
    305                     }
    306                 }
    307                 let mut authenticator_attachment = None;
    308                 let mut authenticator_data = None;
    309                 let mut client_data_json = None;
    310                 let mut ext = false;
    311                 let mut id = None;
    312                 let mut signature = None;
    313                 let mut typ = false;
    314                 let mut user_handle = None;
    315                 while let Some(key) = map.next_key()? {
    316                     match key {
    317                         Field::AuthenticatorAttachment => {
    318                             if authenticator_attachment.is_some() {
    319                                 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT));
    320                             }
    321                             authenticator_attachment = map.next_value::<Option<_>>().map(Some)?;
    322                         }
    323                         Field::AuthenticatorData => {
    324                             if authenticator_data.is_some() {
    325                                 return Err(Error::duplicate_field(AUTHENTICATOR_DATA));
    326                             }
    327                             authenticator_data =
    328                                 map.next_value::<AuthData>().map(|val| Some(val.0))?;
    329                         }
    330                         Field::ClientDataJson => {
    331                             if client_data_json.is_some() {
    332                                 return Err(Error::duplicate_field(CLIENT_DATA_JSON));
    333                             }
    334                             client_data_json = map
    335                                 .next_value::<Base64DecodedVal>()
    336                                 .map(|val| Some(val.0))?;
    337                         }
    338                         Field::ClientExtensionResults => {
    339                             if ext {
    340                                 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS));
    341                             }
    342                             ext = map.next_value::<ClientExtensionsOutputs>().map(|_| true)?;
    343                         }
    344                         Field::Id => {
    345                             if id.is_some() {
    346                                 return Err(Error::duplicate_field(ID));
    347                             }
    348                             id = map.next_value().map(Some)?;
    349                         }
    350                         Field::Signature => {
    351                             if signature.is_some() {
    352                                 return Err(Error::duplicate_field(SIGNATURE));
    353                             }
    354                             signature = map
    355                                 .next_value::<Base64DecodedVal>()
    356                                 .map(|val| Some(val.0))?;
    357                         }
    358                         Field::Type => {
    359                             if typ {
    360                                 return Err(Error::duplicate_field(TYPE));
    361                             }
    362                             typ = map.next_value::<Type>().map(|_| true)?;
    363                         }
    364                         Field::UserHandle => {
    365                             if user_handle.is_some() {
    366                                 return Err(Error::duplicate_field(USER_HANDLE));
    367                             }
    368                             user_handle = map.next_value().map(Some)?;
    369                         }
    370                     }
    371                 }
    372                 authenticator_data
    373                     .ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA))
    374                     .and_then(|auth_data| {
    375                         client_data_json
    376                             .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON))
    377                             .and_then(|c_data| {
    378                                 id.ok_or_else(|| Error::missing_field(ID))
    379                                     .and_then(|raw_id| {
    380                                         signature
    381                                             .ok_or_else(|| Error::missing_field(SIGNATURE))
    382                                             .and_then(|sig| {
    383                                                 if ext {
    384                                                     if DISC {
    385                                                         user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE))
    386                                                     } else {
    387                                                         user_handle.map_or_else(|| Ok(None), Ok)
    388                                                     }.map(|user| {
    389                                                         CustomAuthentication(Authentication {
    390                                                             response: AuthenticatorAssertion::new_inner(
    391                                                                 c_data,
    392                                                                 auth_data,
    393                                                                 sig,
    394                                                                 user,
    395                                                             ),
    396                                                             authenticator_attachment:
    397                                                                 authenticator_attachment.map_or(
    398                                                                     AuthenticatorAttachment::None,
    399                                                                     |auth_attach| {
    400                                                                         auth_attach.unwrap_or(
    401                                                                         AuthenticatorAttachment::None,
    402                                                                     )
    403                                                                     },
    404                                                                 ),
    405                                                             raw_id,
    406                                                         })
    407                                                     })
    408                                                 } else {
    409                                                     Err(Error::missing_field(
    410                                                         CLIENT_EXTENSION_RESULTS,
    411                                                     ))
    412                                                 }
    413                                             })
    414                                     })
    415                             })
    416                     })
    417             }
    418         }
    419         /// `authenticatorAttachment` key.
    420         const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment";
    421         /// `authenticatorData` key.
    422         const AUTHENTICATOR_DATA: &str = "authenticatorData";
    423         /// `clientDataJSON` key.
    424         const CLIENT_DATA_JSON: &str = "clientDataJSON";
    425         /// `clientExtensionResults` key.
    426         const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults";
    427         /// `id` key.
    428         const ID: &str = "id";
    429         /// `signature` key.
    430         const SIGNATURE: &str = "signature";
    431         /// `type` key.
    432         const TYPE: &str = "type";
    433         /// `userHandle` key.
    434         const USER_HANDLE: &str = "userHandle";
    435         /// Fields.
    436         const FIELDS: &[&str; 8] = &[
    437             AUTHENTICATOR_ATTACHMENT,
    438             AUTHENTICATOR_DATA,
    439             CLIENT_DATA_JSON,
    440             CLIENT_EXTENSION_RESULTS,
    441             ID,
    442             SIGNATURE,
    443             TYPE,
    444             USER_HANDLE,
    445         ];
    446         deserializer.deserialize_struct("CustomAuthentication", FIELDS, CustomAuthenticationVisitor)
    447     }
    448 }
    449 /// `CustomAuthentication` with a required `UserHandle`.
    450 pub type DiscoverableCustomAuthentication<const USER_LEN: usize> =
    451     CustomAuthentication<USER_LEN, true>;
    452 /// `CustomAuthentication` with a required `UserHandle64`.
    453 pub type DiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, true>;
    454 /// `CustomAuthentication` with a required `UserHandle16`.
    455 pub type DiscoverableCustomAuthentication16 = CustomAuthentication<16, true>;
    456 /// `CustomAuthentication` with an optional `UserHandle`.
    457 pub type NonDiscoverableCustomAuthentication<const USER_LEN: usize> =
    458     CustomAuthentication<USER_LEN, false>;
    459 /// `CustomAuthentication` with an optional `UserHandle64`.
    460 pub type NonDiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, false>;
    461 /// `CustomAuthentication` with an optional `UserHandle16`.
    462 pub type NonDiscoverableCustomAuthentication16 = CustomAuthentication<16, false>;