webauthn_authenticator_rs/ctap2/
ctap20.rs

1use std::{collections::BTreeMap, fmt::Debug, mem::size_of};
2
3#[cfg(feature = "ctap2-management")]
4use crate::util::check_pin;
5use crate::{
6    authenticator_hashed::AuthenticatorBackendHashedClientData,
7    ctap2::{commands::*, pin_uv::*, Ctap21Authenticator},
8    error::{CtapError, WebauthnCError},
9    transport::Token,
10    ui::UiCallback,
11    SHA256Hash, BASE64_ENGINE,
12};
13
14use base64::Engine;
15use base64urlsafedata::Base64UrlSafeData;
16use futures::executor::block_on;
17
18use webauthn_rs_proto::{
19    AuthenticationExtensionsClientOutputs, AuthenticatorAssertionResponseRaw,
20    AuthenticatorAttestationResponseRaw, PubKeyCredParams, PublicKeyCredential,
21    RegisterPublicKeyCredential, RegistrationExtensionsClientOutputs, RelyingParty, User,
22    UserVerificationPolicy,
23};
24
25use super::internal::CtapAuthenticatorVersion;
26
27#[derive(Debug, Clone)]
28pub(super) enum AuthToken {
29    /// No authentication token to be supplied
30    None,
31    /// `pinUvAuthProtocol`, `pinUvAuthToken`
32    ProtocolToken(u32, Vec<u8>),
33    /// Send request with `uv = true`
34    UvTrue,
35}
36
37impl AuthToken {
38    pub fn into_pin_uv_params(self) -> (Option<u32>, Option<Vec<u8>>) {
39        match self {
40            Self::ProtocolToken(p, t) => (Some(p), Some(t)),
41            _ => (None, None),
42        }
43    }
44}
45
46#[derive(Debug)]
47pub(super) enum AuthSession {
48    None,
49    /// `iface`, `pinToken`
50    InterfaceToken(PinUvPlatformInterface, Vec<u8>),
51    UvTrue,
52}
53
54/// CTAP 2.0 protocol implementation.
55#[derive(Debug)]
56pub struct Ctap20Authenticator<'a, T: Token, U: UiCallback> {
57    pub(super) info: GetInfoResponse,
58    pub(super) token: T,
59    pub(super) ui_callback: &'a U,
60}
61
62impl<'a, T: Token, U: UiCallback> CtapAuthenticatorVersion<'a, T, U>
63    for Ctap20Authenticator<'a, T, U>
64{
65    const VERSION: &'static str = "FIDO_2_0";
66    fn new_with_info(info: GetInfoResponse, token: T, ui_callback: &'a U) -> Self {
67        Self {
68            info,
69            token,
70            ui_callback,
71        }
72    }
73}
74
75impl<'a, T: Token, U: UiCallback> Ctap20Authenticator<'a, T, U> {
76    /// Gets cached information about the authenticator.
77    ///
78    /// This does not transmit to the token.
79    pub fn get_info(&self) -> &GetInfoResponse {
80        &self.info
81    }
82
83    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
84    /// Perform a factory reset of the token, deleting all data.
85    pub async fn factory_reset(&mut self) -> Result<(), WebauthnCError> {
86        let ui_callback = self.ui_callback;
87        self.token
88            .transmit(ResetRequest {}, ui_callback)
89            .await
90            .map(|_| ())
91    }
92
93    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
94    /// Refreshes the cached [GetInfoResponse].
95    ///
96    /// This needs to be called (internally) after sending a command which
97    /// could invalidate its content.
98    pub(super) async fn refresh_info(&mut self) -> Result<(), WebauthnCError> {
99        let ui_callback = self.ui_callback;
100        self.info = self.token.transmit(GetInfoRequest {}, ui_callback).await?;
101        Ok(())
102    }
103
104    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
105    /// Checks whether a provided PIN follows the rules defined by the
106    /// authenticator. This does not share the PIN with the authenticator.
107    pub fn validate_pin(&self, pin: &str) -> Result<String, WebauthnCError> {
108        check_pin(pin, self.info.get_min_pin_length())
109    }
110
111    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
112    /// Sets a PIN on a device which does not already have one.
113    ///
114    /// To change a PIN, use [`change_pin()`][Self::change_pin].
115    pub async fn set_new_pin(&mut self, pin: &str) -> Result<(), WebauthnCError> {
116        let ui_callback = self.ui_callback;
117        let pin = self.validate_pin(pin)?;
118
119        let mut padded_pin: [u8; 64] = [0; 64];
120        padded_pin[..pin.len()].copy_from_slice(pin.as_bytes());
121
122        let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
123
124        let p = iface.get_key_agreement_cmd();
125        let ret = self.token.transmit(p, ui_callback).await?;
126        let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
127        trace!(?key_agreement);
128
129        // The platform calls encapsulate with the public key that the authenticator
130        // returned in order to generate the platform key-agreement key and the shared secret.
131        let shared_secret = iface.encapsulate(key_agreement)?;
132        trace!(?shared_secret);
133
134        let set_pin = iface.set_pin_cmd(padded_pin, shared_secret.as_slice())?;
135        let ret = self.token.transmit(set_pin, ui_callback).await?;
136        trace!(?ret);
137
138        // Setting a PIN invalidates info.
139        self.refresh_info().await?;
140        Ok(())
141    }
142
143    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
144    /// Changes a PIN on a device.
145    ///
146    /// To set a PIN for the first time, use [`set_new_pin()`][Self::set_new_pin].
147    pub async fn change_pin(&mut self, old_pin: &str, new_pin: &str) -> Result<(), WebauthnCError> {
148        let ui_callback = self.ui_callback;
149
150        // TODO: we actually really only need this in normal form C
151        let old_pin = self.validate_pin(old_pin)?;
152        let new_pin = self.validate_pin(new_pin)?;
153        let mut padded_pin: [u8; 64] = [0; 64];
154        padded_pin[..new_pin.len()].copy_from_slice(new_pin.as_bytes());
155
156        let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
157
158        let p = iface.get_key_agreement_cmd();
159        let ret = self.token.transmit(p, ui_callback).await?;
160        let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
161        let shared_secret = iface.encapsulate(key_agreement)?;
162
163        let change_pin = iface.change_pin_cmd(&old_pin, padded_pin, &shared_secret)?;
164        let ret = self.token.transmit(change_pin, ui_callback).await?;
165        trace!(?ret);
166
167        // Changing a PIN invalidates forcePinChange option.
168        self.refresh_info().await?;
169        Ok(())
170    }
171
172    /// Gets a PIN/UV auth token, if required.
173    ///
174    /// This automatically selects an appropriate verification mode.
175    ///
176    /// ## Arguments
177    ///
178    /// * `client_data_hash`: the SHA256 hash of the client data JSON.
179    ///
180    /// * `permissions`: a bitmask of permissions to request. If this argument
181    ///   is not set, the library will always use
182    ///   [legacy `getPinToken` authentication][getPinToken].
183    ///
184    /// * `rp_id`: the Relying Party to associate with the request (permissions
185    ///   RP ID).
186    ///
187    ///   This argument is required if requesting [`GET_ASSERTION`][] and/or
188    ///   [`MAKE_CREDENTIAL`][] `permissions`, and is optional if requesting
189    ///   [`CREDENTIAL_MANAGEMENT`][] `permissions`.
190    ///
191    ///   This argument must not be set if `permissions` is empty.
192    ///
193    /// * `user_verification_policy`: how to verify the user.
194    ///
195    /// ## Returns
196    ///
197    /// * `Option<u32>`: the `pin_uv_auth_protocol`
198    /// * `Option<Vec<u8>>`: the `pin_uv_auth_param`
199    /// * `Ok((None, None))` if PIN and/or UV auth is not required.
200    /// * `Err(UserVerificationRequired)` if user verification was required, but
201    ///   was not available.
202    /// * `Err` for errors from the token.
203    ///
204    /// ## Permissions and CTAP versions
205    ///
206    /// The `permissions` and `rp_id` arguments are **only** enforced when the
207    /// authenticator sets the `pinUvAuthToken` option, which is a
208    /// [mandatory feature][] in CTAP 2.1 and later.
209    ///
210    /// This **will not be enforced** for CTAP 2.0 and 2.1-PRE authenticators,
211    /// and automatically fall back to
212    /// [legacy `getPinToken` authentication][getPinToken].
213    ///
214    /// While this API follows CTAP 2.1 semantics, these are only weakly
215    /// enforced, making it important to test your application with CTAP 2.0,
216    /// 2.1-PRE and 2.1 authenticators.
217    ///
218    /// ## References
219    ///
220    /// * [Operations to Obtain a `pinUvAuthToken`][gettingPinUvAuthToken]
221    ///
222    /// [getPinToken]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#getPinToken
223    /// [gettingPinUvAuthToken]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#gettingPinUvAuthToken
224    /// [mandatory feature]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#mandatory-features
225    /// [`CREDENTIAL_MANAGEMENT`]: Permissions::CREDENTIAL_MANAGEMENT
226    /// [`GET_ASSERTION`]: Permissions::GET_ASSERTION
227    /// [`MAKE_CREDENTIAL`]: Permissions::MAKE_CREDENTIAL
228    pub(super) async fn get_pin_uv_auth_token(
229        &mut self,
230        client_data_hash: &[u8],
231        permissions: Permissions,
232        rp_id: Option<String>,
233        user_verification_policy: UserVerificationPolicy,
234    ) -> Result<AuthToken, WebauthnCError> {
235        let session = self
236            .get_pin_uv_auth_session(permissions, rp_id, user_verification_policy)
237            .await?;
238
239        Ok(match session {
240            AuthSession::InterfaceToken(iface, pin_token) => {
241                let pin_uv_auth_param =
242                    iface.authenticate(pin_token.as_slice(), client_data_hash)?;
243                AuthToken::ProtocolToken(iface.get_pin_uv_protocol(), pin_uv_auth_param)
244            }
245
246            AuthSession::None => AuthToken::None,
247            AuthSession::UvTrue => AuthToken::UvTrue,
248        })
249    }
250
251    pub(super) async fn get_pin_uv_auth_session(
252        &mut self,
253        permissions: Permissions,
254        rp_id: Option<String>,
255        user_verification_policy: UserVerificationPolicy,
256    ) -> Result<AuthSession, WebauthnCError> {
257        #[derive(Debug)]
258        enum PlannedOperation {
259            UvAuthTokenUsingUvWithPermissions,
260            UvAuthTokenUsingPinWithPermissions,
261            Token,
262        }
263
264        if permissions.intersects(Permissions::MAKE_CREDENTIAL | Permissions::GET_ASSERTION)
265            && rp_id.is_none()
266        {
267            error!("rp_id is required for MakeCredential and GetAssertion requests");
268            return Err(WebauthnCError::Internal);
269        }
270
271        if rp_id.is_some() && permissions.is_empty() {
272            error!("rp_id specified with no permissions");
273            return Err(WebauthnCError::Internal);
274        }
275
276        trace!("Authenticator options: {:?}", self.info.options);
277        let ui_callback = self.ui_callback;
278        let client_pin = self.info.get_option("clientPin").unwrap_or_default();
279        let mut always_uv = self.info.get_option("alwaysUv").unwrap_or_default();
280        let make_cred_uv_not_required = self.info.make_cred_uv_not_required();
281        let pin_uv_auth_token = self.info.get_option("pinUvAuthToken").unwrap_or_default();
282        let uv = self.info.get_option("uv").unwrap_or_default();
283        // requesting the acfg permission when invoking
284        // getPinUvAuthTokenUsingUvWithPermissions is supported.
285        let uv_acfg = self.info.get_option("uvAcfg").unwrap_or_default();
286        // requesting the be permission when invoking
287        // getPinUvAuthTokenUsingUvWithPermissions is supported.
288        let uv_bio_enroll = self.info.get_option("uvBioEnroll").unwrap_or_default();
289        // TODO: noMcGaPermissionsWithClientPin means those can only run with biometric auth
290        // TODO: rp_options.uv_required == true > makeCredUvNotRqd == true
291        // TODO: discoverable credentials should bypass makeCredUvNotRqd == true
292
293        // Allow toggleAlwaysUv to bypass alwaysUv if no user verification is
294        // configured, to allow for initial configuration.
295        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorConfig
296        if permissions == Permissions::AUTHENTICATOR_CONFIGURATION
297            && user_verification_policy == UserVerificationPolicy::Discouraged_DO_NOT_USE
298            && !client_pin
299            && !uv
300            && always_uv
301        {
302            trace!(
303                "Pretending alwaysUv = false to allow for initial configuration of toggleAlwaysUv"
304            );
305            always_uv = false;
306        }
307
308        let requires_pin = (permissions.intersects(Permissions::BIO_ENROLLMENT) && !uv_bio_enroll)
309            || (permissions.intersects(Permissions::AUTHENTICATOR_CONFIGURATION) && !uv_acfg)
310            || permissions.intersects(Permissions::CREDENTIAL_MANAGEMENT);
311        trace!("Permissions: {permissions:?}, uvBioEnroll: {uv_bio_enroll:?}, uvAcfg: {uv_acfg:?}, requiresPin: {requires_pin:?}");
312        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-platf-actions
313        // 1. If the authenticator is protected by some form of user
314        // verification, or the Relying Party prefers enforcing user
315        // verification (e.g., by setting
316        // options.authenticatorSelection.userVerification to "required", or
317        // "preferred" in the WebAuthn API):
318
319        trace!("uvPolicy: {user_verification_policy:?}, clientPin: {client_pin:?}, pinUvAuthToken: {pin_uv_auth_token:?}, uv: {uv:?}, alwaysUv: {always_uv:?}, makeCredUvNotRequired: {make_cred_uv_not_required:?}");
320        if user_verification_policy == UserVerificationPolicy::Required
321            || user_verification_policy == UserVerificationPolicy::Preferred
322            || client_pin
323            || uv
324            // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-feature-descriptions-alwaysUv
325            || always_uv
326        // TODO: Implement makeCredUvNotReqd when this supports discoverable creds
327        {
328            // Skip step 1 (we don't already have a parameter)
329            // 2. Otherwise, the platform examines various option IDs in the
330            // authenticatorGetInfo response to determine its course of action:
331
332            let planned_operation = if uv && !requires_pin {
333                // 1. If the uv option ID is present and set to true:
334                if pin_uv_auth_token {
335                    // If the pinUvAuthToken option ID is present and true then
336                    // plan to use getPinUvAuthTokenUsingUvWithPermissions to
337                    // obtain a pinUvAuthToken, and let it be the selected
338                    // operation. Go to Step 1.1.2.3.
339                    PlannedOperation::UvAuthTokenUsingUvWithPermissions
340                } else {
341                    trace!("pinUvAuthToken not supported, planning to use uv=true");
342                    return Ok(AuthSession::UvTrue);
343                }
344            } else {
345                // 2. Else (implying the uv option ID is present and set to
346                // false or absent):
347                if pin_uv_auth_token {
348                    // 1. If the pinUvAuthToken option ID is present and true:
349                    // To continue, ensure the clientPin option ID is present
350                    // and true. Plan to use
351                    // getPinUvAuthTokenUsingPinWithPermissions to obtain a
352                    // pinUvAuthToken, and let it be the selected operation. Go
353                    // to Step 1.1.2.3.
354                    if !client_pin {
355                        error!(
356                            "Client PIN not set, and user verification is preferred or required"
357                        );
358                        return Err(WebauthnCError::UserVerificationRequired);
359                    }
360
361                    PlannedOperation::UvAuthTokenUsingPinWithPermissions
362                } else {
363                    // 2. Else (implying the pinUvAuthToken option ID is absent):
364                    // To continue, ensure the clientPin option ID is present
365                    // and true. Plan to use getPinToken to obtain a
366                    // pinUvAuthToken, and let it be the selected operation.
367                    if !client_pin {
368                        error!(
369                            "Client PIN not set, and user verification is preferred or required"
370                        );
371                        return Err(WebauthnCError::UserVerificationRequired);
372                    }
373                    PlannedOperation::Token
374                }
375            };
376
377            trace!(?planned_operation);
378
379            // Step 1.1.2.3: In preparation for obtaining pinUvAuthToken, the
380            // platform:
381            let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
382
383            // 1. Obtains a shared secret
384            // 6.5.5.4: Obtaining the shared secret
385            let p = iface.get_key_agreement_cmd();
386            let ret = self.token.transmit(p, ui_callback).await?;
387            let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
388            trace!(?key_agreement);
389
390            // The platform calls encapsulate with the public key that the authenticator
391            // returned in order to generate the platform key-agreement key and the shared secret.
392            let shared_secret = iface.encapsulate(key_agreement)?;
393            trace!(?shared_secret);
394
395            // Then the platform obtains a pinUvAuthToken from the
396            // authenticator, with the mc (and likely also with the ga)
397            // permission (see "pre-flight", mentioned above), using the
398            // selected operation. If successful, the platform creates the
399            // pinUvAuthParam parameter by calling authenticate(pinUvAuthToken,
400            // clientDataHash), and goes to Step 1.1.1.
401            let p = match planned_operation {
402                PlannedOperation::UvAuthTokenUsingUvWithPermissions => {
403                    // 6.5.5.7.3. Getting pinUvAuthToken using getPinUvAuthTokenUsingUvWithPermissions (built-in user verification methods)
404                    iface.get_pin_uv_auth_token_using_uv_with_permissions_cmd(permissions, rp_id)
405                }
406                PlannedOperation::UvAuthTokenUsingPinWithPermissions => {
407                    // 6.5.5.7.2. Getting pinUvAuthToken using getPinUvAuthTokenUsingPinWithPermissions (ClientPIN)
408                    let pin = self.request_pin(iface.get_pin_uv_protocol()).await?;
409                    iface.get_pin_uv_auth_token_using_pin_with_permissions_cmd(
410                        &pin,
411                        shared_secret.as_slice(),
412                        permissions,
413                        rp_id,
414                    )?
415                }
416                PlannedOperation::Token => {
417                    // 6.5.5.7.1. Getting pinUvAuthToken using getPinToken (superseded)
418                    let pin = self.request_pin(iface.get_pin_uv_protocol()).await?;
419                    iface.get_pin_token_cmd(&pin, shared_secret.as_slice())?
420                }
421            };
422
423            let ret = self.token.transmit(p, ui_callback).await?;
424            trace!(?ret);
425            let pin_token = ret
426                .pin_uv_auth_token
427                .ok_or(WebauthnCError::MissingRequiredField)?;
428            // Decrypt the pin_token
429            let pin_token = iface.decrypt(shared_secret.as_slice(), pin_token.as_slice())?;
430            trace!(?pin_token);
431            Ok(AuthSession::InterfaceToken(iface, pin_token))
432        } else {
433            // Otherwise, implying the authenticator is not presently protected
434            // by some form of user verification, or the Relying Party wants to
435            // create a non-discoverable credential and not require user
436            // verification (e.g., by setting
437            // options.authenticatorSelection.userVerification to "discouraged"
438            // in the WebAuthn API), the platform invokes the
439            // authenticatorMakeCredential operation using the marshalled input
440            // parameters along with the "uv" option key set to false and
441            // terminate these steps.
442            trace!("User verification disabled");
443            Ok(AuthSession::None)
444        }
445    }
446
447    async fn request_pin(&mut self, pin_uv_protocol: u32) -> Result<String, WebauthnCError> {
448        let p = ClientPinRequest {
449            pin_uv_protocol: Some(pin_uv_protocol),
450            sub_command: ClientPinSubCommand::GetPinRetries,
451            ..Default::default()
452        };
453
454        let ui_callback = self.ui_callback;
455        let ret = self.token.transmit(p, ui_callback).await?;
456        trace!(?ret);
457
458        // TODO: handle lockouts
459
460        ui_callback.request_pin().ok_or(WebauthnCError::Cancelled)
461    }
462
463    /// Prompt for user presence on an authenticator.
464    ///
465    /// On CTAP 2.1 authenticators, this sends a [SelectionRequest].
466    ///
467    /// On CTAP 2.0 and 2.1-PRE authenticators (where there is no
468    /// [SelectionRequest]), this performs a [MakeCredentialRequest] with
469    /// *invalid* PIN/UV auth parameters, using the process described in CTAP
470    /// 2.1 [§ 6.1.2 authenticatorMakeCredential Algorithm][0] step 1.
471    ///
472    /// While this *shouldn't* result in an authenticator lock-out (according to
473    /// the spec), it has been observed that some authenticators will decrement
474    /// their `pinAttempts` counter.
475    ///
476    /// ## References
477    ///
478    /// * CTAP 2.1 [§6.1.2 authenticatorMakeCredential Algorithm][0], step 1.
479    ///
480    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg
481    pub async fn selection(&mut self) -> Result<(), WebauthnCError> {
482        if !self.token.has_button() {
483            // The token doesn't have a button on a transport level (ie: NFC),
484            // so immediately mark this as the "selected" token.
485            return Ok(());
486        }
487
488        if self
489            .info
490            .versions
491            .contains(Ctap21Authenticator::<'a, T, U>::VERSION)
492        {
493            let ui_callback = self.ui_callback;
494            return self
495                .token
496                .transmit(SelectionRequest {}, ui_callback)
497                .await
498                .map(|_| ());
499        }
500
501        let mc = MakeCredentialRequest {
502            client_data_hash: vec![0; size_of::<SHA256Hash>()],
503            rp: RelyingParty {
504                id: "SELECTION".to_string(),
505                name: "SELECTION".to_string(),
506            },
507            user: User {
508                id: Base64UrlSafeData::from(vec![0]),
509                name: "SELECTION".to_string(),
510                display_name: "SELECTION".to_string(),
511            },
512            pub_key_cred_params: vec![
513                PubKeyCredParams {
514                    type_: "public-key".to_owned(),
515                    alg: -7,
516                },
517                PubKeyCredParams {
518                    type_: "public-key".to_owned(),
519                    alg: -257,
520                },
521                PubKeyCredParams {
522                    type_: "public-key".to_owned(),
523                    alg: -37,
524                },
525            ],
526            exclude_list: vec![],
527            options: None,
528            pin_uv_auth_param: Some(vec![]),
529            // https://github.com/kanidm/webauthn-rs/issues/485
530            // According to CTAP 2.0 and 2.1 spec setting `pin_uv_auth_proto`
531            // should not be needed. But when unset, Titan v2 and Token2 respond
532            // with MissingParameter, and Titan v2 doesn't even wait for touch.
533            // Windows 10 and Safari 18.4 both always set this.
534            pin_uv_auth_proto: Some(1),
535            enterprise_attest: None,
536        };
537
538        let ret = self.token.transmit(mc, self.ui_callback).await;
539
540        if let Err(WebauthnCError::Ctap(e)) = ret {
541            // https://github.com/kanidm/webauthn-rs/issues/485#issuecomment-2798457327
542            // CTAP 2.0 spec says to expect PinNotSet or PinInvalid. Many
543            // authenticators send PinAuthInvalid.
544            if e == CtapError::Ctap2PinAuthInvalid
545                || e == CtapError::Ctap2PinNotSet
546                || e == CtapError::Ctap2PinInvalid
547            {
548                // User pressed the button
549                return Ok(());
550            }
551
552            error!("unexpected error from authenticator: {e:?}");
553            return Err(WebauthnCError::Ctap(e));
554        } else {
555            // Some other error
556            ret?;
557        }
558
559        error!("got unexpected OK response from authenticator");
560        Err(WebauthnCError::Internal)
561    }
562}
563
564impl<T: Token, U: UiCallback> AuthenticatorBackendHashedClientData
565    for Ctap20Authenticator<'_, T, U>
566{
567    fn perform_register(
568        &mut self,
569        client_data_hash: Vec<u8>,
570        options: webauthn_rs_proto::PublicKeyCredentialCreationOptions,
571        _timeout_ms: u32,
572    ) -> Result<webauthn_rs_proto::RegisterPublicKeyCredential, crate::prelude::WebauthnCError>
573    {
574        let authenticator_selection = options.authenticator_selection.unwrap_or_default();
575        let auth_token = block_on(self.get_pin_uv_auth_token(
576            client_data_hash.as_slice(),
577            Permissions::MAKE_CREDENTIAL,
578            Some(options.rp.id.clone()),
579            authenticator_selection.user_verification,
580        ))?;
581
582        let req_options = if let AuthToken::UvTrue = auth_token {
583            // No pin_uv_auth_param, but verification is configured, so use it
584            Some(BTreeMap::from([("uv".to_owned(), true)]))
585        } else {
586            None
587        };
588        let (pin_uv_auth_proto, pin_uv_auth_param) = auth_token.into_pin_uv_params();
589
590        let mc = MakeCredentialRequest {
591            client_data_hash,
592            rp: options.rp,
593            user: options.user,
594            pub_key_cred_params: options.pub_key_cred_params,
595            exclude_list: options.exclude_credentials.unwrap_or_default(),
596
597            options: req_options,
598            pin_uv_auth_param,
599            pin_uv_auth_proto,
600            enterprise_attest: None,
601        };
602
603        let ret = block_on(self.token.transmit(mc, self.ui_callback))?;
604        trace!(?ret);
605
606        // The obvious thing to do here would be to pass the raw authenticator
607        // data back, but it seems like everything expects a Map<String, Value>
608        // here, rather than a Map<u32, Value>... so we need to re-serialize
609        // that data!
610        //
611        // Alternatively, it may be possible to do this "more cheaply" by
612        // remapping the keys of the map.
613        let raw = serde_cbor_2::to_vec(&ret).map_err(|e| {
614            error!("MakeCredentialResponse re-serialization: {:?}", e);
615            WebauthnCError::Cbor
616        })?;
617
618        // HACK: parsing out the real ID is complicated, and other parts of the
619        // library will do it for us, so we'll put in empty data here.
620        let cred_id = vec![];
621        let id = String::new();
622
623        let type_ = ret.fmt.ok_or(WebauthnCError::InvalidAlgorithm)?;
624
625        Ok(RegisterPublicKeyCredential {
626            id,
627            raw_id: Base64UrlSafeData::from(cred_id),
628            type_,
629            extensions: RegistrationExtensionsClientOutputs::default(), // TODO
630            response: AuthenticatorAttestationResponseRaw {
631                attestation_object: Base64UrlSafeData::from(raw),
632                client_data_json: Base64UrlSafeData::new(),
633                // All transports the token supports, as opposed to the
634                // transport which was actually used.
635                transports: self.info.get_transports(),
636            },
637        })
638    }
639
640    fn perform_auth(
641        &mut self,
642        client_data_hash: Vec<u8>,
643        options: webauthn_rs_proto::PublicKeyCredentialRequestOptions,
644        _timeout_ms: u32,
645    ) -> Result<webauthn_rs_proto::PublicKeyCredential, crate::prelude::WebauthnCError> {
646        trace!("trying to authenticate...");
647        let auth_token = block_on(self.get_pin_uv_auth_token(
648            client_data_hash.as_slice(),
649            Permissions::GET_ASSERTION,
650            Some(options.rp_id.clone()),
651            options.user_verification,
652        ))?;
653
654        let req_options = if let AuthToken::UvTrue = auth_token {
655            // No pin_uv_auth_param, but verification is configured, so use it
656            Some(BTreeMap::from([("uv".to_owned(), true)]))
657        } else {
658            None
659        };
660        let (pin_uv_auth_proto, pin_uv_auth_param) = auth_token.into_pin_uv_params();
661
662        let ga = GetAssertionRequest {
663            rp_id: options.rp_id,
664            client_data_hash,
665            allow_list: options.allow_credentials,
666            options: req_options,
667            pin_uv_auth_param,
668            pin_uv_auth_proto,
669        };
670
671        trace!(?ga);
672        let ret = block_on(self.token.transmit(ga, self.ui_callback))?;
673        trace!(?ret);
674
675        let raw_id = ret
676            .credential
677            .as_ref()
678            .map(|c| c.id.to_owned())
679            .ok_or(WebauthnCError::Cbor)?;
680        let type_ = ret
681            .credential
682            .map(|c| c.type_)
683            .ok_or(WebauthnCError::Cbor)?;
684        let signature = Base64UrlSafeData::from(ret.signature.ok_or(WebauthnCError::Cbor)?);
685        let authenticator_data =
686            Base64UrlSafeData::from(ret.auth_data.ok_or(WebauthnCError::Cbor)?);
687
688        Ok(PublicKeyCredential {
689            id: BASE64_ENGINE.encode(&raw_id),
690            raw_id,
691            response: AuthenticatorAssertionResponseRaw {
692                authenticator_data,
693                client_data_json: Base64UrlSafeData::new(),
694                signature,
695                // TODO
696                user_handle: None,
697            },
698            // TODO
699            extensions: AuthenticationExtensionsClientOutputs::default(),
700            type_,
701        })
702    }
703}
704
705#[cfg(test)]
706mod tests {
707    // TODO
708}