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}