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}