passkey_authenticator/
authenticator.rs

1use coset::iana;
2use passkey_types::{
3    ctap2::{Aaguid, Ctap2Error, Flags},
4    webauthn,
5};
6
7use crate::{CredentialStore, UserValidationMethod, user_validation::UiHint};
8
9pub mod extensions;
10mod get_assertion;
11mod get_info;
12mod make_credential;
13
14use extensions::Extensions;
15
16/// The length of credentialId that should be randomly generated during a credential creation operation.
17///
18/// The value has a maximum of `64` per the [webauthn specification]. The minimum is a library enforced as `16`.
19///
20/// It is recommended to randomize this if possible to avoid authenticator fingerprinting.
21///
22/// [webauthn specification]: https://www.w3.org/TR/webauthn-3/#user-handle
23#[derive(Debug, Clone, Copy)]
24#[repr(transparent)]
25pub struct CredentialIdLength(u8);
26
27impl CredentialIdLength {
28    /// The default length of a credentialId to generate.
29    ///
30    /// This value is the same as [`Self::default`], but available in
31    /// `const` contexts.
32    pub const DEFAULT: Self = Self(Self::MIN);
33
34    const MIN: u8 = 16;
35
36    // "A user handle is an opaque byte sequence with a maximum size of 64 bytes..."
37    // Ref: https://www.w3.org/TR/webauthn-3/#user-handle
38    const MAX: u8 = 64;
39
40    /// Generates and returns a uniformly random [CredentialIdLength].
41    pub fn randomized(rng: &mut impl rand::Rng) -> Self {
42        let length = rng.gen_range(Self::MIN..=Self::MAX);
43        Self(length)
44    }
45}
46
47impl Default for CredentialIdLength {
48    fn default() -> Self {
49        Self::DEFAULT
50    }
51}
52
53impl From<u8> for CredentialIdLength {
54    fn from(value: u8) -> Self {
55        // Clamp to the specification's maximum.
56        let value = core::cmp::min(Self::MAX, value);
57        // Round values less then what we support up to the default.
58        let value = core::cmp::max(Self::MIN, value);
59        Self(value)
60    }
61}
62
63impl From<CredentialIdLength> for usize {
64    fn from(value: CredentialIdLength) -> Self {
65        usize::from(value.0)
66    }
67}
68
69/// A virtual authenticator with all the necessary state and information.
70pub struct Authenticator<S, U> {
71    /// The authenticator's AAGUID
72    aaguid: Aaguid,
73    /// Provides credential storage capabilities
74    store: S,
75    /// Current supported algorithms by the authenticator
76    algs: Vec<iana::Algorithm>,
77    /// Current supported transports that this authenticator can use to communicate.
78    ///
79    /// Default values are [`webauthn::AuthenticatorTransport::Internal`] and
80    /// [`webauthn::AuthenticatorTransport::Hybrid`].
81    transports: Vec<webauthn::AuthenticatorTransport>,
82    /// Provider of user verification factor.
83    user_validation: U,
84
85    /// Value to control whether the authenticator will save new credentials with a signature counter.
86    /// The default value is `false`.
87    ///
88    /// NOTE: Using a counter with a credential that will sync is not recommended and can cause friction
89    /// with the distributed nature of synced keys. It can also cause issues with backup and restore functionality.
90    make_credentials_with_signature_counter: bool,
91
92    /// The length of the credentialId made during a creation operation.
93    credential_id_length: CredentialIdLength,
94
95    /// Supported authenticator extensions
96    extensions: Extensions,
97}
98
99impl<S, U> Authenticator<S, U>
100where
101    S: CredentialStore,
102    U: UserValidationMethod,
103{
104    /// Create an authenticator with a known aaguid, a backing storage and a User verification system.
105    pub fn new(aaguid: Aaguid, store: S, user: U) -> Self {
106        Self {
107            aaguid,
108            store,
109            // TODO: Change this to a method on the cryptographic backend
110            algs: vec![iana::Algorithm::ES256],
111            transports: vec![
112                webauthn::AuthenticatorTransport::Internal,
113                webauthn::AuthenticatorTransport::Hybrid,
114            ],
115            user_validation: user,
116            make_credentials_with_signature_counter: false,
117            credential_id_length: CredentialIdLength::default(),
118            extensions: Extensions::default(),
119        }
120    }
121
122    /// Set whether the authenticator should save new credentials with a signature counter.
123    ///
124    /// NOTE: Using a counter with a credential that will sync is not recommended and can cause friction
125    /// with the distributed nature of synced keys. It can also cause issues with backup and restore functionality.
126    pub fn set_make_credentials_with_signature_counter(&mut self, value: bool) {
127        self.make_credentials_with_signature_counter = value;
128    }
129
130    /// Get whether the authenticator will save new credentials with a signature counter.
131    pub fn make_credentials_with_signature_counter(&self) -> bool {
132        self.make_credentials_with_signature_counter
133    }
134
135    /// Set the length of credentialId to generate when creating a new credential.
136    pub fn set_make_credential_id_length(&mut self, length: CredentialIdLength) {
137        self.credential_id_length = length;
138    }
139
140    /// Get the current length of credential that will be generated when making a new credential.
141    pub fn make_credential_id_length(&self) -> CredentialIdLength {
142        self.credential_id_length
143    }
144
145    /// Access the [`CredentialStore`] to look into what is stored.
146    pub fn store(&self) -> &S {
147        &self.store
148    }
149
150    /// Exclusively access the [`CredentialStore`] to look into what is stored and modify it if needed.
151    pub fn store_mut(&mut self) -> &mut S {
152        &mut self.store
153    }
154
155    /// Access the authenticator's [`Aaguid`]
156    pub fn aaguid(&self) -> &Aaguid {
157        &self.aaguid
158    }
159
160    /// Return the current attachment type for this authenticator.
161    pub fn attachment_type(&self) -> webauthn::AuthenticatorAttachment {
162        // TODO: Make this variable depending on the transport.
163        webauthn::AuthenticatorAttachment::Platform
164    }
165
166    /// Validate `params` with the following steps
167    ///     1. For each element of `params`:
168    ///         1-2: Handled during deserialization
169    ///         3. If the element specifies an algorithm that is supported by the authenticator, and
170    ///            no algorithm has yet been chosen by this loop, then let the algorithm specified by
171    ///            the current element be the chosen algorithm.
172    ///     2. If the loop completes and no algorithm was chosen then return [`Ctap2Error::UnsupportedAlgorithm`].
173    /// Note: This loop chooses the first occurrence of an algorithm identifier supported by this
174    ///       authenticator but always iterates over every element of `params` to validate them.
175    pub fn choose_algorithm(
176        &self,
177        params: &[webauthn::PublicKeyCredentialParameters],
178    ) -> Result<iana::Algorithm, Ctap2Error> {
179        params
180            .iter()
181            .find(|param| self.algs.contains(&param.alg))
182            .map(|param| param.alg)
183            .ok_or(Ctap2Error::UnsupportedAlgorithm)
184    }
185
186    /// Builder method for overwriting the authenticator's supported transports.
187    pub fn transports(self, transports: Vec<webauthn::AuthenticatorTransport>) -> Self {
188        Self { transports, ..self }
189    }
190
191    /// Collect user consent if required. This step MUST happen before the following steps due
192    ///    to privacy reasons (i.e., authenticator cannot disclose existence of a credential
193    ///    until the user interacted with the device):
194    ///     1. If the "uv" option was specified and set to true:
195    ///         1. If device doesn’t support user-identifiable gestures, return the
196    ///            CTAP2_ERR_UNSUPPORTED_OPTION error.
197    ///         2. Collect a user-identifiable gesture. If gesture validation fails, return the
198    ///            CTAP2_ERR_OPERATION_DENIED error.
199    ///     2. If the "up" option was specified and set to true, collect the user’s consent.
200    ///         1. If no consent is obtained and a timeout occurs, return the
201    ///            CTAP2_ERR_OPERATION_DENIED error.
202    async fn check_user(
203        &self,
204        hint: UiHint<'_, <U as UserValidationMethod>::PasskeyItem>,
205        options: &passkey_types::ctap2::make_credential::Options,
206    ) -> Result<Flags, Ctap2Error> {
207        if options.uv && self.user_validation.is_verification_enabled() != Some(true) {
208            return Err(Ctap2Error::UnsupportedOption);
209        };
210
211        let check_result = self
212            .user_validation
213            .check_user(hint, options.up, options.uv)
214            .await?;
215
216        if options.up && !check_result.presence {
217            return Err(Ctap2Error::OperationDenied);
218        }
219
220        if options.uv && !check_result.verification {
221            return Err(Ctap2Error::OperationDenied);
222        }
223
224        let mut flags = Flags::empty();
225        if check_result.presence {
226            flags |= Flags::UP;
227        }
228
229        if check_result.verification {
230            flags |= Flags::UV;
231        }
232
233        Ok(flags)
234    }
235
236    /// Set the hmac-secret extension as a supported extension
237    pub fn hmac_secret(mut self, ext: extensions::HmacSecretConfig) -> Self {
238        self.extensions.hmac_secret = Some(ext);
239        self
240    }
241}
242
243#[cfg(test)]
244mod tests;