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}