webauthn_rp

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

ser.rs (15503B)


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