webauthn-rs-proto

Patched webauthn-rs-proto (https://crates.io/crates/webauthn-rs-proto) that adds support for Ed25519.
git clone https://git.philomathiclife.com/repos/webauthn-rs-proto
Log | Files | Refs | README | LICENSE

extensions.rs (14983B)


      1 //! Extensions allowing certain types of authenticators to provide supplemental information.
      2 
      3 use base64urlsafedata::Base64UrlSafeData;
      4 use serde::{Deserialize, Serialize};
      5 
      6 /// Valid credential protection policies
      7 #[derive(Debug, Serialize, Clone, Copy, Deserialize, PartialEq, Eq)]
      8 #[serde(rename_all = "camelCase")]
      9 #[repr(u8)]
     10 pub enum CredentialProtectionPolicy {
     11     /// This reflects "FIDO_2_0" semantics. In this configuration, performing
     12     /// some form of user verification is optional with or without credentialID
     13     /// list. This is the default state of the credential if the extension is
     14     /// not specified.
     15     UserVerificationOptional = 0x1,
     16     /// In this configuration, credential is discovered only when its
     17     /// credentialID is provided by the platform or when some form of user
     18     /// verification is performed.
     19     UserVerificationOptionalWithCredentialIDList = 0x2,
     20     /// This reflects that discovery and usage of the credential MUST be
     21     /// preceded by some form of user verification.
     22     UserVerificationRequired = 0x3,
     23 }
     24 
     25 impl TryFrom<u8> for CredentialProtectionPolicy {
     26     type Error = &'static str;
     27 
     28     fn try_from(v: u8) -> Result<Self, Self::Error> {
     29         use CredentialProtectionPolicy::*;
     30         match v {
     31             0x1 => Ok(UserVerificationOptional),
     32             0x2 => Ok(UserVerificationOptionalWithCredentialIDList),
     33             0x3 => Ok(UserVerificationRequired),
     34             _ => Err("Invalid policy number"),
     35         }
     36     }
     37 }
     38 
     39 /// The desired options for the client's use of the `credProtect` extension
     40 ///
     41 /// <https://fidoalliance.org/specs/fido-v2.1-rd-20210309/fido-client-to-authenticator-protocol-v2.1-rd-20210309.html#sctn-credProtect-extension>
     42 #[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
     43 #[serde(rename_all = "camelCase")]
     44 pub struct CredProtect {
     45     /// The credential policy to enact
     46     pub credential_protection_policy: CredentialProtectionPolicy,
     47     /// Whether it is better for the authenticator to fail to create a
     48     /// credential rather than ignore the protection policy
     49     /// If no value is provided, the client treats it as `false`.
     50     #[serde(skip_serializing_if = "Option::is_none")]
     51     pub enforce_credential_protection_policy: Option<bool>,
     52 }
     53 
     54 /// Extension option inputs for PublicKeyCredentialCreationOptions.
     55 ///
     56 /// Implements \[AuthenticatorExtensionsClientInputs\] from the spec.
     57 #[derive(Debug, Serialize, Clone, Deserialize)]
     58 #[serde(rename_all = "camelCase")]
     59 pub struct RequestRegistrationExtensions {
     60     /// The `credProtect` extension options
     61     #[serde(flatten, skip_serializing_if = "Option::is_none")]
     62     pub cred_protect: Option<CredProtect>,
     63 
     64     /// ⚠️  - Browsers do not support this!
     65     /// Uvm
     66     #[serde(skip_serializing_if = "Option::is_none")]
     67     pub uvm: Option<bool>,
     68 
     69     /// ⚠️  - This extension result is always unsigned, and only indicates if the
     70     /// browser *requests* a residentKey to be created. It has no bearing on the
     71     /// true rk state of the credential.
     72     #[serde(skip_serializing_if = "Option::is_none")]
     73     pub cred_props: Option<bool>,
     74 
     75     /// CTAP2.1 Minumum pin length
     76     #[serde(skip_serializing_if = "Option::is_none")]
     77     pub min_pin_length: Option<bool>,
     78 
     79     /// ⚠️  - Browsers support the *creation* of the secret, but not the retrieval of it.
     80     /// CTAP2.1 create hmac secret
     81     #[serde(skip_serializing_if = "Option::is_none")]
     82     pub hmac_create_secret: Option<bool>,
     83 }
     84 
     85 impl Default for RequestRegistrationExtensions {
     86     fn default() -> Self {
     87         RequestRegistrationExtensions {
     88             cred_protect: None,
     89             uvm: Some(true),
     90             cred_props: Some(true),
     91             min_pin_length: None,
     92             hmac_create_secret: None,
     93         }
     94     }
     95 }
     96 
     97 // Unable to create from, because it's an out of crate struct
     98 #[allow(clippy::from_over_into)]
     99 #[cfg(feature = "wasm")]
    100 impl Into<js_sys::Object> for &RequestRegistrationExtensions {
    101     fn into(self) -> js_sys::Object {
    102         use js_sys::Object;
    103         use wasm_bindgen::JsValue;
    104 
    105         let RequestRegistrationExtensions {
    106             cred_protect,
    107             uvm,
    108             cred_props,
    109             min_pin_length,
    110             hmac_create_secret,
    111         } = self;
    112 
    113         let obj = Object::new();
    114 
    115         if let Some(cred_protect) = cred_protect {
    116             let jsv = serde_wasm_bindgen::to_value(&cred_protect).unwrap();
    117             js_sys::Reflect::set(&obj, &"credProtect".into(), &jsv).unwrap();
    118         }
    119 
    120         if let Some(uvm) = uvm {
    121             js_sys::Reflect::set(&obj, &"uvm".into(), &JsValue::from_bool(*uvm)).unwrap();
    122         }
    123 
    124         if let Some(cred_props) = cred_props {
    125             js_sys::Reflect::set(&obj, &"credProps".into(), &JsValue::from_bool(*cred_props))
    126                 .unwrap();
    127         }
    128 
    129         if let Some(min_pin_length) = min_pin_length {
    130             js_sys::Reflect::set(
    131                 &obj,
    132                 &"minPinLength".into(),
    133                 &JsValue::from_bool(*min_pin_length),
    134             )
    135             .unwrap();
    136         }
    137 
    138         if let Some(hmac_create_secret) = hmac_create_secret {
    139             js_sys::Reflect::set(
    140                 &obj,
    141                 &"hmacCreateSecret".into(),
    142                 &JsValue::from_bool(*hmac_create_secret),
    143             )
    144             .unwrap();
    145         }
    146 
    147         obj
    148     }
    149 }
    150 
    151 // ========== Auth exten ============
    152 
    153 /// The inputs to the hmac secret if it was created during registration.
    154 ///
    155 /// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-hmac-secret-extension>
    156 #[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
    157 #[serde(rename_all = "camelCase")]
    158 pub struct HmacGetSecretInput {
    159     /// Retrieve a symmetric secrets from the authenticator with this input.
    160     pub output1: Base64UrlSafeData,
    161     /// Rotate the secret in the same operation.
    162     pub output2: Option<Base64UrlSafeData>,
    163 }
    164 
    165 /// Extension option inputs for PublicKeyCredentialRequestOptions
    166 ///
    167 /// Implements \[AuthenticatorExtensionsClientInputs\] from the spec
    168 #[derive(Debug, Serialize, Clone, Deserialize)]
    169 #[serde(rename_all = "camelCase")]
    170 pub struct RequestAuthenticationExtensions {
    171     /// The `appid` extension options
    172     #[serde(skip_serializing_if = "Option::is_none")]
    173     pub appid: Option<String>,
    174 
    175     /// ⚠️  - Browsers do not support this!
    176     /// Uvm
    177     #[serde(skip_serializing_if = "Option::is_none")]
    178     pub uvm: Option<bool>,
    179 
    180     /// ⚠️  - Browsers do not support this!
    181     /// <https://bugs.chromium.org/p/chromium/issues/detail?id=1023225>
    182     /// Hmac get secret
    183     #[serde(skip_serializing_if = "Option::is_none")]
    184     pub hmac_get_secret: Option<HmacGetSecretInput>,
    185 }
    186 
    187 // Unable to create from, because it's an out of crate struct
    188 #[allow(clippy::from_over_into)]
    189 #[cfg(feature = "wasm")]
    190 impl Into<js_sys::Object> for &RequestAuthenticationExtensions {
    191     fn into(self) -> js_sys::Object {
    192         use js_sys::{Object, Uint8Array};
    193         use wasm_bindgen::JsValue;
    194 
    195         let RequestAuthenticationExtensions {
    196             // I don't think we care?
    197             appid: _,
    198             uvm,
    199             hmac_get_secret,
    200         } = self;
    201 
    202         let obj = Object::new();
    203 
    204         if let Some(uvm) = uvm {
    205             js_sys::Reflect::set(&obj, &"uvm".into(), &JsValue::from_bool(*uvm)).unwrap();
    206         }
    207 
    208         if let Some(HmacGetSecretInput { output1, output2 }) = hmac_get_secret {
    209             let hmac = Object::new();
    210 
    211             let o1 = Uint8Array::from(output1.0.as_slice());
    212             js_sys::Reflect::set(&hmac, &"output1".into(), &o1).unwrap();
    213 
    214             if let Some(output2) = output2 {
    215                 let o2 = Uint8Array::from(output2.0.as_slice());
    216                 js_sys::Reflect::set(&hmac, &"output2".into(), &o2).unwrap();
    217             }
    218 
    219             js_sys::Reflect::set(&obj, &"hmacGetSecret".into(), &hmac).unwrap();
    220         }
    221 
    222         obj
    223     }
    224 }
    225 
    226 /// The response to a hmac get secret request.
    227 #[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
    228 #[serde(rename_all = "camelCase")]
    229 pub struct HmacGetSecretOutput {
    230     /// Output of HMAC(Salt 1 || Client Secret)
    231     pub output1: Base64UrlSafeData,
    232     /// Output of HMAC(Salt 2 || Client Secret)
    233     pub output2: Option<Base64UrlSafeData>,
    234 }
    235 
    236 /// <https://w3c.github.io/webauthn/#dictdef-authenticationextensionsclientoutputs>
    237 /// The default option here for Options are None, so it can be derived
    238 #[derive(Debug, Deserialize, Serialize, Clone, Default)]
    239 pub struct AuthenticationExtensionsClientOutputs {
    240     /// Indicates whether the client used the provided appid extension
    241     #[serde(default)]
    242     pub appid: Option<bool>,
    243     /// The response to a hmac get secret request.
    244     #[serde(default)]
    245     pub hmac_get_secret: Option<HmacGetSecretOutput>,
    246 }
    247 
    248 #[cfg(feature = "wasm")]
    249 impl From<web_sys::AuthenticationExtensionsClientOutputs>
    250     for AuthenticationExtensionsClientOutputs
    251 {
    252     fn from(
    253         ext: web_sys::AuthenticationExtensionsClientOutputs,
    254     ) -> AuthenticationExtensionsClientOutputs {
    255         use js_sys::Uint8Array;
    256 
    257         let appid = js_sys::Reflect::get(&ext, &"appid".into())
    258             .ok()
    259             .and_then(|jv| jv.as_bool());
    260 
    261         let hmac_get_secret = js_sys::Reflect::get(&ext, &"hmacGetSecret".into())
    262             .ok()
    263             .and_then(|jv| {
    264                 let output2 = js_sys::Reflect::get(&jv, &"output2".into())
    265                     .map(|v| Uint8Array::new(&v).to_vec())
    266                     .map(Base64UrlSafeData)
    267                     .ok();
    268 
    269                 let output1 = js_sys::Reflect::get(&jv, &"output1".into())
    270                     .map(|v| Uint8Array::new(&v).to_vec())
    271                     .map(Base64UrlSafeData)
    272                     .ok();
    273 
    274                 output1.map(|output1| HmacGetSecretOutput { output1, output2 })
    275             });
    276 
    277         AuthenticationExtensionsClientOutputs {
    278             appid,
    279             hmac_get_secret,
    280         }
    281     }
    282 }
    283 
    284 /// <https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension>
    285 #[derive(Debug, Deserialize, Serialize, Clone)]
    286 pub struct CredProps {
    287     rk: bool,
    288 }
    289 
    290 /// <https://w3c.github.io/webauthn/#dictdef-authenticationextensionsclientoutputs>
    291 /// The default option here for Options are None, so it can be derived
    292 #[derive(Debug, Deserialize, Serialize, Clone, Default)]
    293 pub struct RegistrationExtensionsClientOutputs {
    294     /// Indicates whether the client used the provided appid extension
    295     #[serde(default, skip_serializing_if = "Option::is_none")]
    296     pub appid: Option<bool>,
    297 
    298     /// Indicates if the client believes it created a resident key. This
    299     /// property is managed by the webbrowser, and is NOT SIGNED and CAN NOT be trusted!
    300     #[serde(default, skip_serializing_if = "Option::is_none")]
    301     pub cred_props: Option<CredProps>,
    302 
    303     /// Indicates if the client successfully applied a HMAC Secret
    304     #[serde(default, skip_serializing_if = "Option::is_none")]
    305     pub hmac_secret: Option<bool>,
    306 
    307     /// Indicates if the client successfully applied a credential protection policy.
    308     #[serde(default, skip_serializing_if = "Option::is_none")]
    309     pub cred_protect: Option<CredentialProtectionPolicy>,
    310 
    311     /// Indicates the current minimum PIN length
    312     #[serde(default, skip_serializing_if = "Option::is_none")]
    313     pub min_pin_length: Option<u32>,
    314 }
    315 
    316 #[cfg(feature = "wasm")]
    317 impl From<web_sys::AuthenticationExtensionsClientOutputs> for RegistrationExtensionsClientOutputs {
    318     fn from(
    319         ext: web_sys::AuthenticationExtensionsClientOutputs,
    320     ) -> RegistrationExtensionsClientOutputs {
    321         let appid = js_sys::Reflect::get(&ext, &"appid".into())
    322             .ok()
    323             .and_then(|jv| jv.as_bool());
    324 
    325         // Destructure "credProps":{"rk":false} from within a map.
    326         let cred_props = js_sys::Reflect::get(&ext, &"credProps".into())
    327             .ok()
    328             .and_then(|cred_props_struct| {
    329                 js_sys::Reflect::get(&cred_props_struct, &"rk".into())
    330                     .ok()
    331                     .and_then(|jv| jv.as_bool())
    332                     .map(|rk| CredProps { rk })
    333             });
    334 
    335         let hmac_secret = js_sys::Reflect::get(&ext, &"hmac-secret".into())
    336             .ok()
    337             .and_then(|jv| jv.as_bool());
    338 
    339         let cred_protect = js_sys::Reflect::get(&ext, &"credProtect".into())
    340             .ok()
    341             .and_then(|jv| jv.as_f64())
    342             .and_then(|f| CredentialProtectionPolicy::try_from(f as u8).ok());
    343 
    344         let min_pin_length = js_sys::Reflect::get(&ext, &"minPinLength".into())
    345             .ok()
    346             .and_then(|jv| jv.as_f64())
    347             .map(|f| f as u32);
    348 
    349         RegistrationExtensionsClientOutputs {
    350             appid,
    351             cred_props,
    352             hmac_secret,
    353             cred_protect,
    354             min_pin_length,
    355         }
    356     }
    357 }
    358 
    359 /// The result state of an extension as returned from the authenticator.
    360 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
    361 pub enum ExtnState<T>
    362 where
    363     T: Clone + core::fmt::Debug,
    364 {
    365     /// This extension was not requested, and so no result was provided.
    366     #[default]
    367     NotRequested,
    368     /// The extension was requested, and the authenticator did NOT act on it.
    369     Ignored,
    370     /// The extension was requested, and the authenticator correctly responded.
    371     Set(T),
    372     /// The extension was not requested, and the authenticator sent an unsolicited extension value.
    373     Unsolicited(T),
    374     /// ⚠️  WARNING: The data in this extension is not signed cryptographically, and can not be
    375     /// trusted for security assertions. It MAY be used for UI/UX hints.
    376     Unsigned(T),
    377 }
    378 
    379 /// The set of extensions that were registered by this credential.
    380 #[derive(Clone, Debug, Serialize, Deserialize)]
    381 pub struct RegisteredExtensions {
    382     // ⚠️  It's critical we place serde default here so that we
    383     // can deserialise in the future as we add new types!
    384     /// The state of the cred_protect extension
    385     #[serde(default)]
    386     pub cred_protect: ExtnState<CredentialProtectionPolicy>,
    387     /// The state of the hmac-secret extension, if it was created
    388     #[serde(default)]
    389     pub hmac_create_secret: ExtnState<bool>,
    390     /// The state of the client appid extensions
    391     #[serde(default)]
    392     pub appid: ExtnState<bool>,
    393     /// The state of the client credential properties extension
    394     #[serde(default)]
    395     pub cred_props: ExtnState<CredProps>,
    396 }
    397 
    398 impl RegisteredExtensions {
    399     /// Yield an empty set of registered extensions
    400     pub fn none() -> Self {
    401         RegisteredExtensions {
    402             cred_protect: ExtnState::NotRequested,
    403             hmac_create_secret: ExtnState::NotRequested,
    404             appid: ExtnState::NotRequested,
    405             cred_props: ExtnState::NotRequested,
    406         }
    407     }
    408 }
    409 
    410 /// The set of extensions that were provided by the client during authentication
    411 #[derive(Clone, Debug, Serialize, Deserialize)]
    412 pub struct AuthenticationExtensions {}