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