webauthn_rs/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 to replace passwords with strong self contained multifactor
17//! authentication, you should use our passkey flow.
18//!
19//! Remember, no other authentication factors are needed. A passkey combines inbuilt user
20//! verification (pin, biometrics, etc) with a hardware cryptographic authenticator.
21//!
22//! ```
23//! use webauthn_rs::prelude::*;
24//!
25//! let rp_id = "example.com";
26//! let rp_origin = Url::parse("https://idm.example.com")
27//! .expect("Invalid URL");
28//! let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
29//! .expect("Invalid configuration");
30//! let webauthn = builder.build()
31//! .expect("Invalid configuration");
32//!
33//! // Initiate a basic registration flow to enroll a cryptographic authenticator
34//! let (ccr, skr) = webauthn
35//! .start_passkey_registration(
36//! Uuid::new_v4(),
37//! "claire",
38//! "Claire",
39//! None,
40//! )
41//! .expect("Failed to start registration.");
42//! ```
43//!
44//! After this point you then need to use `finish_passkey_registration`, followed by
45//! `start_passkey_authentication` and `finish_passkey_authentication`
46//!
47//! # Tutorial
48//!
49//! Tutorials and examples on how to use this library in your website project is on the project
50//! github <https://github.com/kanidm/webauthn-rs/tree/master/tutorial>
51//!
52//! # What is a "Passkey"?
53//!
54//! Like all good things - "it depends". Mostly it depends who you ask, and at what time they adopted
55//! the terminology. There are at least four definitions that we are aware of. A passkey is:
56//!
57//! * any possible webauthn authenticator - security key, tpm, touch id, etc
58//! * a platform authenticator - built into a device such as touch id, tpm, etc. This excludes security keys.
59//! * a synchronised credential - backed by a cloud keychain like Apple iCloud or Bitwarden.
60//! * a resident key (RK) - a stored, discoverable credential allowing usernameless flows
61//!
62//! Each of these definitions have different pros and cons, and different usability implications. For
63//! example, passkeys as resident keys means you can accidentally brick many ctap2.0 devices by exhausting
64//! their storage (forcing them to require a full reset, wiping all credentials). Passkeys as platform
65//! authenticators means only certain laptops and phones can use them.
66//! Passkeys as synced credentials means only certain devices with specific browser combinations
67//! or extensions can use passkeys.
68//!
69//! In this library we chose to define passkeys as "any possible authenticator". This definition
70//! aligns with the W3C WebAuthn Working Group's mission of enabling strong authentication without
71//! compromising end-user experience, regardless of their authenticator's modality.
72//!
73//! We may look to enable (but not require) usernameless flows in the future for on devices which
74//! opportunistically create resident keys (such as Apple's iCloud Keychain). However, the platform
75//! and browser user experience is not good enough to justify enabling these flows at present.
76//!
77//! # Features
78//!
79//! This library supports some optional features that you may wish to use. These are all
80//! disabled by default as they have risks associated that you need to be aware of as an
81//! authentication provider.
82//!
83//! ## Allow Serialising Registration and Authentication State
84//!
85//! During a webauthn registration or authentication ceremony, a random challenge is produced and
86//! provided to the client. The full content of what is needed for the server to validate this
87//! challenge is stored in the associated registration or authentication state types. This value
88//! *MUST* be persisted on the server. If you store this in a cookie or some other form of client
89//! side stored value, the client can replay a previous authentication state and signature without
90//! possession of, or interaction with the authenticator, bypassing pretty much all of the security guarantees
91//! of webauthn. Because of this risk by default these states are *not* allowed to be serialised
92//! which prevents them from accidentally being placed into a cookie.
93//!
94//! However there are some *safe* cases of serialising these values. This includes serialising to
95//! a database, or using a cookie "memory store" where the client side cookie is a key into a server-side
96//! map or similar. Any of these prevent the replay attack threat.
97//!
98//! An alternate but "less good" method to mitigate replay attacks is to associate a very short
99//! expiry window to the cookie if you need full client side state, but this may still allow some
100//! forms of real time replay attacks to occur. We do not recommend this.
101//!
102//! Enabling the feature `danger-allow-state-serialisation` allows you to re-enable serialisation
103//! of these types, provided you accept and understand the handling risks associated.
104//!
105//! ## Credential Internals and Type Changes
106//!
107//! By default the type wrappers around the keys are opaque. However in some cases you
108//! may wish to migrate a key between types (security key to passkey, attested_passkey to passkey)
109//! for example. Alternately, you may wish to access the internals of a credential to implement
110//! an alternate serialisation or storage mechanism. In these cases you can access the underlying
111//! [Credential] type via Into and From by enabling the feature `danger-credential-internals`. The
112//! [Credential] type is exposed via the [prelude] when this feature is enabled.
113//!
114//! However, you should be aware that manipulating the internals of a [Credential] may affect the usage
115//! any security properties of that [Credential] in certain cases. You should be careful when
116//! enabling this feature that you do not change internal [Credential] values without understanding
117//! the implications.
118//!
119//! ## User-Presence only SecurityKeys
120//!
121//! By default, SecurityKeys will opportunistically enforce User Verification (Such as a PIN or
122//! Biometric). This prevent UV bypass attacks and allows upgrade of the SecurityKey to a Passkey.
123//!
124//! Enabling the feature `danger-user-presence-only-security-keys` changes these keys to prevent
125//! User Verification if possible. However, newer keys will confusingly force a User Verification
126//! on registration, but will then not prompt for this during usage. Some user surveys have shown
127//! this to confuse users to why the UV is not requested, and it can lower trust in these tokens
128//! when they are elevated to be self-contained MFA (passkey) as the user believes these UV prompts to be
129//! unreliable and not verified correctly - in other words it trains users to believe that these
130//! prompts do nothing and have no effect. In these cases you MUST communicate to the user that
131//! the UV *may* occur on registration and then will not occur again, and that is *by design*.
132//!
133//! If in doubt, do not enable this feature.
134//!
135//! ## 'Google Passkey stored in Google Password Manager' Specific Workarounds
136//!
137//! Android (with GMS Core) has a number of issues in the dialogs they present to users for authenticator
138//! selection. Instead of allowing the user to choose what kind of passkey they want to
139//! use and create (security key, device screen unlock or 'Google Passkey stored in Google Password
140//! Manager'), Android expects every website to implement their own selection UI's ahead of time
141//! so that the RP sends the specific options to trigger each of these flows. This adds complexity
142//! to RP implementations and a large surface area for mistakes, confusion and inconsistent
143//! workflows.
144//!
145//! By default for maximum compatibility and the most accessible user experience this library
146//! sends the options to trigger security keys and the device screen unlock as choices. As RPs
147//! must provide methods to allow users to enroll multiple independent devices, we consider that
148//! this is a reasonable trade since we allow the widest possible sets of authenticators and Android
149//! devices (including devices without GMS Core) to operate.
150//!
151//! To enable the registration call that triggers the 'Google Passkey stored in Google Password
152//! Manager' key flow, you can enable the feature `workaround-google-passkey-specific-issues`. This
153//! flow can only be used on Android devices with GMS Core, and you must have a way to detect this
154//! ahead of time. This flow must NEVER be triggered on any other class of device or browser.
155//!
156//! ## Conditional UI / Username Autocompletion
157//!
158//! Some passkey devices will create a resident key opportunistically during registration. These
159//! keys in some cases allow the device to autocomplete the username *and* authenticate in a
160//! single step.
161//!
162//! Not all devices support this, nor do all browsers. As a result you must always support the
163//! full passkey flow, and conditional-ui is only opportunistic in itself.
164//!
165//! User testing has shown that these conditional UI flows in most browsers are hard to activate
166//! and may be confusing to users, as they attempt to force users to use caBLE/hybrid. We don't
167//! recommend conditional UI as a result.
168//!
169//! If you still wish to experiment with conditional UI, then enabling the feature `conditional-ui`
170//! will expose the needed methods to create conditional-ui mediated challenges and expose the
171//! functions to extract the users uuid from the authentication request.
172
173#![cfg_attr(docsrs, feature(doc_cfg))]
174#![deny(warnings)]
175#![warn(unused_extern_crates)]
176#![warn(missing_docs)]
177#![deny(clippy::todo)]
178#![deny(clippy::unimplemented)]
179#![deny(clippy::unwrap_used)]
180#![deny(clippy::expect_used)]
181#![deny(clippy::panic)]
182#![deny(clippy::unreachable)]
183#![deny(clippy::await_holding_lock)]
184#![deny(clippy::needless_pass_by_value)]
185#![deny(clippy::trivially_copy_pass_by_ref)]
186
187#[macro_use]
188extern crate tracing;
189
190mod interface;
191
192use std::time::Duration;
193use url::Url;
194use uuid::Uuid;
195use webauthn_rs_core::error::{WebauthnError, WebauthnResult};
196use webauthn_rs_core::proto::*;
197use webauthn_rs_core::WebauthnCore;
198
199use crate::interface::*;
200
201/// Fake `CredentialID` generator. See [WebauthnFakeCredentialGenerator](fake::WebauthnFakeCredentialGenerator) for more details.
202pub mod fake {
203 pub use webauthn_rs_core::fake::*;
204}
205
206/// A prelude of types that are used by `Webauthn`
207pub mod prelude {
208 pub use crate::interface::*;
209 pub use crate::{Webauthn, WebauthnBuilder};
210 pub use base64urlsafedata::Base64UrlSafeData;
211 pub use url::Url;
212 pub use uuid::Uuid;
213 pub use webauthn_rs_core::error::{WebauthnError, WebauthnResult};
214 #[cfg(feature = "danger-credential-internals")]
215 pub use webauthn_rs_core::proto::Credential;
216 pub use webauthn_rs_core::proto::{
217 AttestationCa, AttestationCaList, AttestationCaListBuilder, AttestationFormat,
218 AuthenticatorAttachment,
219 };
220 pub use webauthn_rs_core::proto::{
221 AttestationMetadata, AuthenticationResult, AuthenticationState, CreationChallengeResponse,
222 CredentialID, ParsedAttestation, ParsedAttestationData, PublicKeyCredential,
223 RegisterPublicKeyCredential, RequestChallengeResponse,
224 };
225 pub use webauthn_rs_core::proto::{
226 COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, COSEKeyTypeId, COSEOKPKey, COSERSAKey,
227 ECDSACurve, EDDSACurve,
228 };
229}
230
231/// The [Webauthn recommended authenticator interaction timeout][0].
232///
233/// [0]: https://www.w3.org/TR/webauthn-3/#ref-for-dom-publickeycredentialcreationoptions-timeout
234pub const DEFAULT_AUTHENTICATOR_TIMEOUT: Duration = Duration::from_secs(300);
235
236/// A constructor for a new [Webauthn] instance. This accepts and configures a number of site-wide
237/// properties that apply to all webauthn operations of this service.
238#[derive(Debug)]
239pub struct WebauthnBuilder<'a> {
240 rp_name: Option<&'a str>,
241 rp_id: &'a str,
242 allowed_origins: Vec<Url>,
243 allow_subdomains: bool,
244 allow_any_port: bool,
245 timeout: Duration,
246 algorithms: Vec<COSEAlgorithm>,
247 user_presence_only_security_keys: bool,
248}
249
250impl<'a> WebauthnBuilder<'a> {
251 /// Initiate a new builder. This takes the relying party id and relying party origin.
252 ///
253 /// # Safety
254 ///
255 /// rp_id is what Credentials (Authenticators) bind themselves to - rp_id can NOT be changed
256 /// without breaking all of your users' associated credentials in the future!
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use webauthn_rs::prelude::*;
262 ///
263 /// let rp_id = "example.com";
264 /// let rp_origin = Url::parse("https://idm.example.com")
265 /// .expect("Invalid URL");
266 /// let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
267 /// .expect("Invalid configuration");
268 /// ```
269 ///
270 /// # Errors
271 ///
272 /// rp_id *must* be an effective domain of rp_origin. This means that if you are hosting
273 /// `https://idm.example.com`, rp_id must be `idm.example.com`, `example.com` or `com`.
274 ///
275 /// ```
276 /// use webauthn_rs::prelude::*;
277 ///
278 /// let rp_id = "example.com";
279 /// let rp_origin = Url::parse("https://idm.different.com")
280 /// .expect("Invalid URL");
281 /// assert!(WebauthnBuilder::new(rp_id, &rp_origin).is_err());
282 /// ```
283 pub fn new(rp_id: &'a str, rp_origin: &'a Url) -> WebauthnResult<Self> {
284 // Check the rp_name and rp_id.
285 let valid = rp_origin
286 .domain()
287 .map(|effective_domain| {
288 // We need to prepend the '.' here to ensure that myexample.com != example.com,
289 // rather than just ends with.
290 effective_domain.ends_with(&format!(".{rp_id}")) || effective_domain == rp_id
291 })
292 .unwrap_or(false);
293
294 if valid {
295 Ok(WebauthnBuilder {
296 rp_name: None,
297 rp_id,
298 allowed_origins: vec![rp_origin.to_owned()],
299 allow_subdomains: false,
300 allow_any_port: false,
301 timeout: DEFAULT_AUTHENTICATOR_TIMEOUT,
302 algorithms: COSEAlgorithm::secure_algs(),
303 user_presence_only_security_keys: false,
304 })
305 } else {
306 error!("rp_id is not an effective_domain of rp_origin");
307 Err(WebauthnError::Configuration)
308 }
309 }
310
311 /// Setting this flag to true allows subdomains to be considered valid in Webauthn operations.
312 /// An example of this is if you wish for `https://au.idm.example.com` to be a valid domain
313 /// for Webauthn when the configuration is `https://idm.example.com`. Generally this occurs
314 /// when you have a centralised IDM system, but location specific systems with DNS based
315 /// redirection or routing.
316 ///
317 /// If in doubt, do NOT change this value. Defaults to "false".
318 pub fn allow_subdomains(mut self, allow: bool) -> Self {
319 self.allow_subdomains = allow;
320 self
321 }
322
323 /// Setting this flag skips port checks on origin matches
324 pub fn allow_any_port(mut self, allow: bool) -> Self {
325 self.allow_any_port = allow;
326 self
327 }
328
329 /// Set extra origins to be considered valid in Webauthn operations. A common example of this is
330 /// enabling use with iOS or Android native "webauthn-like" APIs, which return different
331 /// app-specific origins than a web browser would.
332 pub fn append_allowed_origin(mut self, origin: &Url) -> Self {
333 self.allowed_origins.push(origin.to_owned());
334 self
335 }
336
337 /// Set the timeout value to use for credential creation and authentication challenges.
338 ///
339 /// If not set, this defaults to [`DEFAULT_AUTHENTICATOR_TIMEOUT`], per
340 /// [Webauthn Level 3 recommendations][0].
341 ///
342 /// Short timeouts are difficult for some users to meet, particularly if
343 /// they need to physically locate and plug in their authenticator, use a
344 /// [hybrid authenticator][1], need to enter a PIN and/or use a fingerprint
345 /// reader.
346 ///
347 /// This may take even longer for users with cognitive, motor, mobility
348 /// and/or vision impairments. Even a minor skin condition can make it hard
349 /// to use a fingerprint reader!
350 ///
351 /// Consult the [Webauthn specification's accessibility considerations][2],
352 /// [WCAG 2.1's "Enough time" guideline][3] and
353 /// ["Timeouts" success criterion][4] when choosing a value, particularly if
354 /// it is *shorter* than the default.
355 ///
356 /// [0]: https://www.w3.org/TR/webauthn-3/#ref-for-dom-publickeycredentialcreationoptions-timeout
357 /// [1]: https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-hybrid
358 /// [2]: https://www.w3.org/TR/webauthn-3/#sctn-accessiblility-considerations
359 /// [3]: https://www.w3.org/TR/WCAG21/#enough-time
360 /// [4]: https://www.w3.org/WAI/WCAG21/Understanding/timeouts.html
361 pub fn timeout(mut self, timeout: Duration) -> Self {
362 self.timeout = timeout;
363 self
364 }
365
366 /// Set the relying party name. This may be shown to the user. This value can be changed in
367 /// the future without affecting credentials that have already registered.
368 ///
369 /// If not set, defaults to rp_id.
370 pub fn rp_name(mut self, rp_name: &'a str) -> Self {
371 self.rp_name = Some(rp_name);
372 self
373 }
374
375 /// Enable security keys to only require user presence, rather than enforcing
376 /// their user-verification state.
377 ///
378 /// *requires feature danger-user-presence-only-security-keys*
379 #[cfg(feature = "danger-user-presence-only-security-keys")]
380 pub fn danger_set_user_presence_only_security_keys(mut self, enable: bool) -> Self {
381 self.user_presence_only_security_keys = enable;
382 self
383 }
384
385 /// Complete the construction of the [Webauthn] instance. If an invalid configuration setting
386 /// is found, an Error will be returned.
387 ///
388 /// # Examples
389 ///
390 /// ```
391 /// use webauthn_rs::prelude::*;
392 ///
393 /// let rp_id = "example.com";
394 /// let rp_origin = Url::parse("https://idm.example.com")
395 /// .expect("Invalid URL");
396 /// let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
397 /// .expect("Invalid configuration");
398 /// let webauthn = builder.build()
399 /// .expect("Invalid configuration");
400 /// ```
401 pub fn build(self) -> WebauthnResult<Webauthn> {
402 Ok(Webauthn {
403 core: WebauthnCore::new_unsafe_experts_only(
404 self.rp_name.unwrap_or(self.rp_id),
405 self.rp_id,
406 self.allowed_origins,
407 self.timeout,
408 Some(self.allow_subdomains),
409 Some(self.allow_any_port),
410 ),
411 algorithms: self.algorithms,
412 user_presence_only_security_keys: self.user_presence_only_security_keys,
413 })
414 }
415}
416
417/// An instance of a Webauthn site. This is the main point of interaction for registering and
418/// authenticating credentials for users. Depending on your needs, you'll want to allow users
419/// to register and authenticate with different kinds of authenticators.
420///
421/// __I just want to replace passwords with strong cryptographic authentication, and I don't have other requirements__
422///
423/// > You should use [`start_passkey_registration`](Webauthn::start_passkey_registration)
424///
425///
426/// __I want to replace passwords with strong multi-factor cryptographic authentication, limited to
427/// a known set of controlled and trusted authenticator types__
428///
429/// This type requires `attestation` enabled as the current form of the Attestation CA List
430/// may change in the future.
431///
432/// > You should use [`start_attested_passkey_registration`](Webauthn::start_attested_passkey_registration)
433///
434///
435/// __I want users to have their identities stored on their devices, and for them to authenticate with
436/// strong multi-factor cryptographic authentication limited to a known set of trusted authenticator types__
437///
438/// This authenticator type consumes limited storage space on users' authenticators, and may result in failures or device
439/// bricking.
440/// You **MUST** only use it in tightly controlled environments where you supply devices to your
441/// users.
442///
443/// > You should use [`start_attested_resident_key_registration`](Webauthn::start_attested_resident_key_registration) (still in development, requires `resident-key-support` feature)
444///
445///
446/// __I want a security token along with an external password to create multi-factor authentication__
447///
448/// If possible, consider [`start_passkey_registration`](Webauthn::start_passkey_registration) OR
449/// [`start_attested_passkey_registration`](Webauthn::start_attested_passkey_registration)
450/// instead - it's likely to provide a better user experience over security keys as MFA!
451///
452/// > If you really want a security key, you should use [`start_securitykey_registration`](Webauthn::start_securitykey_registration)
453///
454#[derive(Debug, Clone)]
455pub struct Webauthn {
456 core: WebauthnCore,
457 algorithms: Vec<COSEAlgorithm>,
458 user_presence_only_security_keys: bool,
459}
460
461impl Webauthn {
462 /// Get the currently configured origins
463 pub fn get_allowed_origins(&self) -> &[Url] {
464 self.core.get_allowed_origins()
465 }
466
467 /// Initiate the registration of a new passkey for a user. A passkey is any cryptographic
468 /// authenticator which internally verifies the user's identity. As these are self contained
469 /// multifactor authenticators, they are far stronger than a password or email-reset link. Due
470 /// to how webauthn is designed these authentications are unable to be phished.
471 ///
472 /// Some examples of passkeys include Yubikeys, TouchID, FaceID, Windows Hello and others.
473 ///
474 /// The keys *may* exist and 'roam' between multiple devices. For example, Apple allows Passkeys
475 /// to sync between devices owned by the same Apple account. This can affect your risk model
476 /// related to these credentials, but generally in most cases passkeys are better than passwords!
477 ///
478 /// You *should* NOT pair this authentication with any other factor. A passkey will always
479 /// enforce user-verification (MFA) removing the need for other factors.
480 ///
481 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
482 /// identify the user during certain client side work flows.
483 ///
484 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
485 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
486 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
487 /// MUST NOT be used as primary keys. They *are not* present in authentication, these are only
488 /// present to allow client facing work flows to display human friendly identifiers.
489 ///
490 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
491 /// You *should* provide the list of credentials that are already registered to this user's account
492 /// to prevent duplicate credential registrations. These credentials *can* be from different
493 /// authenticator classes since we only require the `CredentialID`
494 ///
495 /// # Returns
496 ///
497 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
498 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
499 /// on the server the `PasskeyRegistration` which contains the state of this registration
500 /// attempt and is paired to the `CreationChallengeResponse`.
501 ///
502 /// Finally you need to call [`finish_passkey_registration`](Webauthn::finish_passkey_registration)
503 /// to complete the registration.
504 ///
505 /// WARNING ⚠️ YOU MUST STORE THE [PasskeyRegistration] VALUE SERVER SIDE.
506 ///
507 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
508 /// security of this system.
509 ///
510 /// ```
511 /// # use webauthn_rs::prelude::*;
512 /// # let rp_id = "example.com";
513 /// # let rp_origin = Url::parse("https://idm.example.com")
514 /// # .expect("Invalid URL");
515 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
516 /// # .expect("Invalid configuration");
517 /// # let webauthn = builder.build()
518 /// # .expect("Invalid configuration");
519 /// #
520 /// // you must store this user's unique id with the account. Alternatelly you can
521 /// // use an existed UUID source.
522 /// let user_unique_id = Uuid::new_v4();
523 ///
524 /// // Initiate a basic registration flow, allowing any cryptograhpic authenticator to proceed.
525 /// let (ccr, skr) = webauthn
526 /// .start_passkey_registration(
527 /// user_unique_id,
528 /// "claire",
529 /// "Claire",
530 /// None, // No other credentials are registered yet.
531 /// )
532 /// .expect("Failed to start registration.");
533 /// ```
534 pub fn start_passkey_registration(
535 &self,
536 user_unique_id: Uuid,
537 user_name: &str,
538 user_display_name: &str,
539 exclude_credentials: Option<Vec<CredentialID>>,
540 ) -> WebauthnResult<(CreationChallengeResponse, PasskeyRegistration)> {
541 let extensions = Some(RequestRegistrationExtensions {
542 cred_protect: Some(CredProtect {
543 // Since this may contain PII, we want to enforce this. We also
544 // want the device to strictly enforce its UV state.
545 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
546 // If set to true, causes many authenticators to shit the bed. We have to just hope
547 // and pray instead. This is because many device classes when they see this extension
548 // and can't satisfy it, they fail the operation instead.
549 enforce_credential_protection_policy: Some(false),
550 }),
551 uvm: Some(true),
552 cred_props: Some(true),
553 min_pin_length: None,
554 hmac_create_secret: None,
555 });
556
557 let builder = self
558 .core
559 .new_challenge_register_builder(
560 user_unique_id.as_bytes(),
561 user_name,
562 user_display_name,
563 )?
564 .attestation(AttestationConveyancePreference::None)
565 .credential_algorithms(self.algorithms.clone())
566 .require_resident_key(false)
567 .authenticator_attachment(None)
568 .user_verification_policy(UserVerificationPolicy::Required)
569 .reject_synchronised_authenticators(false)
570 .exclude_credentials(exclude_credentials)
571 .hints(None)
572 .extensions(extensions);
573
574 self.core
575 .generate_challenge_register(builder)
576 .map(|(ccr, rs)| (ccr, PasskeyRegistration { rs }))
577 }
578
579 /// Initiate the registration of a 'Google Passkey stored in Google Password Manager' on an
580 /// Android device with GMS Core.
581 ///
582 /// This function is required as Android's support for Webauthn/Passkeys is broken
583 /// and does not correctly perform authenticator selection for the user. Instead
584 /// of Android correctly presenting the choice to users to select between a
585 /// security key, or a 'Google Passkey stored in Google Password Manager', Android
586 /// expects the Relying Party to pre-select this and send a correct set of options for either
587 /// a security key *or* a 'Google Passkey stored in Google Password Manager'.
588 ///
589 /// If you choose to use this function you *MUST* ensure that the device you are
590 /// contacting is an Android device with GMS Core, and you *MUST* provide the user the choice
591 /// on your site ahead of time to choose between a security key / screen unlock
592 /// (triggered by [`start_passkey_registration`](Webauthn::start_passkey_registration))
593 /// or a 'Google Passkey stored in Google Password Manager' (triggered by this function).
594 #[cfg(any(
595 all(doc, not(doctest)),
596 feature = "workaround-google-passkey-specific-issues"
597 ))]
598 pub fn start_google_passkey_in_google_password_manager_only_registration(
599 &self,
600 user_unique_id: Uuid,
601 user_name: &str,
602 user_display_name: &str,
603 exclude_credentials: Option<Vec<CredentialID>>,
604 ) -> WebauthnResult<(CreationChallengeResponse, PasskeyRegistration)> {
605 let extensions = Some(RequestRegistrationExtensions {
606 // Android doesn't support cred protect.
607 cred_protect: None,
608 uvm: Some(true),
609 cred_props: Some(true),
610 min_pin_length: None,
611 hmac_create_secret: None,
612 });
613
614 let builder = self
615 .core
616 .new_challenge_register_builder(
617 user_unique_id.as_bytes(),
618 user_name,
619 user_display_name,
620 )?
621 .attestation(AttestationConveyancePreference::None)
622 .credential_algorithms(self.algorithms.clone())
623 // Required for android to work
624 .require_resident_key(true)
625 // Prevent accidentally triggering RK on security keys
626 .authenticator_attachment(Some(AuthenticatorAttachment::Platform))
627 .user_verification_policy(UserVerificationPolicy::Required)
628 .reject_synchronised_authenticators(false)
629 .exclude_credentials(exclude_credentials)
630 .hints(Some(vec![PublicKeyCredentialHints::ClientDevice]))
631 .extensions(extensions);
632
633 self.core
634 .generate_challenge_register(builder)
635 .map(|(ccr, rs)| (ccr, PasskeyRegistration { rs }))
636 }
637
638 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
639 /// and the server provides its paired [PasskeyRegistration]. The details of the Authenticator
640 /// based on the registration parameters are asserted.
641 ///
642 /// # Errors
643 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
644 ///
645 /// # Returns
646 ///
647 /// The returned `Passkey` must be associated to the users account, and is used for future
648 /// authentications via [`start_passkey_authentication`](Webauthn::start_passkey_authentication).
649 ///
650 /// You MUST assert that the registered `CredentialID` has not previously been registered.
651 /// to any other account.
652 pub fn finish_passkey_registration(
653 &self,
654 reg: &RegisterPublicKeyCredential,
655 state: &PasskeyRegistration,
656 ) -> WebauthnResult<Passkey> {
657 self.core
658 .register_credential(reg, &state.rs, None)
659 .map(|cred| Passkey { cred })
660 }
661
662 /// Given a set of `Passkey`'s, begin an authentication of the user. This returns
663 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
664 /// The server must persist the [PasskeyAuthentication] state as it is paired to the
665 /// `RequestChallengeResponse` and required to complete the authentication.
666 ///
667 /// Finally you need to call [`finish_passkey_authentication`](Webauthn::finish_passkey_authentication)
668 /// to complete the authentication.
669 ///
670 /// WARNING ⚠️ YOU MUST STORE THE [PasskeyAuthentication] VALUE SERVER SIDE.
671 ///
672 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
673 /// security of this system.
674 pub fn start_passkey_authentication(
675 &self,
676 creds: &[Passkey],
677 ) -> WebauthnResult<(RequestChallengeResponse, PasskeyAuthentication)> {
678 let extensions = None;
679 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
680 let policy = Some(UserVerificationPolicy::Required);
681 let allow_backup_eligible_upgrade = true;
682 let hints = None;
683
684 self.core
685 .new_challenge_authenticate_builder(creds, policy)
686 .map(|builder| {
687 builder
688 .extensions(extensions)
689 .allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
690 .hints(hints)
691 })
692 .and_then(|b| self.core.generate_challenge_authenticate(b))
693 .map(|(rcr, ast)| (rcr, PasskeyAuthentication { ast }))
694 }
695
696 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [PasskeyAuthentication]
697 /// complete the authentication of the user.
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 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
704 /// process.
705 ///
706 /// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
707 ///
708 /// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
709 /// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
710 /// and you SHOULD invalidate and reject that credential as a result.
711 ///
712 /// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
713 /// valid per the above check. If you wish
714 /// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
715 /// presence of the user verification flag).
716 pub fn finish_passkey_authentication(
717 &self,
718 reg: &PublicKeyCredential,
719 state: &PasskeyAuthentication,
720 ) -> WebauthnResult<AuthenticationResult> {
721 self.core.authenticate_credential(reg, &state.ast)
722 }
723
724 /// Initiate the registration of a new security key for a user. A security key is any cryptographic
725 /// authenticator acting as a single factor of authentication to supplement a password or some
726 /// other authentication factor.
727 ///
728 /// Some examples of security keys include Yubikeys, Feitian ePass, and others.
729 ///
730 /// We don't recommend this over [Passkey] or [AttestedPasskey], as today in Webauthn most devices
731 /// due to their construction require userVerification to be maintained for user trust. What this
732 /// means is that most users will require a password, their security key, and a pin or biometric
733 /// on the security key for a total of three factors. This adds friction to the user experience
734 /// but is required due to a consistency flaw in CTAP2.0 and newer devices. Since the user already
735 /// needs a pin or biometrics, why not just use the device as a self contained MFA?
736 ///
737 /// You MUST pair this authentication with another factor. A security key may opportunistically
738 /// allow and enforce user-verification (MFA), but this is NOT guaranteed.
739 ///
740 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
741 /// identify the user during certain client side work flows.
742 ///
743 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
744 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
745 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
746 /// MUST NOT be used as primary keys. They *may not* be present in authentication, these are only
747 /// present to allow client work flows to display human friendly identifiers.
748 ///
749 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
750 /// You *should* provide the list of credentials that are already registered to this user's account
751 /// to prevent duplicate credential registrations.
752 ///
753 /// `attestation_ca_list` contains an optional list of Root CA certificates of authenticator
754 /// manufacturers that you wish to trust. For example, if you want to only allow Yubikeys on
755 /// your site, then you can provide the Yubico Root CA in this list, to validate that all
756 /// registered devices are manufactured by Yubico.
757 ///
758 /// Extensions may ONLY be accessed if an `attestation_ca_list` is provided, else they
759 /// ARE NOT trusted.
760 ///
761 /// # Returns
762 ///
763 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
764 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
765 /// on the server the [SecurityKeyRegistration] which contains the state of this registration
766 /// attempt and is paired to the `CreationChallengeResponse`.
767 ///
768 /// Finally you need to call [`finish_securitykey_registration`](Webauthn::finish_securitykey_registration)
769 /// to complete the registration.
770 ///
771 /// WARNING ⚠️ YOU MUST STORE THE [SecurityKeyRegistration] VALUE SERVER SIDE.
772 ///
773 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
774 /// security of this system.
775 ///
776 /// ```
777 /// # use webauthn_rs::prelude::*;
778 ///
779 /// # let rp_id = "example.com";
780 /// # let rp_origin = Url::parse("https://idm.example.com")
781 /// # .expect("Invalid URL");
782 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
783 /// # .expect("Invalid configuration");
784 /// # let webauthn = builder.build()
785 /// # .expect("Invalid configuration");
786 ///
787 /// // you must store this user's unique id with the account. Alternatelly you can
788 /// // use an existed UUID source.
789 /// let user_unique_id = Uuid::new_v4();
790 ///
791 /// // Initiate a basic registration flow, allowing any cryptograhpic authenticator to proceed.
792 /// let (ccr, skr) = webauthn
793 /// .start_securitykey_registration(
794 /// user_unique_id,
795 /// "claire",
796 /// "Claire",
797 /// None,
798 /// None,
799 /// None,
800 /// )
801 /// .expect("Failed to start registration.");
802 ///
803 /// // Initiate a basic registration flow, hinting that the device is probably roaming (i.e. a usb),
804 /// // but it could have any attachement in reality
805 /// let (ccr, skr) = webauthn
806 /// .start_securitykey_registration(
807 /// Uuid::new_v4(),
808 /// "claire",
809 /// "Claire",
810 /// None,
811 /// None,
812 /// Some(AuthenticatorAttachment::CrossPlatform),
813 /// )
814 /// .expect("Failed to start registration.");
815 ///
816 /// // Only allow credentials from manufacturers that are trusted and part of the webauthn-rs
817 /// // strict "high quality" list.
818 ///
819 /// use webauthn_rs_device_catalog::Data;
820 /// let device_catalog = Data::strict();
821 ///
822 /// let attestation_ca_list = (&device_catalog)
823 /// .try_into()
824 /// .expect("Failed to build attestation ca list");
825 ///
826 /// let (ccr, skr) = webauthn
827 /// .start_securitykey_registration(
828 /// Uuid::new_v4(),
829 /// "claire",
830 /// "Claire",
831 /// None,
832 /// Some(attestation_ca_list),
833 /// None,
834 /// )
835 /// .expect("Failed to start registration.");
836 /// ```
837 pub fn start_securitykey_registration(
838 &self,
839 user_unique_id: Uuid,
840 user_name: &str,
841 user_display_name: &str,
842 exclude_credentials: Option<Vec<CredentialID>>,
843 attestation_ca_list: Option<AttestationCaList>,
844 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
845 ) -> WebauthnResult<(CreationChallengeResponse, SecurityKeyRegistration)> {
846 let attestation = if let Some(ca_list) = attestation_ca_list.as_ref() {
847 if ca_list.is_empty() {
848 return Err(WebauthnError::MissingAttestationCaList);
849 } else {
850 AttestationConveyancePreference::Direct
851 }
852 } else {
853 AttestationConveyancePreference::None
854 };
855
856 let cred_protect = if self.user_presence_only_security_keys {
857 None
858 } else {
859 Some(CredProtect {
860 // We want the device to strictly enforce its UV state.
861 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
862 // If set to true, causes many authenticators to shit the bed. Since this type doesn't
863 // have the same strict rules about attestation, then we just use this opportunistically.
864 enforce_credential_protection_policy: Some(false),
865 })
866 };
867
868 let extensions = Some(RequestRegistrationExtensions {
869 cred_protect,
870 uvm: Some(true),
871 cred_props: Some(true),
872 min_pin_length: None,
873 hmac_create_secret: None,
874 });
875
876 let policy = if self.user_presence_only_security_keys {
877 UserVerificationPolicy::Discouraged_DO_NOT_USE
878 } else {
879 UserVerificationPolicy::Preferred
880 };
881
882 let builder = self
883 .core
884 .new_challenge_register_builder(
885 user_unique_id.as_bytes(),
886 user_name,
887 user_display_name,
888 )?
889 .attestation(attestation)
890 .credential_algorithms(self.algorithms.clone())
891 .require_resident_key(false)
892 .authenticator_attachment(ui_hint_authenticator_attachment)
893 .user_verification_policy(policy)
894 .reject_synchronised_authenticators(false)
895 .exclude_credentials(exclude_credentials)
896 .hints(Some(vec![PublicKeyCredentialHints::SecurityKey]))
897 .extensions(extensions);
898
899 self.core
900 .generate_challenge_register(builder)
901 .map(|(ccr, rs)| {
902 (
903 ccr,
904 SecurityKeyRegistration {
905 rs,
906 ca_list: attestation_ca_list,
907 },
908 )
909 })
910 }
911
912 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
913 /// and the server provides its paired [SecurityKeyRegistration]. The details of the Authenticator
914 /// based on the registration parameters are asserted.
915 ///
916 /// # Errors
917 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
918 ///
919 /// # Returns
920 ///
921 /// The returned [SecurityKey] must be associated to the users account, and is used for future
922 /// authentications via (`start_securitykey_authentication`)[crate::Webauthn::start_securitykey_authentication].
923 ///
924 /// You MUST assert that the registered [CredentialID] has not previously been registered.
925 /// to any other account.
926 ///
927 /// # Verifying specific device models
928 /// If you wish to assert a specific type of device model is in use, you can inspect the
929 /// SecurityKey `attestation()` and its associated metadata. You can use this to check for
930 /// specific device aaguids for example.
931 ///
932 pub fn finish_securitykey_registration(
933 &self,
934 reg: &RegisterPublicKeyCredential,
935 state: &SecurityKeyRegistration,
936 ) -> WebauthnResult<SecurityKey> {
937 self.core
938 .register_credential(reg, &state.rs, state.ca_list.as_ref())
939 .map(|cred| SecurityKey { cred })
940 }
941
942 /// Given a set of [SecurityKey], begin an authentication of the user. This returns
943 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
944 /// The server must persist the [SecurityKeyAuthentication] state as it is paired to the
945 /// `RequestChallengeResponse` and required to complete the authentication.
946 ///
947 /// Finally you need to call [`finish_securitykey_authentication`](Webauthn::finish_securitykey_authentication)
948 /// to complete the authentication.
949 ///
950 /// WARNING ⚠️ YOU MUST STORE THE [SecurityKeyAuthentication] VALUE SERVER SIDE.
951 ///
952 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
953 /// security of this system.
954 pub fn start_securitykey_authentication(
955 &self,
956 creds: &[SecurityKey],
957 ) -> WebauthnResult<(RequestChallengeResponse, SecurityKeyAuthentication)> {
958 let extensions = None;
959 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
960 let allow_backup_eligible_upgrade = false;
961
962 let policy = if self.user_presence_only_security_keys {
963 Some(UserVerificationPolicy::Discouraged_DO_NOT_USE)
964 } else {
965 Some(UserVerificationPolicy::Preferred)
966 };
967
968 let hints = Some(vec![PublicKeyCredentialHints::SecurityKey]);
969
970 self.core
971 .new_challenge_authenticate_builder(creds, policy)
972 .map(|builder| {
973 builder
974 .extensions(extensions)
975 .allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
976 .hints(hints)
977 })
978 .and_then(|b| self.core.generate_challenge_authenticate(b))
979 .map(|(rcr, ast)| (rcr, SecurityKeyAuthentication { ast }))
980 }
981
982 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [SecurityKeyAuthentication]
983 /// complete the authentication of the user.
984 ///
985 /// # Errors
986 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
987 ///
988 /// # Returns
989 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
990 /// process.
991 ///
992 /// You should use `SecurityKey::update_credential` on the returned [AuthenticationResult] and
993 /// ensure it is persisted.
994 pub fn finish_securitykey_authentication(
995 &self,
996 reg: &PublicKeyCredential,
997 state: &SecurityKeyAuthentication,
998 ) -> WebauthnResult<AuthenticationResult> {
999 self.core.authenticate_credential(reg, &state.ast)
1000 }
1001}
1002
1003#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
1004impl Webauthn {
1005 /// Initiate the registration of a new attested_passkey key for a user. A attested_passkey key is a
1006 /// cryptographic authenticator that is a self-contained multifactor authenticator. This means
1007 /// that the device (such as a yubikey) verifies the user is who they say they are via a PIN,
1008 /// biometric or other factor. Only if this verification passes, is the signature released
1009 /// and provided.
1010 ///
1011 /// As a result, the server *only* requires this attested_passkey key to authenticate the user
1012 /// and assert their identity. Because of this reliance on the authenticator, attestation of
1013 /// the authenticator and its properties is strongly recommended.
1014 ///
1015 /// The primary difference to a passkey, is that these credentials must provide an attestation
1016 /// certificate which will be cryptographically validated to strictly enforce that only certain
1017 /// devices may be registered.
1018 ///
1019 /// This attestation requires that private key material is bound to a single hardware
1020 /// authenticator, and cannot be copied or moved out of it. At present, all widely deployed
1021 /// Hybrid authenticators (Apple iCloud Keychain and Google Passkeys in Google Password
1022 /// Manager) are synchronised authenticators which can roam between multiple devices, and so can
1023 /// never be attested.
1024 ///
1025 /// As of webauthn-rs v0.5.0, this creates a registration challenge with
1026 /// [credential selection hints](PublicKeyCredentialHints) that only use ClientDevice or
1027 /// SecurityKey devices, so a user-agent supporting Webauthn L3 won't offer to use Hybrid
1028 /// credentials. On user-agents not supporting Webauthn L3, and on older versions of
1029 /// webauthn-rs, user-agents would show a QR code and a user could attempt to register a
1030 /// Hybrid authenticator, but it would always fail at the end -- which is a frustrating user
1031 /// experience!
1032 ///
1033 /// You *should* recommend to the user to register multiple attested_passkey keys to their account on
1034 /// separate devices so that they have fall back authentication in the case of device failure or loss.
1035 ///
1036 /// You *should* have a workflow that allows a user to register new devices without a need to register
1037 /// other factors. For example, allow a QR code that can be scanned from a phone, or a one-time
1038 /// link that can be copied to the device.
1039 ///
1040 /// You *must* have a recovery workflow in case all devices are lost or destroyed.
1041 ///
1042 /// `user_unique_id` *may* be stored in the authenticator. This may allow the credential to
1043 /// identify the user during certain client side work flows.
1044 ///
1045 /// `user_name` and `user_display_name` *may* be stored in the authenticator. `user_name` is a
1046 /// friendly account name such as "claire@example.com". `user_display_name` is the persons chosen
1047 /// way to be identified such as "Claire". Both can change at *any* time on the client side, and
1048 /// MUST NOT be used as primary keys. They *may not* be present in authentication, these are only
1049 /// present to allow client work flows to display human friendly identifiers.
1050 ///
1051 /// `exclude_credentials` ensures that a set of credentials may not participate in this registration.
1052 /// You *should* provide the list of credentials that are already registered to this user's account
1053 /// to prevent duplicate credential registrations.
1054 ///
1055 /// `attestation_ca_list` contains a required list of Root CA certificates of authenticator
1056 /// manufacturers that you wish to trust. For example, if you want to only allow Yubikeys on
1057 /// your site, then you can provide the Yubico Root CA in this list, to validate that all
1058 /// registered devices are manufactured by Yubico.
1059 ///
1060 /// `ui_hint_authenticator_attachment` provides a UX/UI hint to the browser about the types
1061 /// of credentials that could be used in this registration. If set to `None` all authenticator
1062 /// attachement classes are valid. If set to Platform, only authenticators that are part of the
1063 /// device are used such as a TPM or TouchId. If set to Cross-Platform, only devices that are
1064 /// removable from the device can be used such as yubikeys.
1065 ///
1066 /// # Returns
1067 ///
1068 /// This function returns a `CreationChallengeResponse` which you must serialise to json and
1069 /// send to the user agent (e.g. a browser) for it to conduct the registration. You must persist
1070 /// on the server the `AttestedPasskeyRegistration` which contains the state of this registration
1071 /// attempt and is paired to the `CreationChallengeResponse`.
1072 ///
1073 /// Finally you need to call [`finish_attested_passkey_registration`](Webauthn::finish_attested_passkey_registration)
1074 /// to complete the registration.
1075 ///
1076 /// WARNING ⚠️ YOU MUST STORE THE [AttestedPasskeyRegistration] VALUE SERVER SIDE.
1077 ///
1078 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
1079 /// security of this system.
1080 ///
1081 /// ```
1082 /// # use webauthn_rs::prelude::*;
1083 /// use webauthn_rs_device_catalog::Data;
1084 /// # let rp_id = "example.com";
1085 /// # let rp_origin = Url::parse("https://idm.example.com")
1086 /// # .expect("Invalid url");
1087 /// # let mut builder = WebauthnBuilder::new(rp_id, &rp_origin)
1088 /// # .expect("Invalid configuration");
1089 /// # let webauthn = builder.build()
1090 /// # .expect("Invalid configuration");
1091 /// #
1092 /// // you must store this user's unique id with the account. Alternatively you can
1093 /// // use an existed UUID source.
1094 /// let user_unique_id = Uuid::new_v4();
1095 ///
1096 /// // Create a device catalog reference that contains a list of known high quality authenticators
1097 /// let device_catalog = Data::all_known_devices();
1098 ///
1099 /// let attestation_ca_list = (&device_catalog)
1100 /// .try_into()
1101 /// .expect("Failed to build attestation ca list");
1102 ///
1103 /// // Initiate a basic registration flow, allowing any attested cryptograhpic authenticator to proceed.
1104 /// // Hint (but do not enforce) that we prefer this to be a token/key like a yubikey.
1105 /// // To enforce this you can validate the properties of the returned device aaguid.
1106 /// let (ccr, skr) = webauthn
1107 /// .start_attested_passkey_registration(
1108 /// user_unique_id,
1109 /// "claire",
1110 /// "Claire",
1111 /// None,
1112 /// attestation_ca_list,
1113 /// Some(AuthenticatorAttachment::CrossPlatform),
1114 /// )
1115 /// .expect("Failed to start registration.");
1116 ///
1117 /// // Only allow credentials from manufacturers that are trusted and part of the webauthn-rs
1118 /// // strict "high quality" list.
1119 /// // Hint (but do not enforce) that we prefer this to be a device like TouchID.
1120 /// // To enforce this you can validate the attestation ca used along with the returned device aaguid
1121 ///
1122 /// let device_catalog = Data::strict();
1123 ///
1124 /// let attestation_ca_list = (&device_catalog)
1125 /// .try_into()
1126 /// .expect("Failed to build attestation ca list");
1127 ///
1128 /// let (ccr, skr) = webauthn
1129 /// .start_attested_passkey_registration(
1130 /// Uuid::new_v4(),
1131 /// "claire",
1132 /// "Claire",
1133 /// None,
1134 /// attestation_ca_list,
1135 /// Some(AuthenticatorAttachment::Platform),
1136 /// )
1137 /// .expect("Failed to start registration.");
1138 /// ```
1139 pub fn start_attested_passkey_registration(
1140 &self,
1141 user_unique_id: Uuid,
1142 user_name: &str,
1143 user_display_name: &str,
1144 exclude_credentials: Option<Vec<CredentialID>>,
1145 attestation_ca_list: AttestationCaList,
1146 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
1147 // extensions
1148 ) -> WebauthnResult<(CreationChallengeResponse, AttestedPasskeyRegistration)> {
1149 if attestation_ca_list.is_empty() {
1150 return Err(WebauthnError::MissingAttestationCaList);
1151 }
1152
1153 let extensions = Some(RequestRegistrationExtensions {
1154 cred_protect: Some(CredProtect {
1155 // Since this may contain PII, we need to enforce this. We also
1156 // want the device to strictly enforce its UV state.
1157 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
1158 // Set to true since this function requires attestation, and attestation is really
1159 // only viable on FIDO2/CTAP2 creds that actually support this.
1160 enforce_credential_protection_policy: Some(true),
1161 }),
1162 // https://www.w3.org/TR/webauthn-2/#sctn-uvm-extension
1163 uvm: Some(true),
1164 cred_props: Some(true),
1165 // https://fidoalliance.org/specs/fido-v2.1-rd-20210309/fido-client-to-authenticator-protocol-v2.1-rd-20210309.html#sctn-minpinlength-extension
1166 min_pin_length: Some(true),
1167 hmac_create_secret: Some(true),
1168 });
1169
1170 let builder = self
1171 .core
1172 .new_challenge_register_builder(
1173 user_unique_id.as_bytes(),
1174 user_name,
1175 user_display_name,
1176 )?
1177 .attestation(AttestationConveyancePreference::Direct)
1178 .credential_algorithms(self.algorithms.clone())
1179 .require_resident_key(false)
1180 .authenticator_attachment(ui_hint_authenticator_attachment)
1181 .user_verification_policy(UserVerificationPolicy::Required)
1182 .reject_synchronised_authenticators(true)
1183 .exclude_credentials(exclude_credentials)
1184 .hints(Some(
1185 // hybrid does NOT perform attestation
1186 vec![
1187 PublicKeyCredentialHints::ClientDevice,
1188 PublicKeyCredentialHints::SecurityKey,
1189 ],
1190 ))
1191 .attestation_formats(Some(vec![
1192 AttestationFormat::Packed,
1193 AttestationFormat::Tpm,
1194 ]))
1195 .extensions(extensions);
1196
1197 self.core
1198 .generate_challenge_register(builder)
1199 .map(|(ccr, rs)| {
1200 (
1201 ccr,
1202 AttestedPasskeyRegistration {
1203 rs,
1204 ca_list: attestation_ca_list,
1205 },
1206 )
1207 })
1208 }
1209
1210 /// Complete the registration of the credential. The user agent (e.g. a browser) will return the data of `RegisterPublicKeyCredential`,
1211 /// and the server provides its paired [AttestedPasskeyRegistration]. The details of the Authenticator
1212 /// based on the registration parameters are asserted.
1213 ///
1214 /// # Errors
1215 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
1216 ///
1217 /// # Returns
1218 /// The returned [AttestedPasskey] must be associated to the users account, and is used for future
1219 /// authentications via [crate::Webauthn::start_attested_passkey_authentication].
1220 ///
1221 /// # Verifying specific device models
1222 /// If you wish to assert a specific type of device model is in use, you can inspect the
1223 /// AttestedPasskey `attestation()` and its associated metadata. You can use this to check for
1224 /// specific device aaguids for example.
1225 ///
1226 pub fn finish_attested_passkey_registration(
1227 &self,
1228 reg: &RegisterPublicKeyCredential,
1229 state: &AttestedPasskeyRegistration,
1230 ) -> WebauthnResult<AttestedPasskey> {
1231 self.core
1232 .register_credential(reg, &state.rs, Some(&state.ca_list))
1233 .map(|cred| AttestedPasskey { cred })
1234 }
1235
1236 /// Given a set of `AttestedPasskey`'s, begin an authentication of the user. This returns
1237 /// a `RequestChallengeResponse`, which should be serialised to json and sent to the user agent (e.g. a browser).
1238 /// The server must persist the [AttestedPasskeyAuthentication] state as it is paired to the
1239 /// `RequestChallengeResponse` and required to complete the authentication.
1240 ///
1241 /// Finally you need to call [`finish_attested_passkey_authentication`](Webauthn::finish_attested_passkey_authentication)
1242 /// to complete the authentication.
1243 ///
1244 /// WARNING ⚠️ YOU MUST STORE THE [AttestedPasskeyAuthentication] VALUE SERVER SIDE.
1245 ///
1246 /// Failure to do so *may* open you to replay attacks which can significantly weaken the
1247 /// security of this system.
1248 pub fn start_attested_passkey_authentication(
1249 &self,
1250 creds: &[AttestedPasskey],
1251 ) -> WebauthnResult<(RequestChallengeResponse, AttestedPasskeyAuthentication)> {
1252 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
1253
1254 let extensions = Some(RequestAuthenticationExtensions {
1255 appid: None,
1256 uvm: Some(true),
1257 hmac_get_secret: None,
1258 });
1259
1260 let policy = Some(UserVerificationPolicy::Required);
1261 let allow_backup_eligible_upgrade = false;
1262
1263 let hints = Some(vec![
1264 PublicKeyCredentialHints::SecurityKey,
1265 PublicKeyCredentialHints::ClientDevice,
1266 ]);
1267
1268 self.core
1269 .new_challenge_authenticate_builder(creds, policy)
1270 .map(|builder| {
1271 builder
1272 .extensions(extensions)
1273 .allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
1274 .hints(hints)
1275 })
1276 .and_then(|b| self.core.generate_challenge_authenticate(b))
1277 .map(|(rcr, ast)| (rcr, AttestedPasskeyAuthentication { ast }))
1278 }
1279
1280 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), and the stored [AttestedPasskeyAuthentication]
1281 /// complete the authentication of the user. This asserts that user verification must have been correctly
1282 /// performed allowing you to trust this as a MFA interfaction.
1283 ///
1284 /// # Errors
1285 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
1286 ///
1287 /// # Returns
1288 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
1289 /// process.
1290 ///
1291 /// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
1292 ///
1293 /// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
1294 /// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
1295 /// and you SHOULD invalidate and reject that credential as a result.
1296 ///
1297 /// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
1298 /// valid per the above check. If you wish
1299 /// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
1300 /// user verification flag).
1301 pub fn finish_attested_passkey_authentication(
1302 &self,
1303 reg: &PublicKeyCredential,
1304 state: &AttestedPasskeyAuthentication,
1305 ) -> WebauthnResult<AuthenticationResult> {
1306 self.core.authenticate_credential(reg, &state.ast)
1307 }
1308}
1309
1310#[cfg(any(all(doc, not(doctest)), feature = "conditional-ui"))]
1311impl Webauthn {
1312 /// This function will initiate a conditional ui authentication for discoverable
1313 /// credentials.
1314 ///
1315 /// Since this relies on the client to "discover" what credential and user id to
1316 /// use, there are no options required to start this.
1317 pub fn start_discoverable_authentication(
1318 &self,
1319 ) -> WebauthnResult<(RequestChallengeResponse, DiscoverableAuthentication)> {
1320 let policy = Some(UserVerificationPolicy::Required);
1321 let extensions = Some(RequestAuthenticationExtensions {
1322 appid: None,
1323 uvm: Some(true),
1324 hmac_get_secret: None,
1325 });
1326 let allow_backup_eligible_upgrade = false;
1327 let hints = None;
1328
1329 self.core
1330 .new_challenge_authenticate_builder(Vec::with_capacity(0), policy)
1331 .map(|builder| {
1332 builder
1333 .extensions(extensions)
1334 .allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
1335 .hints(hints)
1336 })
1337 .and_then(|b| self.core.generate_challenge_authenticate(b))
1338 .map(|(mut rcr, ast)| {
1339 // Force conditional ui - this is not a generic discoverable credential
1340 // workflow!
1341 rcr.mediation = Some(Mediation::Conditional);
1342 (rcr, DiscoverableAuthentication { ast })
1343 })
1344 }
1345
1346 /// Pre-process the clients response from a conditional ui authentication attempt. This
1347 /// will extract the users Uuid and the Credential ID that was used by the user
1348 /// in their authentication.
1349 ///
1350 /// You must use this information to locate the relavent credential that was used
1351 /// to allow you to finish the authentication.
1352 pub fn identify_discoverable_authentication<'a>(
1353 &'_ self,
1354 reg: &'a PublicKeyCredential,
1355 ) -> WebauthnResult<(Uuid, &'a [u8])> {
1356 let cred_id = reg.get_credential_id();
1357 reg.get_user_unique_id()
1358 .and_then(|b| Uuid::from_slice(b).ok())
1359 .map(|u| (u, cred_id))
1360 .ok_or(WebauthnError::InvalidUserUniqueId)
1361 }
1362
1363 /// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), the
1364 /// stored [DiscoverableAuthentication] and the users [DiscoverableKey],
1365 /// complete the authentication of the user. This asserts that user verification must have been correctly
1366 /// performed allowing you to trust this as a MFA interfaction.
1367 ///
1368 /// # Errors
1369 /// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
1370 ///
1371 /// # Returns
1372 /// On success, [AuthenticationResult] is returned which contains some details of the Authentication
1373 /// process.
1374 ///
1375 /// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
1376 ///
1377 /// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
1378 /// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
1379 /// and you SHOULD invalidate and reject that credential as a result.
1380 ///
1381 /// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
1382 /// valid per the above check. If you wish
1383 /// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
1384 /// user verification flag).
1385 pub fn finish_discoverable_authentication(
1386 &self,
1387 reg: &PublicKeyCredential,
1388 mut state: DiscoverableAuthentication,
1389 creds: &[DiscoverableKey],
1390 ) -> WebauthnResult<AuthenticationResult> {
1391 let creds = creds.iter().map(|dk| dk.cred.clone()).collect();
1392 state.ast.set_allowed_credentials(creds);
1393 self.core.authenticate_credential(reg, &state.ast)
1394 }
1395}
1396
1397#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
1398impl Webauthn {
1399 /// TODO
1400 pub fn start_attested_resident_key_registration(
1401 &self,
1402 user_unique_id: Uuid,
1403 user_name: &str,
1404 user_display_name: &str,
1405 exclude_credentials: Option<Vec<CredentialID>>,
1406 attestation_ca_list: AttestationCaList,
1407 ui_hint_authenticator_attachment: Option<AuthenticatorAttachment>,
1408 ) -> WebauthnResult<(CreationChallengeResponse, AttestedResidentKeyRegistration)> {
1409 if attestation_ca_list.is_empty() {
1410 return Err(WebauthnError::MissingAttestationCaList);
1411 }
1412
1413 // credProtect
1414 let extensions = Some(RequestRegistrationExtensions {
1415 cred_protect: Some(CredProtect {
1416 // Since this will contain PII, we need to enforce this.
1417 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
1418 // Set to true since this function requires attestation, and attestation is really
1419 // only viable on FIDO2/CTAP2 creds that actually support this.
1420 enforce_credential_protection_policy: Some(true),
1421 }),
1422 // https://www.w3.org/TR/webauthn-2/#sctn-uvm-extension
1423 uvm: Some(true),
1424 cred_props: Some(true),
1425 // https://fidoalliance.org/specs/fido-v2.1-rd-20210309/fido-client-to-authenticator-protocol-v2.1-rd-20210309.html#sctn-minpinlength-extension
1426 min_pin_length: Some(true),
1427 hmac_create_secret: Some(true),
1428 });
1429
1430 let builder = self
1431 .core
1432 .new_challenge_register_builder(
1433 user_unique_id.as_bytes(),
1434 user_name,
1435 user_display_name,
1436 )?
1437 .attestation(AttestationConveyancePreference::Direct)
1438 .credential_algorithms(self.algorithms.clone())
1439 .require_resident_key(true)
1440 .authenticator_attachment(ui_hint_authenticator_attachment)
1441 .user_verification_policy(UserVerificationPolicy::Required)
1442 .reject_synchronised_authenticators(true)
1443 .exclude_credentials(exclude_credentials)
1444 .hints(Some(
1445 // hybrid does NOT perform attestation
1446 vec![
1447 PublicKeyCredentialHints::ClientDevice,
1448 PublicKeyCredentialHints::SecurityKey,
1449 ],
1450 ))
1451 .attestation_formats(Some(vec![
1452 AttestationFormat::Packed,
1453 AttestationFormat::Tpm,
1454 ]))
1455 .extensions(extensions);
1456
1457 self.core
1458 .generate_challenge_register(builder)
1459 .map(|(ccr, rs)| {
1460 (
1461 ccr,
1462 AttestedResidentKeyRegistration {
1463 rs,
1464 ca_list: attestation_ca_list,
1465 },
1466 )
1467 })
1468 }
1469
1470 /// TODO
1471 pub fn finish_attested_resident_key_registration(
1472 &self,
1473 reg: &RegisterPublicKeyCredential,
1474 state: &AttestedResidentKeyRegistration,
1475 ) -> WebauthnResult<AttestedResidentKey> {
1476 let cred = self
1477 .core
1478 .register_credential(reg, &state.rs, Some(&state.ca_list))?;
1479
1480 trace!("finish attested_resident_key -> {:?}", cred);
1481
1482 // cred protect ignored :(
1483 // Is the pin long enough?
1484 // Is it rk?
1485 // I guess we'll never know ...
1486
1487 // Is it an approved cred / aaguid?
1488
1489 Ok(AttestedResidentKey { cred })
1490 }
1491
1492 /// TODO
1493 pub fn start_attested_resident_key_authentication(
1494 &self,
1495 creds: &[AttestedResidentKey],
1496 ) -> WebauthnResult<(RequestChallengeResponse, AttestedResidentKeyAuthentication)> {
1497 let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
1498 let extensions = Some(RequestAuthenticationExtensions {
1499 appid: None,
1500 uvm: Some(true),
1501 hmac_get_secret: None,
1502 });
1503
1504 let policy = Some(UserVerificationPolicy::Required);
1505 let allow_backup_eligible_upgrade = false;
1506
1507 let hints = Some(vec![
1508 PublicKeyCredentialHints::SecurityKey,
1509 PublicKeyCredentialHints::ClientDevice,
1510 ]);
1511
1512 self.core
1513 .new_challenge_authenticate_builder(creds, policy)
1514 .map(|builder| {
1515 builder
1516 .extensions(extensions)
1517 .allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
1518 .hints(hints)
1519 })
1520 .and_then(|b| self.core.generate_challenge_authenticate(b))
1521 .map(|(rcr, ast)| (rcr, AttestedResidentKeyAuthentication { ast }))
1522 }
1523
1524 /// TODO
1525 pub fn finish_attested_resident_key_authentication(
1526 &self,
1527 reg: &PublicKeyCredential,
1528 state: &AttestedResidentKeyAuthentication,
1529 ) -> WebauthnResult<AuthenticationResult> {
1530 self.core.authenticate_credential(reg, &state.ast)
1531 }
1532}
1533
1534#[cfg(test)]
1535mod tests {
1536 #[test]
1537 /// Test that building a webauthn object from a chrome extension origin is successful.
1538 fn test_webauthnbuilder_chrome_url() -> Result<(), Box<dyn std::error::Error>> {
1539 use crate::prelude::*;
1540 let rp_id = "2114c9f524d0cbd74dbe846a51c3e5b34b83ac02c5220ec5cdff751096fa25a5";
1541 let rp_origin = Url::parse(&format!("chrome-extension://{rp_id}"))?;
1542 eprintln!("{rp_origin:?}");
1543 let builder = WebauthnBuilder::new(rp_id, &rp_origin)?;
1544 eprintln!("rp_id: {:?}", builder.rp_id);
1545 let built = builder.build()?;
1546 eprintln!("rp_name: {}", built.core.rp_name());
1547 Ok(())
1548 }
1549}