webauthn_rp

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

ser.rs (15738B)


      1 use super::{
      2     Challenge, CredentialId, CredentialMediationRequirement, Hint, PrfInput,
      3     PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement,
      4 };
      5 use core::str;
      6 use data_encoding::BASE64URL_NOPAD;
      7 use serde::ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer};
      8 impl Serialize for CredentialMediationRequirement {
      9     /// Serializes `self` to conform with
     10     /// [`CredentialMediationRequirement`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement).
     11     ///
     12     /// # Examples
     13     ///
     14     /// ```
     15     /// # use webauthn_rp::request::CredentialMediationRequirement;
     16     /// assert_eq!(
     17     ///     serde_json::to_string(&CredentialMediationRequirement::Silent)?,
     18     ///     r#""silent""#
     19     /// );
     20     /// assert_eq!(
     21     ///     serde_json::to_string(&CredentialMediationRequirement::Optional)?,
     22     ///     r#""optional""#
     23     /// );
     24     /// assert_eq!(
     25     ///     serde_json::to_string(&CredentialMediationRequirement::Conditional)?,
     26     ///     r#""conditional""#
     27     /// );
     28     /// assert_eq!(
     29     ///     serde_json::to_string(&CredentialMediationRequirement::Required)?,
     30     ///     r#""required""#
     31     /// );
     32     /// # Ok::<_, serde_json::Error>(())
     33     /// ```
     34     #[inline]
     35     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     36     where
     37         S: Serializer,
     38     {
     39         serializer.serialize_str(match *self {
     40             Self::Silent => "silent",
     41             Self::Optional => "optional",
     42             Self::Conditional => "conditional",
     43             Self::Required => "required",
     44         })
     45     }
     46 }
     47 impl Serialize for Challenge {
     48     /// Serializes `self` to conform with
     49     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-challenge).
     50     ///
     51     /// Specifically `self` is interpreted as a little-endian array of 16 bytes that is then transformed into a
     52     /// base64url-encoded string.
     53     ///
     54     /// # Examples
     55     ///
     56     /// ```
     57     /// # use webauthn_rp::request::Challenge;
     58     /// # // `Challenge::BASE64_LEN` is 22, but we add two for the quotes.
     59     /// assert_eq!(serde_json::to_string(&Challenge::new())?.len(), 24);
     60     /// # Ok::<_, serde_json::Error>(())
     61     /// ```
     62     #[expect(
     63         clippy::little_endian_bytes,
     64         reason = "SentChallenge::deserialize and Challenge::serialize need to be consistent across architectures"
     65     )]
     66     #[inline]
     67     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     68     where
     69         S: Serializer,
     70     {
     71         serializer.serialize_str(BASE64URL_NOPAD.encode_mut_str(
     72             self.0.to_le_bytes().as_slice(),
     73             [0; Self::BASE64_LEN].as_mut_slice(),
     74         ))
     75     }
     76 }
     77 impl Serialize for RpId {
     78     /// Serializes `self` as a [`prim@str`].
     79     ///
     80     /// # Examples
     81     ///
     82     /// ```
     83     /// # use webauthn_rp::request::{AsciiDomain, RpId};
     84     /// assert_eq!(
     85     ///     serde_json::to_string(&RpId::Domain(AsciiDomain::try_from("www.example.com".to_owned()).unwrap())).unwrap(),
     86     ///     r#""www.example.com""#
     87     /// );
     88     /// assert_eq!(
     89     ///     serde_json::to_string(&RpId::Url("ssh:foo".parse().unwrap())).unwrap(),
     90     ///     r#""ssh:foo""#
     91     /// );
     92     /// # Ok::<_, serde_json::Error>(())
     93     /// ```
     94     #[inline]
     95     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     96     where
     97         S: Serializer,
     98     {
     99         serializer.serialize_str(self.as_ref())
    100     }
    101 }
    102 impl<T> Serialize for PublicKeyCredentialDescriptor<T>
    103 where
    104     CredentialId<T>: Serialize,
    105 {
    106     /// Serializes `self` to conform with
    107     /// [`PublicKeyCredentialDescriptorJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptorjson).
    108     ///
    109     /// # Examples
    110     ///
    111     /// ```
    112     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    113     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    114     /// # use webauthn_rp::{
    115     /// #     request::PublicKeyCredentialDescriptor,
    116     /// #     response::{AuthTransports, CredentialId},
    117     /// # };
    118     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    119     /// /// from the database.
    120     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    121     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    122     ///     // ⋮
    123     /// #     AuthTransports::decode(32)
    124     /// }
    125     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    126     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    127     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    128     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    129     /// let id = CredentialId::try_from(vec![0; 16])?;
    130     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    131     /// let transports = get_transports((&id).into())?;
    132     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    133     /// assert_eq!(
    134     ///     serde_json::to_string(&PublicKeyCredentialDescriptor { id, transports }).unwrap(),
    135     ///     r#"{"type":"public-key","id":"AAAAAAAAAAAAAAAAAAAAAA","transports":["usb"]}"#
    136     /// );
    137     /// # Ok::<_, webauthn_rp::AggErr>(())
    138     /// ```
    139     #[inline]
    140     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    141     where
    142         S: Serializer,
    143     {
    144         serializer
    145             .serialize_struct("PublicKeyCredentialDescriptor", 3)
    146             .and_then(|mut ser| {
    147                 ser.serialize_field("type", "public-key").and_then(|()| {
    148                     ser.serialize_field("id", &self.id).and_then(|()| {
    149                         ser.serialize_field("transports", &self.transports)
    150                             .and_then(|()| ser.end())
    151                     })
    152                 })
    153             })
    154     }
    155 }
    156 impl Serialize for UserVerificationRequirement {
    157     /// Serializes `self` to conform with
    158     /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement).
    159     ///
    160     /// # Examples
    161     ///
    162     /// ```
    163     /// # use webauthn_rp::request::UserVerificationRequirement;
    164     /// assert_eq!(
    165     ///     serde_json::to_string(&UserVerificationRequirement::Required)?,
    166     ///     r#""required""#
    167     /// );
    168     /// assert_eq!(
    169     ///     serde_json::to_string(&UserVerificationRequirement::Discouraged)?,
    170     ///     r#""discouraged""#
    171     /// );
    172     /// assert_eq!(
    173     ///     serde_json::to_string(&UserVerificationRequirement::Preferred)?,
    174     ///     r#""preferred""#
    175     /// );
    176     /// # Ok::<_, serde_json::Error>(())
    177     /// ```
    178     #[inline]
    179     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    180     where
    181         S: Serializer,
    182     {
    183         serializer.serialize_str(match *self {
    184             Self::Required => "required",
    185             Self::Discouraged => "discouraged",
    186             Self::Preferred => "preferred",
    187         })
    188     }
    189 }
    190 impl Serialize for Hint {
    191     /// Serializes `self` to conform with
    192     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints).
    193     ///
    194     /// # Examples
    195     ///
    196     /// ```
    197     /// # use webauthn_rp::request::Hint;
    198     /// assert_eq!(
    199     ///     serde_json::to_string(&Hint::None)?,
    200     ///     r#"[]"#
    201     /// );
    202     /// assert_eq!(
    203     ///     serde_json::to_string(&Hint::SecurityKey)?,
    204     ///     r#"["security-key"]"#
    205     /// );
    206     /// assert_eq!(
    207     ///     serde_json::to_string(&Hint::ClientDevice)?,
    208     ///     r#"["client-device"]"#
    209     /// );
    210     /// assert_eq!(
    211     ///     serde_json::to_string(&Hint::Hybrid)?,
    212     ///     r#"["hybrid"]"#
    213     /// );
    214     /// assert_eq!(
    215     ///     serde_json::to_string(&Hint::SecurityKeyClientDevice)?,
    216     ///     r#"["security-key","client-device"]"#
    217     /// );
    218     /// assert_eq!(
    219     ///     serde_json::to_string(&Hint::ClientDeviceSecurityKey)?,
    220     ///     r#"["client-device","security-key"]"#
    221     /// );
    222     /// assert_eq!(
    223     ///     serde_json::to_string(&Hint::SecurityKeyHybrid)?,
    224     ///     r#"["security-key","hybrid"]"#
    225     /// );
    226     /// assert_eq!(
    227     ///     serde_json::to_string(&Hint::HybridSecurityKey)?,
    228     ///     r#"["hybrid","security-key"]"#
    229     /// );
    230     /// assert_eq!(
    231     ///     serde_json::to_string(&Hint::ClientDeviceHybrid)?,
    232     ///     r#"["client-device","hybrid"]"#
    233     /// );
    234     /// assert_eq!(
    235     ///     serde_json::to_string(&Hint::HybridClientDevice)?,
    236     ///     r#"["hybrid","client-device"]"#
    237     /// );
    238     /// assert_eq!(
    239     ///     serde_json::to_string(&Hint::SecurityKeyClientDeviceHybrid)?,
    240     ///     r#"["security-key","client-device","hybrid"]"#
    241     /// );
    242     /// assert_eq!(
    243     ///     serde_json::to_string(&Hint::SecurityKeyHybridClientDevice)?,
    244     ///     r#"["security-key","hybrid","client-device"]"#
    245     /// );
    246     /// assert_eq!(
    247     ///     serde_json::to_string(&Hint::ClientDeviceSecurityKeyHybrid)?,
    248     ///     r#"["client-device","security-key","hybrid"]"#
    249     /// );
    250     /// assert_eq!(
    251     ///     serde_json::to_string(&Hint::ClientDeviceHybridSecurityKey)?,
    252     ///     r#"["client-device","hybrid","security-key"]"#
    253     /// );
    254     /// assert_eq!(
    255     ///     serde_json::to_string(&Hint::HybridSecurityKeyClientDevice)?,
    256     ///     r#"["hybrid","security-key","client-device"]"#
    257     /// );
    258     /// assert_eq!(
    259     ///     serde_json::to_string(&Hint::HybridClientDeviceSecurityKey)?,
    260     ///     r#"["hybrid","client-device","security-key"]"#
    261     /// );
    262     /// # Ok::<_, serde_json::Error>(())
    263     /// ```
    264     #[inline]
    265     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    266     where
    267         S: Serializer,
    268     {
    269         /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-security-key).
    270         const SECURITY_KEY: &str = "security-key";
    271         /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-client-device).
    272         const CLIENT_DEVICE: &str = "client-device";
    273         /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-hybrid).
    274         const HYBRID: &str = "hybrid";
    275         let count = match *self {
    276             Self::None => 0,
    277             Self::SecurityKey | Self::ClientDevice | Self::Hybrid => 1,
    278             Self::SecurityKeyClientDevice
    279             | Self::ClientDeviceSecurityKey
    280             | Self::SecurityKeyHybrid
    281             | Self::HybridSecurityKey
    282             | Self::ClientDeviceHybrid
    283             | Self::HybridClientDevice => 2,
    284             Self::SecurityKeyClientDeviceHybrid
    285             | Self::SecurityKeyHybridClientDevice
    286             | Self::ClientDeviceSecurityKeyHybrid
    287             | Self::ClientDeviceHybridSecurityKey
    288             | Self::HybridSecurityKeyClientDevice
    289             | Self::HybridClientDeviceSecurityKey => 3,
    290         };
    291         serializer.serialize_seq(Some(count)).and_then(|mut ser| {
    292             match *self {
    293                 Self::None => Ok(()),
    294                 Self::SecurityKey => ser.serialize_element(SECURITY_KEY),
    295                 Self::ClientDevice => ser.serialize_element(CLIENT_DEVICE),
    296                 Self::Hybrid => ser.serialize_element(HYBRID),
    297                 Self::SecurityKeyClientDevice => ser
    298                     .serialize_element(SECURITY_KEY)
    299                     .and_then(|()| ser.serialize_element(CLIENT_DEVICE)),
    300                 Self::ClientDeviceSecurityKey => ser
    301                     .serialize_element(CLIENT_DEVICE)
    302                     .and_then(|()| ser.serialize_element(SECURITY_KEY)),
    303                 Self::SecurityKeyHybrid => ser
    304                     .serialize_element(SECURITY_KEY)
    305                     .and_then(|()| ser.serialize_element(HYBRID)),
    306                 Self::HybridSecurityKey => ser
    307                     .serialize_element(HYBRID)
    308                     .and_then(|()| ser.serialize_element(SECURITY_KEY)),
    309                 Self::ClientDeviceHybrid => ser
    310                     .serialize_element(CLIENT_DEVICE)
    311                     .and_then(|()| ser.serialize_element(HYBRID)),
    312                 Self::HybridClientDevice => ser
    313                     .serialize_element(HYBRID)
    314                     .and_then(|()| ser.serialize_element(CLIENT_DEVICE)),
    315                 Self::SecurityKeyClientDeviceHybrid => {
    316                     ser.serialize_element(SECURITY_KEY).and_then(|()| {
    317                         ser.serialize_element(CLIENT_DEVICE)
    318                             .and_then(|()| ser.serialize_element(HYBRID))
    319                     })
    320                 }
    321                 Self::SecurityKeyHybridClientDevice => {
    322                     ser.serialize_element(SECURITY_KEY).and_then(|()| {
    323                         ser.serialize_element(HYBRID)
    324                             .and_then(|()| ser.serialize_element(CLIENT_DEVICE))
    325                     })
    326                 }
    327                 Self::ClientDeviceSecurityKeyHybrid => {
    328                     ser.serialize_element(CLIENT_DEVICE).and_then(|()| {
    329                         ser.serialize_element(SECURITY_KEY)
    330                             .and_then(|()| ser.serialize_element(HYBRID))
    331                     })
    332                 }
    333                 Self::ClientDeviceHybridSecurityKey => {
    334                     ser.serialize_element(CLIENT_DEVICE).and_then(|()| {
    335                         ser.serialize_element(HYBRID)
    336                             .and_then(|()| ser.serialize_element(SECURITY_KEY))
    337                     })
    338                 }
    339                 Self::HybridSecurityKeyClientDevice => {
    340                     ser.serialize_element(HYBRID).and_then(|()| {
    341                         ser.serialize_element(SECURITY_KEY)
    342                             .and_then(|()| ser.serialize_element(CLIENT_DEVICE))
    343                     })
    344                 }
    345                 Self::HybridClientDeviceSecurityKey => {
    346                     ser.serialize_element(HYBRID).and_then(|()| {
    347                         ser.serialize_element(CLIENT_DEVICE)
    348                             .and_then(|()| ser.serialize_element(SECURITY_KEY))
    349                     })
    350                 }
    351             }
    352             .and_then(|()| ser.end())
    353         })
    354     }
    355 }
    356 impl Serialize for PrfInput<'_, '_> {
    357     /// Serializes `self` to conform with
    358     /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues).
    359     ///
    360     /// # Examples
    361     ///
    362     /// ```
    363     /// # use webauthn_rp::request::{PrfInput, ExtensionReq};
    364     /// assert_eq!(
    365     ///     serde_json::to_string(&PrfInput {
    366     ///         first: [0; 4].as_slice(),
    367     ///         second: Some([2; 1].as_slice()),
    368     ///     })?,
    369     ///     r#"{"first":"AAAAAA","second":"Ag"}"#
    370     /// );
    371     /// # Ok::<_, serde_json::Error>(())
    372     /// ```
    373     #[expect(
    374         clippy::arithmetic_side_effects,
    375         reason = "comment justifies how overflow is not possible"
    376     )]
    377     #[inline]
    378     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    379     where
    380         S: Serializer,
    381     {
    382         serializer
    383             // The max value is 1 + 1 = 2, so overflow is not an issue.
    384             .serialize_struct("PrfInput", 1 + usize::from(self.second.is_some()))
    385             .and_then(|mut ser| {
    386                 ser.serialize_field("first", BASE64URL_NOPAD.encode(self.first).as_str())
    387                     .and_then(|()| {
    388                         self.second
    389                             .as_ref()
    390                             .map_or(Ok(()), |second| {
    391                                 ser.serialize_field(
    392                                     "second",
    393                                     BASE64URL_NOPAD.encode(second).as_str(),
    394                                 )
    395                             })
    396                             .and_then(|()| ser.end())
    397                     })
    398             })
    399     }
    400 }