webauthn_rs_proto/
options.rs

1//! Types that define options as to how an authenticator may interact with
2//! with the server.
3
4use base64urlsafedata::Base64UrlSafeData;
5use serde::{Deserialize, Serialize};
6use std::fmt::Display;
7use std::{collections::BTreeMap, str::FromStr};
8
9/// Defines the User Authenticator Verification policy. This is documented
10/// <https://w3c.github.io/webauthn/#enumdef-userverificationrequirement>, and each
11/// variant lists it's effects.
12///
13/// To be clear, Verification means that the Authenticator perform extra or supplementary
14/// interaction with the user to verify who they are. An example of this is Apple Touch Id
15/// required a fingerprint to be verified, or a yubico device requiring a pin in addition to
16/// a touch event.
17///
18/// An example of a non-verified interaction is a yubico device with no pin where touch is
19/// the only interaction - we only verify a user is present, but we don't have extra details
20/// to the legitimacy of that user.
21///
22/// As UserVerificationPolicy is *only* used in credential registration, this stores the
23/// verification state of the credential in the persisted credential. These persisted
24/// credentials define which UserVerificationPolicy is issued during authentications.
25///
26/// **IMPORTANT** - Due to limitations of the webauthn specification, CTAP devices, and browser
27/// implementations, the only secure choice as an RP is *required*.
28///
29/// > ⚠️  **WARNING** - discouraged is marked with a warning, as some authenticators
30/// > will FORCE verification during registration but NOT during authentication.
31/// > This makes it impossible for a relying party to *consistently* enforce user verification,
32/// > which can confuse users and lead them to distrust user verification is being enforced.
33///
34/// > ⚠️  **WARNING** - preferred can lead to authentication errors in some cases due to browser
35/// > peripheral exchange allowing authentication verification bypass. Webauthn RS is not vulnerable
36/// > to these bypasses due to our
37/// > tracking of UV during registration through authentication, however preferred can cause
38/// > legitimate credentials to not prompt for UV correctly due to browser perhipheral exchange
39/// > leading Webauthn RS to deny them in what should otherwise be legitimate operations.
40#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
41#[allow(non_camel_case_types)]
42#[serde(rename_all = "lowercase")]
43pub enum UserVerificationPolicy {
44    /// Require user verification bit to be set, and fail the registration or authentication
45    /// if false. If the authenticator is not able to perform verification, it will not be
46    /// usable with this policy.
47    ///
48    /// This policy is the default as it is the only secure and consistent user verification option.
49    #[serde(rename = "required")]
50    #[default]
51    Required,
52    /// Prefer UV if possible, but ignore if not present. In other webauthn deployments this is bypassable
53    /// as it implies the library will not check UV is set correctly for this credential. Webauthn-RS
54    /// is *not* vulnerable to this as we check the UV state always based on it's presence at registration.
55    ///
56    /// However, in some cases use of this policy can lead to some credentials failing to verify
57    /// correctly due to browser peripheral exchange bypasses.
58    #[serde(rename = "preferred")]
59    Preferred,
60    /// Discourage - but do not prevent - user verification from being supplied. Many CTAP devices
61    /// will attempt UV during registration but not authentication leading to user confusion.
62    #[serde(rename = "discouraged")]
63    Discouraged_DO_NOT_USE,
64}
65
66/// Relying Party Entity
67#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
68#[serde(rename_all = "camelCase")]
69pub struct RelyingParty {
70    /// The name of the relying party.
71    pub name: String,
72    /// The id of the relying party.
73    pub id: String,
74    // Note: "icon" is deprecated: https://github.com/w3c/webauthn/pull/1337
75}
76
77/// User Entity
78#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
79#[serde(rename_all = "camelCase")]
80pub struct User {
81    /// The user's id in base64 form. This MUST be a unique id, and
82    /// must NOT contain personally identifying information, as this value can NEVER
83    /// be changed. If in doubt, use a UUID.
84    pub id: Base64UrlSafeData,
85    /// A detailed name for the account, such as an email address. This value
86    /// **can** change, so **must not** be used as a primary key.
87    pub name: String,
88    /// The user's preferred name for display. This value **can** change, so
89    /// **must not** be used as a primary key.
90    pub display_name: String,
91    // Note: "icon" is deprecated: https://github.com/w3c/webauthn/pull/1337
92}
93
94/// Public key cryptographic parameters
95#[derive(Debug, Serialize, Clone, Deserialize)]
96pub struct PubKeyCredParams {
97    /// The type of public-key credential.
98    #[serde(rename = "type")]
99    pub type_: String,
100    /// The algorithm in use defined by COSE.
101    pub alg: i64,
102}
103
104/// <https://www.w3.org/TR/webauthn/#enumdef-attestationconveyancepreference>
105#[derive(Debug, Serialize, Clone, Deserialize, Default)]
106#[serde(rename_all = "lowercase")]
107pub enum AttestationConveyancePreference {
108    /// Do not request attestation.
109    /// <https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-none>
110    #[default]
111    None,
112
113    /// Request attestation in a semi-anonymized form.
114    /// <https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-indirect>
115    Indirect,
116
117    /// Request attestation in a direct form.
118    /// <https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-direct>
119    Direct,
120}
121
122/// <https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport>
123#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
124#[serde(rename_all = "lowercase")]
125#[allow(unused)]
126pub enum AuthenticatorTransport {
127    /// <https://www.w3.org/TR/webauthn/#dom-authenticatortransport-usb>
128    Usb,
129    /// <https://www.w3.org/TR/webauthn/#dom-authenticatortransport-nfc>
130    Nfc,
131    /// <https://www.w3.org/TR/webauthn/#dom-authenticatortransport-ble>
132    Ble,
133    /// <https://www.w3.org/TR/webauthn/#dom-authenticatortransport-internal>
134    Internal,
135    /// Hybrid transport, formerly caBLE. Part of the level 3 draft specification.
136    /// <https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid>
137    Hybrid,
138    /// Test transport; used for Windows 10.
139    Test,
140    /// An unknown transport was provided - it will be ignored.
141    #[serde(other)]
142    Unknown,
143}
144
145impl FromStr for AuthenticatorTransport {
146    type Err = ();
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        use AuthenticatorTransport::*;
149
150        // "internal" is longest (8 chars)
151        if s.len() > 8 {
152            return Err(());
153        }
154
155        Ok(match s.to_ascii_lowercase().as_str() {
156            "usb" => Usb,
157            "nfc" => Nfc,
158            "ble" => Ble,
159            "internal" => Internal,
160            "test" => Test,
161            "hybrid" => Hybrid,
162            &_ => return Err(()),
163        })
164    }
165}
166
167impl Display for AuthenticatorTransport {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        f.write_str(self.as_ref())
170    }
171}
172
173impl AsRef<str> for AuthenticatorTransport {
174    fn as_ref(&self) -> &'static str {
175        use AuthenticatorTransport::*;
176        match self {
177            Usb => "usb",
178            Nfc => "nfc",
179            Ble => "ble",
180            Internal => "internal",
181            Test => "test",
182            Hybrid => "hybrid",
183            Unknown => "unknown",
184        }
185    }
186}
187
188/// The type of attestation on the credential
189///
190/// <https://www.iana.org/assignments/webauthn/webauthn.xhtml>
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
192pub enum AttestationFormat {
193    /// Packed attestation
194    #[serde(rename = "packed", alias = "Packed")]
195    Packed,
196    /// TPM attestation (like Microsoft)
197    #[serde(rename = "tpm", alias = "Tpm", alias = "TPM")]
198    Tpm,
199    /// Android hardware attestation
200    #[serde(rename = "android-key", alias = "AndroidKey")]
201    AndroidKey,
202    /// Older Android Safety Net
203    #[serde(
204        rename = "android-safetynet",
205        alias = "AndroidSafetyNet",
206        alias = "AndroidSafetynet"
207    )]
208    AndroidSafetyNet,
209    /// Old U2F attestation type
210    #[serde(rename = "fido-u2f", alias = "FIDOU2F")]
211    FIDOU2F,
212    /// Apple touchID/faceID
213    #[serde(rename = "apple", alias = "AppleAnonymous")]
214    AppleAnonymous,
215    /// No attestation
216    #[serde(rename = "none", alias = "None")]
217    None,
218}
219
220impl TryFrom<&str> for AttestationFormat {
221    type Error = ();
222
223    fn try_from(a: &str) -> Result<AttestationFormat, Self::Error> {
224        match a {
225            "packed" => Ok(AttestationFormat::Packed),
226            "tpm" => Ok(AttestationFormat::Tpm),
227            "android-key" => Ok(AttestationFormat::AndroidKey),
228            "android-safetynet" => Ok(AttestationFormat::AndroidSafetyNet),
229            "fido-u2f" => Ok(AttestationFormat::FIDOU2F),
230            "apple" => Ok(AttestationFormat::AppleAnonymous),
231            "none" => Ok(AttestationFormat::None),
232            // _ => Err(WebauthnError::AttestationNotSupported),
233            _ => Err(()),
234        }
235    }
236}
237
238/// <https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor>
239#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
240pub struct PublicKeyCredentialDescriptor {
241    /// The type of credential
242    #[serde(rename = "type")]
243    pub type_: String,
244    /// The credential id.
245    pub id: Base64UrlSafeData,
246    /// The allowed transports for this credential. Note this is a hint, and is NOT
247    /// enforced.
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub transports: Option<Vec<AuthenticatorTransport>>,
250}
251
252/// The authenticator attachment hint. This is NOT enforced, and is only used
253/// to help a user select a relevant authenticator type.
254///
255/// <https://www.w3.org/TR/webauthn/#attachment>
256#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
257pub enum AuthenticatorAttachment {
258    /// Request a device that is part of the machine aka inseperable.
259    /// <https://www.w3.org/TR/webauthn/#attachment>
260    #[serde(rename = "platform")]
261    Platform,
262    /// Request a device that can be seperated from the machine aka an external token.
263    /// <https://www.w3.org/TR/webauthn/#attachment>
264    #[serde(rename = "cross-platform")]
265    CrossPlatform,
266}
267
268/// A hint as to the class of device that is expected to fufil this operation.
269///
270/// <https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhints>
271#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
272#[serde(rename_all = "kebab-case")]
273#[allow(unused)]
274pub enum PublicKeyCredentialHints {
275    /// The credential is a removable security key
276    SecurityKey,
277    /// The credential is a platform authenticator
278    ClientDevice,
279    /// The credential will come from an external device
280    Hybrid,
281}
282
283/// The Relying Party's requirements for client-side discoverable credentials.
284///
285/// <https://www.w3.org/TR/webauthn-2/#enumdef-residentkeyrequirement>
286#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
287#[serde(rename_all = "lowercase")]
288pub enum ResidentKeyRequirement {
289    /// <https://www.w3.org/TR/webauthn-2/#dom-residentkeyrequirement-discouraged>
290    Discouraged,
291    /// ⚠️  In all major browsers preferred is identical in behaviour to required.
292    /// You should use required instead.
293    /// <https://www.w3.org/TR/webauthn-2/#dom-residentkeyrequirement-preferred>
294    Preferred,
295    /// <https://www.w3.org/TR/webauthn-2/#dom-residentkeyrequirement-required>
296    Required,
297}
298
299/// <https://www.w3.org/TR/webauthn/#dictdef-authenticatorselectioncriteria>
300#[derive(Debug, Default, Serialize, Clone, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct AuthenticatorSelectionCriteria {
303    /// How the authenticator should be attached to the client machine.
304    /// Note this is only a hint. It is not enforced in anyway shape or form.
305    /// <https://www.w3.org/TR/webauthn/#attachment>
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub authenticator_attachment: Option<AuthenticatorAttachment>,
308
309    /// Hint to the credential to create a resident key. Note this value should be
310    /// a member of ResidentKeyRequirement, but client must ignore unknown values,
311    /// treating an unknown value as if the member does not exist.
312    /// <https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-residentkey>
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub resident_key: Option<ResidentKeyRequirement>,
315
316    /// Hint to the credential to create a resident key. Note this can not be enforced
317    /// or validated, so the authenticator may choose to ignore this parameter.
318    /// <https://www.w3.org/TR/webauthn/#resident-credential>
319    pub require_resident_key: bool,
320
321    /// The user verification level to request during registration. Depending on if this
322    /// authenticator provides verification may affect future interactions as this is
323    /// associated to the credential during registration.
324    pub user_verification: UserVerificationPolicy,
325}
326
327/// A descriptor of a credential that can be used.
328#[derive(Debug, Serialize, Clone, Deserialize)]
329pub struct AllowCredentials {
330    #[serde(rename = "type")]
331    /// The type of credential.
332    pub type_: String,
333    /// The id of the credential.
334    pub id: Base64UrlSafeData,
335    /// <https://www.w3.org/TR/webauthn/#transport>
336    /// may be usb, nfc, ble, internal
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub transports: Option<Vec<AuthenticatorTransport>>,
339}
340
341/// The data collected and hashed in the operation.
342/// <https://www.w3.org/TR/webauthn-2/#dictdef-collectedclientdata>
343#[derive(Debug, Serialize, Clone, Deserialize)]
344pub struct CollectedClientData {
345    /// The credential type
346    #[serde(rename = "type")]
347    pub type_: String,
348    /// The challenge.
349    pub challenge: Base64UrlSafeData,
350    /// The rp origin as the browser understood it.
351    pub origin: url::Url,
352    /// The inverse of the sameOriginWithAncestors argument value that was
353    /// passed into the internal method.
354    #[serde(rename = "crossOrigin", skip_serializing_if = "Option::is_none")]
355    pub cross_origin: Option<bool>,
356    /// tokenBinding.
357    #[serde(rename = "tokenBinding")]
358    pub token_binding: Option<TokenBinding>,
359    /// This struct be extended, so it's important to be tolerant of unknown
360    /// keys.
361    #[serde(flatten)]
362    pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
363}
364
365/*
366impl TryFrom<&[u8]> for CollectedClientData {
367    type Error = WebauthnError;
368    fn try_from(data: &[u8]) -> Result<CollectedClientData, WebauthnError> {
369        let ccd: CollectedClientData =
370            serde_json::from_slice(data).map_err(WebauthnError::ParseJSONFailure)?;
371        Ok(ccd)
372    }
373}
374*/
375
376/// Token binding
377#[derive(Debug, Clone, Deserialize, Serialize)]
378pub struct TokenBinding {
379    /// status
380    pub status: String,
381    /// id
382    pub id: Option<String>,
383}
384
385#[cfg(test)]
386mod test {
387    use std::str::FromStr;
388
389    use crate::AuthenticatorTransport;
390
391    #[test]
392    fn test_authenticator_transports_from_str() {
393        let cases: [(&str, AuthenticatorTransport); 6] = [
394            ("ble", AuthenticatorTransport::Ble),
395            ("internal", AuthenticatorTransport::Internal),
396            ("nfc", AuthenticatorTransport::Nfc),
397            ("usb", AuthenticatorTransport::Usb),
398            ("test", AuthenticatorTransport::Test),
399            ("hybrid", AuthenticatorTransport::Hybrid),
400        ];
401
402        for (s, t) in cases {
403            assert_eq!(
404                t,
405                AuthenticatorTransport::from_str(s).expect("unknown authenticatorTransport")
406            );
407            assert_eq!(s, AuthenticatorTransport::to_string(&t));
408        }
409
410        assert!(AuthenticatorTransport::from_str("fake fake").is_err());
411    }
412
413    #[test]
414    fn test_authenticator_transports_serde() {
415        let cases: [(&str, AuthenticatorTransport); 9] = [
416            ("\"ble\"", AuthenticatorTransport::Ble),
417            ("\"internal\"", AuthenticatorTransport::Internal),
418            ("\"nfc\"", AuthenticatorTransport::Nfc),
419            ("\"usb\"", AuthenticatorTransport::Usb),
420            ("\"test\"", AuthenticatorTransport::Test),
421            ("\"hybrid\"", AuthenticatorTransport::Hybrid),
422            ("\"unknown\"", AuthenticatorTransport::Unknown),
423            ("\"cable\"", AuthenticatorTransport::Unknown),
424            ("\"auth mc authface\"", AuthenticatorTransport::Unknown),
425        ];
426
427        for (s, t) in cases {
428            assert_eq!(
429                t,
430                serde_json::from_str(s).expect("Unable to parse transport")
431            );
432        }
433    }
434}