Skip to main content

webauthn_rs_icp/
lib.rs

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