webauthn_rs_icp/lib.rs
1//! # Webauthn-rs - Webauthn for Rust Server Applications
2//!
3//! Webauthn is a standard allowing communication between servers, browsers and authenticators
4//! to allow strong, passwordless, cryptographic authentication to be performed. Webauthn
5//! is able to operate with many authenticator types, such as U2F, TouchID, Windows Hello
6//! and many more.
7//!
8//! This library aims to provide a secure Webauthn implementation that you can
9//! plug into your application, so that you can provide Webauthn to your users.
10//!
11//! There are a number of focused use cases that this library provides, which are described in
12//! the [WebauthnBuilder] and [Webauthn] struct.
13//!
14//! # Getting started
15//!
16//! In the simplest case where you just want a password replacement, you should use our passkey flow.
17//!
18//! ```
19//! use webauthn_rs::prelude::*;
20//!
21//! let rp_id = "example.com";
22//! let rp_origin = Url::parse("https://idm.example.com")
23//! .expect("Invalid URL");
24//! let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
25//! .expect("Invalid configuration");
26//! let webauthn = builder.build()
27//! .expect("Invalid configuration");
28//!
29//! // Initiate a basic registration flow to enroll a cryptographic authenticator
30//! let (ccr, skr) = webauthn
31//! .start_passkey_registration(
32//! Uuid::new_v4(),
33//! "claire",
34//! "Claire",
35//! None,
36//! )
37//! .expect("Failed to start registration.");
38//! ```
39//!
40//! After this point you then need to use `finish_passkey_registration`, followed by
41//! `start_passkey_authentication` and `finish_passkey_authentication`
42//!
43//! No other authentication factors are needed!
44//!
45//! # Tutorial
46//!
47//! A tutorial on how to use this library is on the project github <https://github.com/kanidm/webauthn-rs/tree/master/tutorial>
48//!
49//! # Features
50//!
51//! ## Allow Serialising Registration and Authentication State
52//!
53//! During a webauthn registration or authentication ceremony, a random challenge is produced and
54//! provided to the client. The full content of what is needed for the server to validate this
55//! challenge is stored in the associated registration or authentication state types. This value
56//! *MUST* be persisted on the server. If you store this in a cookie or some other form of client
57//! side stored value, the client can replay a previous authentication state and signature without
58//! possession of, or interaction with the authenticator, bypassing pretty much all of the guarantees
59//! of webauthn. Because of this risk by default these states are *not* allowed to be serialised
60//! which prevents them from accidentally being placed into a cookie.
61//!
62//! However there are some *safe* cases of serialising these values. This includes serialising to
63//! a database, or using a cookie "memory store" where the client side cookie is a key into a server-side
64//! map or similar. Both of these prevent the replay attack threat.
65//!
66//! An alternate but "less good" method to mitigate replay attacks is to associate a very short
67//! expiry window to the cookie if you need full client side state, but this may still allow some
68//! forms of real time replay attacks to occur.
69//!
70//! Enabling the feature `danger-allow-state-serialisation` allows you to re-enable serialisation
71//! of these types, provided you accept and understand the handling risks associated.
72//!
73//! This library supports some optional features that you may wish to use. These are all
74//! disabled by default as they have risks associated.
75//!
76//! ## Allow Insecure RSA_SHA1
77//!
78//! Many Windows Hello credentials are signed with RSA and SHA1. SHA1 is considered broken
79//! and should not be trusted in cryptographic contexts. These signatures are used only
80//! during attestation, but the credentials themself are generally RSA-SHA256. In some
81//! cases this may allow forgery of a credentials attestation, meaning you are unable to
82//! trust the integrity of the authenticator.
83//!
84//! For the broadest compatibility, and if you do not use attestation (such as passkey only users)
85//! you may choose to use RSA SHA1 signed credentials with `danger-insecure-rs1` as this has no impact
86//! on your system security. For users who use attestation, you should NOT enable this feature as it
87//! undermines attestation.
88//!
89//! ## Credential Internals and Type Changes
90//!
91//! By default the type wrappers around the keys are opaque. However in some cases you
92//! may wish to migrate a key between types (security key to passkey, passwordlesskey to passkey)
93//! for example. Alternately, you may wish to access the internals of a credential to implement
94//! an alternate serialisation or storage mechanism. In these cases you can access the underlying
95//! [Credential] type via Into and From by enabling the feature `danger-credential-internals`. The
96//! [Credential] type is exposed via the [prelude] when this feature is enabled.
97//!
98//! ## User-Presence only SecurityKeys
99//!
100//! By default, SecurityKeys will opportunistically enforce User Verification (Such as a PIN or
101//! Biometric). This can cause issues with Firefox which only supports CTAP1. An example of this
102//! is if you register a SecurityKey on chromium it will be bound to always perform UserVerification
103//! for the life of the SecurityKey precluding it's use on Firefox.
104//!
105//! Enabling the feature `danger-user-presence-only-security-keys` changes these keys to prevent
106//! User Verification if possible. However, newer keys will confusingly force a User Verification
107//! on registration, but will then not prompt for this during usage. Some user surveys have shown
108//! this to confuse users to why the UV is not requested, and it can lower trust in these tokens
109//! when they are elevated to be self-contained MFA as the user believes these UV prompts to be
110//! unreliable and not verified correctly. In these cases you MUST communicate to the user that
111//! the UV *may* occur on registration and then will not occur again, and that is *by design*.
112//!
113
114#![deny(warnings)]
115#![warn(unused_extern_crates)]
116#![warn(missing_docs)]
117#![deny(clippy::todo)]
118#![deny(clippy::unimplemented)]
119#![deny(clippy::unwrap_used)]
120#![deny(clippy::expect_used)]
121#![deny(clippy::panic)]
122#![deny(clippy::unreachable)]
123#![deny(clippy::await_holding_lock)]
124#![deny(clippy::needless_pass_by_value)]
125#![deny(clippy::trivially_copy_pass_by_ref)]
126
127// #[macro_use]
128// extern crate tracing;
129
130mod interface;
131
132use url::Url;
133// use uuid::Uuid;
134use webauthn_rs_core_icp::error::{WebauthnError, WebauthnResult};
135use webauthn_rs_core_icp::proto::*;
136use webauthn_rs_core_icp::WebauthnCore;
137
138use crate::interface::*;
139/// A prelude of types that are used by `Webauthn`
140pub mod prelude {
141 pub use crate::interface::*;
142 pub use crate::{Webauthn, WebauthnBuilder};
143 pub use base64urlsafedata_icp::Base64UrlSafeData;
144 pub use url::Url;
145 // pub use uuid::Uuid;
146 pub use webauthn_rs_core_icp::error::{WebauthnError, WebauthnResult};
147 #[cfg(feature = "danger-credential-internals")]
148 pub use webauthn_rs_core_icp::proto::Credential;
149 pub use webauthn_rs_core_icp::proto::{AttestationCa, AttestationCaList, AuthenticatorAttachment};
150 pub use webauthn_rs_core_icp::proto::{
151 AttestationMetadata, AuthenticationResult, AuthenticationState, CreationChallengeResponse,
152 CredentialID, ParsedAttestation, ParsedAttestationData, PublicKeyCredential,
153 RegisterPublicKeyCredential, RequestChallengeResponse,
154 };
155 pub use webauthn_rs_core_icp::AttestationFormat;
156}
157
158/// A constructor for a new [Webauthn] instance. This accepts and configures a number of site-wide
159/// properties that apply to all webauthn operations of this service.
160#[derive(Debug)]
161pub struct WebauthnBuilder<'a> {
162 rp_name: Option<&'a str>,
163 rp_id: &'a str,
164 allowed_origins: Vec<Url>,
165 allow_subdomains: bool,
166 allow_any_port: bool,
167 algorithms: Vec<COSEAlgorithm>,
168}
169
170impl<'a> WebauthnBuilder<'a> {
171 /// Initiate a new builder. This takes the relying party id and relying party origin.
172 ///
173 /// # Safety
174 ///
175 /// rp_id is what Credentials (Authenticators) bind themself to - rp_id can NOT be changed
176 /// without potentially breaking all of your associated credentials in the future!
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// use webauthn_rs::prelude::*;
182 ///
183 /// let rp_id = "example.com";
184 /// let rp_origin = Url::parse("https://idm.example.com")
185 /// .expect("Invalid URL");
186 /// let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
187 /// .expect("Invalid configuration");
188 /// ```
189 ///
190 /// # Errors
191 ///
192 /// rp_id *must* be an effective domain of rp_origin. This means that if you are hosting
193 /// `https://idm.example.com`, rp_id must be `idm.example.com`, `example.com` or `com`.
194 ///
195 /// ```
196 /// use webauthn_rs::prelude::*;
197 ///
198 /// let rp_id = "example.com";
199 /// let rp_origin = Url::parse("https://idm.different.com")
200 /// .expect("Invalid URL");
201 /// assert!(WebauthnBuilder::new(rp_id, &rp_origin).is_err());
202 /// ```
203 pub fn new(rp_id: &'a str, rp_origin: &'a Url) -> WebauthnResult<Self> {
204 // Check the rp_name and rp_id.
205 let valid = rp_origin
206 .domain()
207 .map(|effective_domain| {
208 // We need to prepend the '.' here to ensure that myexample.com != example.com,
209 // rather than just ends with.
210 effective_domain.ends_with(&format!(".{}", rp_id)) || effective_domain == rp_id
211 })
212 .unwrap_or(false);
213
214 if valid {
215 Ok(WebauthnBuilder {
216 rp_name: None,
217 rp_id,
218 allowed_origins: vec![rp_origin.to_owned()],
219 allow_subdomains: false,
220 allow_any_port: false,
221 algorithms: COSEAlgorithm::secure_algs(),
222 })
223 } else {
224 // error!("rp_id is not an effective_domain of rp_origin");
225 Err(WebauthnError::Configuration)
226 }
227 }
228
229 /// Setting this flag to true allows subdomains to be considered valid in Webauthn operations.
230 /// An example of this is if you wish for `https://au.idm.example.com` to be a valid domain
231 /// for Webauthn when the configuration is `https://idm.example.com`. Generally this occurs
232 /// when you have a centralised IDM system, but location specific systems with DNS based
233 /// redirection or routing.
234 ///
235 /// If in doubt, do NOT change this value. Defaults to "false".
236 pub fn allow_subdomains(mut self, allow: bool) -> Self {
237 self.allow_subdomains = allow;
238 self
239 }
240
241 /// Setting this flag skips port checks on origin matches
242 pub fn allow_any_port(mut self, allow: bool) -> Self {
243 self.allow_any_port = allow;
244 self
245 }
246
247 /// Set an origin to be considered valid in Webauthn operations. A common example of this is
248 /// enabling use with iOS or Android native "webauthn-like" APIs, which return different
249 /// origins than a web browser would.
250 pub fn append_allowed_origin(mut self, origin: &Url) -> Self {
251 self.allowed_origins.push(origin.to_owned());
252 self
253 }
254
255 /// Set the relying party name. This may be shown to the user. This value can be changed in
256 /// the future without affecting credentials that have already registered.
257 ///
258 /// If not set, defaults to rp_id.
259 pub fn rp_name(mut self, rp_name: &'a str) -> Self {
260 self.rp_name = Some(rp_name);
261 self
262 }
263
264 /// Complete the construction of the [Webauthn] instance. If an invalid configuration setting
265 /// is found, an Error may be returned.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// use webauthn_rs::prelude::*;
271 ///
272 /// let rp_id = "example.com";
273 /// let rp_origin = Url::parse("https://idm.example.com")
274 /// .expect("Invalid URL");
275 /// let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
276 /// .expect("Invalid configuration");
277 /// let webauthn = builder.build()
278 /// .expect("Invalid configuration");
279 /// ```
280 pub fn build(self) -> WebauthnResult<Webauthn> {
281 Ok(Webauthn {
282 core: WebauthnCore::new_unsafe_experts_only(
283 self.rp_name.unwrap_or(self.rp_id),
284 self.rp_id,
285 self.allowed_origins,
286 None,
287 Some(self.allow_subdomains),
288 Some(self.allow_any_port),
289 ),
290 algorithms: self.algorithms,
291 })
292 }
293}
294
295/// An instance of a Webauthn site. This is the main point of interaction for registering and
296/// authenticating credentials for users. Depending on your needs, you'll want to allow users
297/// to register and authenticate with different kinds of authenticators.
298///
299/// *I just want to replace passwords with strong cryptographic authentication, and I don't have other requirements*
300///
301/// --> You should use `start_passkey_registration`
302///
303///
304/// *I want to replace passwords with strong multi-factor cryptographic authentication, limited to
305/// a known set of controlled and trusted authenticator types*
306///
307/// This type requires `preview-features` enabled as the current form of the Attestation CA List
308/// may change in the future.
309///
310/// --> You should use `start_passwordlesskey_registration`
311///
312///
313/// *I want users to have their identites stored on their devices, and for them to authenticate with
314/// strong multi-factor cryptographic authentication limited to a known set of trusted authenticator types*
315///
316/// This authenticator type consumes resources of the users devices, and may result in failures,
317/// so you should only use it in tightly controlled environments where you supply devices to your
318/// users.
319///
320/// --> You should use `start_devicekey_registration` (still in development)
321///
322///
323/// *I want a security token along with an external password to create multi-factor authentication*
324///
325/// If possible, consider `start_passkey_registration` OR `start_passwordlesskey_registration`
326/// instead - it's likely to provide a better user experience than security keys as MFA!
327///
328/// --> If you really want a security key, you should use `start_securitykey_registration`
329///
330#[derive(Debug)]
331pub struct Webauthn {
332 core: WebauthnCore,
333 algorithms: Vec<COSEAlgorithm>,
334}
335
336impl Webauthn {
337 /// Get the currently configured origins
338 pub fn get_allowed_origins(&self) -> &[Url] {
339 self.core.get_allowed_origins()
340 }
341
342 /// Initiate the registration of a new pass key for a user. A pass key is any cryptographic
343 /// authenticator acting as a single factor of authentication, far stronger than a password
344 /// or email-reset link.
345 ///
346 /// Some examples of pass keys include Yubikeys, TouchID, FaceID, Windows Hello and others.
347 ///
348 /// The keys *may* exist and 'roam' between multiple devices. For example, Apple allows Passkeys
349 /// to sync between devices owned by the same Apple account. This can affect your risk model
350 /// related to these credentials, but generally in all cases passkeys are better than passwords!
351 ///
352 /// You *should* NOT pair this authentication with another factor. A passkey may opportunistically
353 /// allow and enforce user-verification (MFA), but this is NOT guaranteed with all authenticator
354 /// types.
355 ///
356 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
357 /// identify the user during certain client side work flows.
358 ///
359 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
360 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
361 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
362 /// MUST NOT be used as primary keys. They *may not* be present in authentication, these are only
363 /// present to allow client work flows to display human friendly identifiers.
364 ///
365 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
366 /// You *should* provide the list of credentials that are already registered to this user's account
367 /// to prevent duplicate credential registrations. These credentials *can* be from different
368 /// authenticator classes since we only require the `CredentialID`
369 ///
370 /// # Returns
371 ///
372 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
373 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
374 /// on the server the `PasskeyRegistration` which contains the state of this registration
375 /// attempt and is paired to the `CreationChallengeResponse`.
376 ///
377 /// WARNING ⚠️ YOU MUST STORE THE [PasskeyRegistration] VALUE SERVER SIDE.
378 ///
379 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
380 /// security of this system.
381 ///
382 /// ```
383 /// # use webauthn_rs::prelude::*;
384 ///
385 /// # let rp_id = "example.com";
386 /// # let rp_origin = Url::parse("https://idm.example.com")
387 /// # .expect("Invalid URL");
388 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
389 /// # .expect("Invalid configuration");
390 /// # let webauthn = builder.build()
391 /// # .expect("Invalid configuration");
392 ///
393 /// // you must store this user's unique id with the account. Alternatelly you can
394 /// // use an existed UUID source.
395 /// let user_unique_id = Uuid::new_v4();
396 ///
397 /// // Initiate a basic registration flow, allowing any cryptograhpic authenticator to proceed.
398 /// let (ccr, skr) = webauthn
399 /// .start_passkey_registration(
400 /// user_unique_id,
401 /// "claire",
402 /// "Claire",
403 /// None, // No other credentials are registered yet.
404 /// )
405 /// .expect("Failed to start registration.");
406 ///
407 /// // Only allow credentials from manufacturers that are trusted and part of the webauthn-rs
408 /// // strict "high quality" list.
409 /// let (ccr, skr) = webauthn
410 /// .start_passkey_registration(
411 /// Uuid::new_v4(),
412 /// "claire",
413 /// "Claire",
414 /// None, // No other credentials are registered yet.
415 /// )
416 /// .expect("Failed to start registration.");
417 /// ```
418 pub fn start_passkey_registration(
419 &self,
420 user_unique_id: &Vec<u8>,
421 user_name: &str,
422 user_display_name: &str,
423 exclude_credentials: Option<Vec<CredentialID>>,
424 ) -> WebauthnResult<(CreationChallengeResponse, PasskeyRegistration)> {
425 let attestation = AttestationConveyancePreference::None;
426 let credential_algorithms = self.algorithms.clone();
427 let require_resident_key = false;
428 // change this to crossPlatform for test use
429 // let authenticator_attachment = Some(AuthenticatorAttachment::CrossPlatform);
430 let authenticator_attachment = None;
431
432 let policy = Some(UserVerificationPolicy::Preferred);
433 let reject_passkeys = false;
434
435 let extensions = Some(RequestRegistrationExtensions {
436 cred_protect: None,
437 uvm: Some(true),
438 cred_props: Some(true),
439 min_pin_length: None,
440 hmac_create_secret: None,
441 });
442
443 self.core
444 .generate_challenge_register_options(
445 user_unique_id,
446 user_name,
447 user_display_name,
448 attestation,
449 policy,
450 exclude_credentials,
451 extensions,
452 credential_algorithms,
453 require_resident_key,
454 authenticator_attachment,
455 reject_passkeys,
456 )
457 .map(|(ccr, rs)| (ccr, PasskeyRegistration { rs }))
458 }
459
460 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
461 /// and the server provides it's paired [PasskeyRegistration]. The details of the Authenticator
462 /// based on the registration parameters are asserted.
463 ///
464 /// # Errors
465 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
466 ///
467 /// # Returns
468 ///
469 /// The returned `Passkey` must be associated to the users account, and is used for future
470 /// authentications via `start_passkey_authentication`.
471 ///
472 /// You MUST assert that the registered credential id has not previously been registered.
473 /// to any other account.
474 pub fn finish_passkey_registration(
475 &self,
476 reg: &RegisterPublicKeyCredential,
477 state: &PasskeyRegistration,
478 ) -> WebauthnResult<Passkey> {
479 self.core
480 .register_credential(reg, &state.rs, None)
481 .map(|cred| Passkey { cred })
482 }
483
484 /// Given a set of `Passkey`'s, begin an authentication of the user. This returns
485 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
486 /// The server must persist the [PasskeyAuthentication] state as it is paired to the
487 /// `RequestChallengeResponse` and required to complete the authentication.
488 ///
489 /// WARNING ⚠️ YOU MUST STORE THE [PasskeyAuthentication] VALUE SERVER SIDE.
490 ///
491 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
492 /// security of this system.
493 pub fn start_passkey_authentication(
494 &self,
495 creds: &[Passkey],
496 ) -> WebauthnResult<(RequestChallengeResponse, PasskeyAuthentication)> {
497 let extensions = None;
498 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
499 let policy = UserVerificationPolicy::Preferred;
500 let allow_backup_eligible_upgrade = true;
501
502 self.core
503 .generate_challenge_authenticate_policy(
504 creds,
505 policy,
506 extensions,
507 allow_backup_eligible_upgrade,
508 )
509 .map(|(rcr, ast)| (rcr, PasskeyAuthentication { ast }))
510 }
511
512 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [PasskeyAuthentication]
513 /// complete the authentication of the user.
514 ///
515 /// # Errors
516 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
517 ///
518 /// # Returns
519 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
520 /// process.
521 ///
522 /// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
523 ///
524 /// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
525 /// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
526 /// and you SHOULD invalidate and reject that credential as a result.
527 ///
528 /// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
529 /// valid per the above check. If you wish
530 /// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
531 /// presence of the user verification flag).
532 pub fn finish_passkey_authentication(
533 &self,
534 reg: &PublicKeyCredential,
535 state: &PasskeyAuthentication,
536 ) -> WebauthnResult<AuthenticationResult> {
537 self.core.authenticate_credential(reg, &state.ast)
538 }
539
540 /// Initiate the registration of a new security key for a user. A security key is any cryptographic
541 /// authenticator acting as a single factor of authentication to supplement a password or some
542 /// other authentication factor.
543 ///
544 /// Some examples of security keys include Yubikeys, Solokeys, and others.
545 ///
546 /// We don't recommend this over Passkeys or PasswordlessKeys, as today in Webauthn most devices
547 /// due to their construction require userVerification to be maintained for user trust. What this
548 /// means is that most users will require a password, their security key, and a pin or biometric
549 /// on the security key for a total of three factors. This adds friction to the user experience
550 /// but is required due to a consistency flaw in CTAP2.0 and newer devices. Since the user already
551 /// needs a pin or biometrics, why not just use the device as a self contained MFA?
552 ///
553 /// You MUST pair this authentication with another factor. A security key may opportunistically
554 /// allow and enforce user-verification (MFA), but this is NOT guaranteed.
555 ///
556 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
557 /// identify the user during certain client side work flows.
558 ///
559 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
560 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
561 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
562 /// MUST NOT be used as primary keys. They *may not* be present in authentication, these are only
563 /// present to allow client work flows to display human friendly identifiers.
564 ///
565 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
566 /// You *should* provide the list of credentials that are already registered to this user's account
567 /// to prevent duplicate credential registrations.
568 ///
569 /// `attestation_ca_list` contains an optional list of Root CA certificates of authenticator
570 /// manufacturers that you wish to trust. For example, if you want to only allow Yubikeys on
571 /// your site, then you can provide the Yubico Root CA in this list, to validate that all
572 /// registered devices are manufactured by Yubico.
573 ///
574 /// Extensions may ONLY be accessed if an `attestation_ca_list` is provided, else they can
575 /// ARE NOT trusted.
576 ///
577 /// # Returns
578 ///
579 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
580 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
581 /// on the server the [SecurityKeyRegistration] which contains the state of this registration
582 /// attempt and is paired to the `CreationChallengeResponse`.
583 ///
584 /// WARNING ⚠️ YOU MUST STORE THE [SecurityKeyRegistration] VALUE SERVER SIDE.
585 ///
586 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
587 /// security of this system.
588 ///
589 /// ```
590 /// # use webauthn_rs::prelude::*;
591 ///
592 /// # let rp_id = "example.com";
593 /// # let rp_origin = Url::parse("https://idm.example.com")
594 /// # .expect("Invalid URL");
595 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
596 /// # .expect("Invalid configuration");
597 /// # let webauthn = builder.build()
598 /// # .expect("Invalid configuration");
599 ///
600 /// // you must store this user's unique id with the account. Alternatelly you can
601 /// // use an existed UUID source.
602 /// let user_unique_id = Uuid::new_v4();
603 ///
604 /// // Initiate a basic registration flow, allowing any cryptograhpic authenticator to proceed.
605 /// let (ccr, skr) = webauthn
606 /// .start_securitykey_registration(
607 /// user_unique_id,
608 /// "claire",
609 /// "Claire",
610 /// None,
611 /// None,
612 /// None,
613 /// )
614 /// .expect("Failed to start registration.");
615 ///
616 /// // Initiate a basic registration flow, hinting that the device is probably roaming (i.e. a usb),
617 /// // but it could have any attachement in reality
618 /// let (ccr, skr) = webauthn
619 /// .start_securitykey_registration(
620 /// Uuid::new_v4(),
621 /// "claire",
622 /// "Claire",
623 /// None,
624 /// None,
625 /// Some(AuthenticatorAttachment::CrossPlatform),
626 /// )
627 /// .expect("Failed to start registration.");
628 ///
629 /// // Only allow credentials from manufacturers that are trusted and part of the webauthn-rs
630 /// // strict "high quality" list.
631 /// let (ccr, skr) = webauthn
632 /// .start_securitykey_registration(
633 /// Uuid::new_v4(),
634 /// "claire",
635 /// "Claire",
636 /// None,
637 /// Some(AttestationCaList::strict()),
638 /// None,
639 /// )
640 /// .expect("Failed to start registration.");
641 /// ```
642 pub fn start_securitykey_registration(
643 &self,
644 user_unique_id: &Vec<u8>,
645 user_name: &str,
646 user_display_name: &str,
647 exclude_credentials: Option<Vec<CredentialID>>,
648 attestation_ca_list: Option<AttestationCaList>,
649 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
650 ) -> WebauthnResult<(CreationChallengeResponse, SecurityKeyRegistration)> {
651 let attestation = if let Some(ca_list) = attestation_ca_list.as_ref() {
652 if ca_list.is_empty() {
653 return Err(WebauthnError::MissingAttestationCaList);
654 } else {
655 AttestationConveyancePreference::Direct
656 }
657 } else {
658 AttestationConveyancePreference::None
659 };
660 let extensions = None;
661 let credential_algorithms = self.algorithms.clone();
662 let require_resident_key = false;
663 let policy = if cfg!(feature = "danger-user-presence-only-security-keys") {
664 Some(UserVerificationPolicy::Discouraged_DO_NOT_USE)
665 } else {
666 Some(UserVerificationPolicy::Preferred)
667 };
668 let reject_passkeys = true;
669
670 self.core
671 .generate_challenge_register_options(
672 user_unique_id,
673 user_name,
674 user_display_name,
675 attestation,
676 policy,
677 exclude_credentials,
678 extensions,
679 credential_algorithms,
680 require_resident_key,
681 ui_hint_authenticator_attachment,
682 reject_passkeys,
683 )
684 .map(|(ccr, rs)| {
685 (
686 ccr,
687 SecurityKeyRegistration {
688 rs,
689 ca_list: attestation_ca_list,
690 },
691 )
692 })
693 }
694
695 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
696 /// and the server provides it's paired [SecurityKeyRegistration]. The details of the Authenticator
697 /// based on the registration parameters are asserted.
698 ///
699 /// # Errors
700 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
701 ///
702 /// # Returns
703 ///
704 /// The returned [SecurityKey] must be associated to the users account, and is used for future
705 /// authentications via [crate::Webauthn::start_securitykey_authentication].
706 ///
707 /// You MUST assert that the registered credential id has not previously been registered.
708 /// to any other account.
709 ///
710 /// # Verifying specific device models
711 /// If you wish to assert a specifc type of device model is in use, you can inspect the
712 /// PasswordlessKey `attestation()` and it's associated metadata. You can use this to check for
713 /// specific device aaguids for example.
714 ///
715 pub fn finish_securitykey_registration(
716 &self,
717 reg: &RegisterPublicKeyCredential,
718 state: &SecurityKeyRegistration,
719 ) -> WebauthnResult<SecurityKey> {
720 self.core
721 .register_credential(reg, &state.rs, state.ca_list.as_ref())
722 .map(|cred| SecurityKey { cred })
723 }
724
725 /// Given a set of `SecurityKey`'s, begin an authentication of the user. This returns
726 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
727 /// The server must persist the [SecurityKeyAuthentication] state as it is paired to the
728 /// `RequestChallengeResponse` and required to complete the authentication.
729 ///
730 /// WARNING ⚠️ YOU MUST STORE THE [SecurityKeyAuthentication] VALUE SERVER SIDE.
731 ///
732 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
733 /// security of this system.
734 pub fn start_securitykey_authentication(
735 &self,
736 creds: &[SecurityKey],
737 ) -> WebauthnResult<(RequestChallengeResponse, SecurityKeyAuthentication)> {
738 let extensions = None;
739 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
740 let allow_backup_eligible_upgrade = false;
741
742 let policy = if cfg!(feature = "danger-user-presence-only-security-keys") {
743 UserVerificationPolicy::Discouraged_DO_NOT_USE
744 } else {
745 UserVerificationPolicy::Preferred
746 };
747
748 self.core
749 .generate_challenge_authenticate_policy(
750 creds,
751 policy,
752 extensions,
753 allow_backup_eligible_upgrade,
754 )
755 .map(|(rcr, ast)| (rcr, SecurityKeyAuthentication { ast }))
756 }
757
758 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [SecurityKeyAuthentication]
759 /// complete the authentication of the user.
760 ///
761 /// # Errors
762 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
763 ///
764 /// # Returns
765 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
766 /// process.
767 ///
768 /// You should use `SecurityKey::update_credential` on the returned [AuthenticationResult] and
769 /// ensure it is persisted.
770 pub fn finish_securitykey_authentication(
771 &self,
772 reg: &PublicKeyCredential,
773 state: &SecurityKeyAuthentication,
774 ) -> WebauthnResult<AuthenticationResult> {
775 self.core.authenticate_credential(reg, &state.ast)
776 }
777}
778
779#[cfg(feature = "preview-features")]
780impl Webauthn {
781 /// Initiate the registration of a new passwordless key for a user. A passwordless key is a
782 /// cryptographic authenticator that is a self-contained multifactor authenticator. This means
783 /// that the device (such as a yubikey) verifies the user is who they say they are via a PIN,
784 /// biometric or other factor. Only if this verification passes, is the signature released
785 /// and provided.
786 ///
787 /// As a result, the server *only* requires this passwordless key to authenticator the user
788 /// and assert their identity. Because of this reliance on the authenticator, attestation of
789 /// the authenticator and it's properties is strongly recommended.
790 ///
791 /// The primary difference to a passkey, is that these credentials *can not* 'roam' between multiple
792 /// devices, and must be bound to a single authenticator. This precludes the use of certain types
793 /// of authenticators (such as Apple's Passkeys as these are always synced).
794 ///
795 /// Additionally, these credentials can have an attestation or certificate of authenticity
796 /// validated to give you stronger assertions in the types of devices in use.
797 ///
798 /// You *should* recommend to the user to register multiple passwordless keys to their account on
799 /// seperate devices so that they have fall back authentication.
800 ///
801 /// You *should* have a workflow that allows a user to register new devices without a need to register
802 /// other factors. For example, allow a QR code that can be scanned from a phone, or a one-time
803 /// link that can be copied to the device.
804 ///
805 /// You *must* have a recovery workflow in case all devices are lost or destroyed.
806 ///
807 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
808 /// identify the user during certain client side work flows.
809 ///
810 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
811 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
812 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
813 /// MUST NOT be used as primary keys. They *may not* be present in authentication, these are only
814 /// present to allow client work flows to display human friendly identifiers.
815 ///
816 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
817 /// You *should* provide the list of credentials that are already registered to this user's account
818 /// to prevent duplicate credential registrations.
819 ///
820 /// `attestation_ca_list` contains an optional list of Root CA certificates of authenticator
821 /// manufacturers that you wish to trust. For example, if you want to only allow Yubikeys on
822 /// your site, then you can provide the Yubico Root CA in this list, to validate that all
823 /// registered devices are manufactured by Yubico.
824 ///
825 /// `ui_hint_authenticator_attachment` provides a UX/UI hint to the browser about the types
826 /// of credentials that could be used in this registration. If set to `None` all authenticator
827 /// attachement classes are valid. If set to Platform, only authenticators that are part of the
828 /// device are used such as a TPM or TouchId. If set to Cross-Platform, only devices that are
829 /// removable from the device can be used such as yubikeys.
830 ///
831 /// Currently, extensions are *not* possible to request due to webauthn not properly supporting
832 /// them in broader contexts.
833 ///
834 /// # Returns
835 ///
836 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
837 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
838 /// on the server the `PasswordlessKeyRegistration` which contains the state of this registration
839 /// attempt and is paired to the `CreationChallengeResponse`.
840 ///
841 /// WARNING ⚠️ YOU MUST STORE THE [PasswordlessKeyRegistration] VALUE SERVER SIDE.
842 ///
843 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
844 /// security of this system.
845 ///
846 /// ```
847 /// # use webauthn_rs::prelude::*;
848 ///
849 /// # let rp_id = "example.com";
850 /// # let rp_origin = Url::parse("https://idm.example.com")
851 /// # .expect("Invalid url");
852 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
853 /// # .expect("Invalid configuration");
854 /// # let webauthn = builder.build()
855 /// # .expect("Invalid configuration");
856 ///
857 /// // you must store this user's unique id with the account. Alternatelly you can
858 /// // use an existed UUID source.
859 /// let user_unique_id = Uuid::new_v4();
860 ///
861 /// // Initiate a basic registration flow, allowing any cryptograhpic authenticator to proceed.
862 /// // Hint (but do not enforce) that we prefer this to be a token/key like a yubikey.
863 /// // To enforce this you can validate the properties of the returned device aaguid.
864 /// let (ccr, skr) = webauthn
865 /// .start_passwordlesskey_registration(
866 /// user_unique_id,
867 /// "claire",
868 /// "Claire",
869 /// None,
870 /// AttestationCaList::strict(),
871 /// Some(AuthenticatorAttachment::CrossPlatform),
872 /// )
873 /// .expect("Failed to start registration.");
874 ///
875 /// // Only allow credentials from manufacturers that are trusted and part of the webauthn-rs
876 /// // strict "high quality" list.
877 /// // Hint (but do not enforce) that we prefer this to be a device like TouchID.
878 /// // To enforce this you can validate the attestation ca used along with the returned device aaguid
879 /// let (ccr, skr) = webauthn
880 /// .start_passwordlesskey_registration(
881 /// Uuid::new_v4(),
882 /// "claire",
883 /// "Claire",
884 /// None,
885 /// AttestationCaList::strict(),
886 /// Some(AuthenticatorAttachment::Platform),
887 /// )
888 /// .expect("Failed to start registration.");
889 /// ```
890 pub fn start_passwordlesskey_registration(
891 &self,
892 user_unique_id: Uuid,
893 user_name: &str,
894 user_display_name: &str,
895 exclude_credentials: Option<Vec<CredentialID>>,
896 attestation_ca_list: AttestationCaList,
897 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
898 // extensions
899 ) -> WebauthnResult<(CreationChallengeResponse, PasswordlessKeyRegistration)> {
900 let attestation = AttestationConveyancePreference::Direct;
901 if attestation_ca_list.is_empty() {
902 return Err(WebauthnError::MissingAttestationCaList);
903 }
904
905 let credential_algorithms = self.algorithms.clone();
906 let require_resident_key = false;
907 let policy = Some(UserVerificationPolicy::Required);
908 let reject_passkeys = true;
909
910 let extensions = Some(RequestRegistrationExtensions {
911 cred_protect: None,
912 uvm: Some(true),
913 cred_props: Some(true),
914 min_pin_length: Some(true),
915 hmac_create_secret: None,
916 });
917
918 self.core
919 .generate_challenge_register_options(
920 user_unique_id.as_bytes(),
921 user_name,
922 user_display_name,
923 attestation,
924 policy,
925 exclude_credentials,
926 extensions,
927 credential_algorithms,
928 require_resident_key,
929 ui_hint_authenticator_attachment,
930 reject_passkeys,
931 )
932 .map(|(ccr, rs)| {
933 (
934 ccr,
935 PasswordlessKeyRegistration {
936 rs,
937 ca_list: attestation_ca_list,
938 },
939 )
940 })
941 }
942
943 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
944 /// and the server provides it's paired [PasswordlessKeyRegistration]. The details of the Authenticator
945 /// based on the registration parameters are asserted.
946 ///
947 /// # Errors
948 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
949 ///
950 /// # Returns
951 /// The returned [PasswordlessKey] must be associated to the users account, and is used for future
952 /// authentications via [crate::Webauthn::start_passwordlesskey_authentication].
953 ///
954 /// # Verifying specific device models
955 /// If you wish to assert a specifc type of device model is in use, you can inspect the
956 /// PasswordlessKey `attestation()` and it's associated metadata. You can use this to check for
957 /// specific device aaguids for example.
958 ///
959 pub fn finish_passwordlesskey_registration(
960 &self,
961 reg: &RegisterPublicKeyCredential,
962 state: &PasswordlessKeyRegistration,
963 ) -> WebauthnResult<PasswordlessKey> {
964 self.core
965 .register_credential(reg, &state.rs, Some(&state.ca_list))
966 .map(|cred| PasswordlessKey { cred })
967 }
968
969 /// Given a set of `PasswordlessKey`'s, begin an authentication of the user. This returns
970 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
971 /// The server must persist the [PasswordlessKeyAuthentication] state as it is paired to the
972 /// `RequestChallengeResponse` and required to complete the authentication.
973 ///
974 /// WARNING ⚠️ YOU MUST STORE THE [PasswordlessKeyAuthentication] VALUE SERVER SIDE.
975 ///
976 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
977 /// security of this system.
978 pub fn start_passwordlesskey_authentication(
979 &self,
980 creds: &[PasswordlessKey],
981 ) -> WebauthnResult<(RequestChallengeResponse, PasswordlessKeyAuthentication)> {
982 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
983
984 let extensions = Some(RequestAuthenticationExtensions {
985 appid: None,
986 uvm: Some(true),
987 hmac_get_secret: None,
988 });
989
990 let policy = UserVerificationPolicy::Required;
991 let allow_backup_eligible_upgrade = false;
992
993 self.core
994 .generate_challenge_authenticate_policy(
995 creds,
996 policy,
997 extensions,
998 allow_backup_eligible_upgrade,
999 )
1000 .map(|(rcr, ast)| (rcr, PasswordlessKeyAuthentication { ast }))
1001 }
1002
1003 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [PasswordlessKeyAuthentication]
1004 /// complete the authentication of the user. This asserts that user verification must have been correctly
1005 /// performed allowing you to trust this as a MFA interfaction.
1006 ///
1007 /// # Errors
1008 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
1009 ///
1010 /// # Returns
1011 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
1012 /// process.
1013 ///
1014 /// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
1015 ///
1016 /// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
1017 /// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
1018 /// and you SHOULD invalidate and reject that credential as a result.
1019 ///
1020 /// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
1021 /// valid per the above check. If you wish
1022 /// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
1023 /// user verification flag).
1024 ///
1025 /// In *some* cases, you *may* be able to identify the user by examinin
1026 pub fn finish_passwordlesskey_authentication(
1027 &self,
1028 reg: &PublicKeyCredential,
1029 state: &PasswordlessKeyAuthentication,
1030 ) -> WebauthnResult<AuthenticationResult> {
1031 self.core.authenticate_credential(reg, &state.ast)
1032 }
1033
1034 /// WIP DO NOT USE
1035 pub fn start_discoverable_authentication(
1036 &self,
1037 ) -> WebauthnResult<(RequestChallengeResponse, DiscoverableAuthentication)> {
1038 let policy = UserVerificationPolicy::Required;
1039 let extensions = Some(RequestAuthenticationExtensions {
1040 appid: None,
1041 uvm: Some(true),
1042 hmac_get_secret: None,
1043 });
1044
1045 self.core
1046 .generate_challenge_authenticate_discoverable(policy, extensions)
1047 .map(|(rcr, ast)| (rcr, DiscoverableAuthentication { ast }))
1048 }
1049
1050 /// WIP DO NOT USE
1051 pub fn identify_discoverable_authentication<'a>(
1052 &'_ self,
1053 reg: &'a PublicKeyCredential,
1054 ) -> WebauthnResult<(Uuid, &'a [u8])> {
1055 let cred_id = reg.get_credential_id();
1056 reg.get_user_unique_id()
1057 .and_then(|b| Uuid::from_slice(b).ok())
1058 .map(|u| (u, cred_id))
1059 .ok_or(WebauthnError::InvalidUserUniqueId)
1060 }
1061
1062 /// WIP DO NOT USE
1063 pub fn finish_discoverable_authentication(
1064 &self,
1065 reg: &PublicKeyCredential,
1066 mut state: DiscoverableAuthentication,
1067 creds: &[DiscoverableKey],
1068 ) -> WebauthnResult<AuthenticationResult> {
1069 let creds = creds.iter().map(|dk| dk.cred.clone()).collect();
1070 state.ast.set_allowed_credentials(creds);
1071 self.core.authenticate_credential(reg, &state.ast)
1072 }
1073}
1074
1075#[cfg(feature = "resident-key-support")]
1076impl Webauthn {
1077 /// TODO
1078 pub fn start_devicekey_registration(
1079 &self,
1080 user_unique_id: Uuid,
1081 user_name: &str,
1082 user_display_name: &str,
1083 exclude_credentials: Option<Vec<CredentialID>>,
1084 attestation_ca_list: AttestationCaList,
1085 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
1086 ) -> WebauthnResult<(CreationChallengeResponse, DeviceKeyRegistration)> {
1087 if attestation_ca_list.is_empty() {
1088 return Err(WebauthnError::MissingAttestationCaList);
1089 }
1090
1091 let attestation = AttestationConveyancePreference::Direct;
1092 let credential_algorithms = self.algorithms.clone();
1093 let require_resident_key = true;
1094 let policy = Some(UserVerificationPolicy::Required);
1095 let reject_passkeys = true;
1096
1097 // credProtect
1098 let extensions = Some(RequestRegistrationExtensions {
1099 cred_protect: Some(CredProtect {
1100 // Since this will contain PII, we need to enforce this.
1101 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
1102 // If set to true, causes many authenticators to shit the bed. As a result,
1103 // during the registration, we check if the aaguid is credProtect viable and
1104 // then enforce it there.
1105 enforce_credential_protection_policy: Some(false),
1106 }),
1107 // https://www.w3.org/TR/webauthn-2/#sctn-uvm-extension
1108 uvm: Some(true),
1109 cred_props: Some(true),
1110 // https://fidoalliance.org/specs/fido-v2.1-rd-20210309/fido-client-to-authenticator-protocol-v2.1-rd-20210309.html#sctn-minpinlength-extension
1111 min_pin_length: Some(true),
1112 hmac_create_secret: Some(true),
1113 });
1114
1115 self.core
1116 .generate_challenge_register_options(
1117 user_unique_id.as_bytes(),
1118 user_name,
1119 user_display_name,
1120 attestation,
1121 policy,
1122 exclude_credentials,
1123 extensions,
1124 credential_algorithms,
1125 require_resident_key,
1126 ui_hint_authenticator_attachment,
1127 reject_passkeys,
1128 )
1129 .map(|(ccr, rs)| {
1130 (
1131 ccr,
1132 DeviceKeyRegistration {
1133 rs,
1134 ca_list: attestation_ca_list,
1135 },
1136 )
1137 })
1138 }
1139
1140 /// TODO
1141 pub fn finish_devicekey_registration(
1142 &self,
1143 reg: &RegisterPublicKeyCredential,
1144 state: &DeviceKeyRegistration,
1145 ) -> WebauthnResult<DeviceKey> {
1146 let cred = self
1147 .core
1148 .register_credential(reg, &state.rs, Some(&state.ca_list))?;
1149
1150 trace!("finish devicekey -> {:?}", cred);
1151
1152 // cred protect ignored :(
1153 // Is the pin long enough?
1154 // Is it rk?
1155 // I guess we'll never know ...
1156
1157 // Is it an approved cred / aaguid?
1158
1159 Ok(DeviceKey { cred })
1160 }
1161
1162 /// TODO
1163 pub fn start_devicekey_authentication(
1164 &self,
1165 creds: &[DeviceKey],
1166 ) -> WebauthnResult<(RequestChallengeResponse, DeviceKeyAuthentication)> {
1167 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
1168 let extensions = Some(RequestAuthenticationExtensions {
1169 appid: None,
1170 uvm: Some(true),
1171 hmac_get_secret: None,
1172 });
1173
1174 let policy = UserVerificationPolicy::Required;
1175 let allow_backup_eligible_upgrade = false;
1176
1177 self.core
1178 .generate_challenge_authenticate_policy(
1179 creds,
1180 policy,
1181 extensions,
1182 allow_backup_eligible_upgrade,
1183 )
1184 .map(|(rcr, ast)| (rcr, DeviceKeyAuthentication { ast }))
1185 }
1186
1187 /// TODO
1188 pub fn finish_devicekey_authentication(
1189 &self,
1190 reg: &PublicKeyCredential,
1191 state: &DeviceKeyAuthentication,
1192 ) -> WebauthnResult<AuthenticationResult> {
1193 self.core.authenticate_credential(reg, &state.ast)
1194 }
1195}