webauthn_authenticator_rs/ctap2/
ctap21.rs

1use crate::{
2    ctap2::{
3        commands::GetInfoResponse, ctap21_bio::BiometricAuthenticatorInfo,
4        ctap21_cred::CredentialManagementAuthenticatorInfo, internal::CtapAuthenticatorVersion,
5        Ctap20Authenticator,
6    },
7    transport::Token,
8    ui::UiCallback,
9};
10#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
11use crate::{
12    ctap2::{
13        commands::{
14            BioEnrollmentRequest, ConfigRequest, ConfigSubCommand, CredSubCommand,
15            CredentialManagementRequest, Permissions, PublicKeyCredentialDescriptorCM,
16            SetMinPinLengthParams, UserCM,
17        },
18        ctap21_cred::CredentialManagementAuthenticatorSupport,
19        CredentialManagementAuthenticator,
20    },
21    error::WebauthnCError,
22};
23use std::ops::{Deref, DerefMut};
24#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
25use webauthn_rs_proto::UserVerificationPolicy;
26
27/// CTAP 2.1 protocol implementation.
28///
29/// This contains only CTAP 2.1-specific functionality. All CTAP 2.0
30/// functionality is avaliable via a [Deref] to [Ctap20Authenticator].
31#[derive(Debug)]
32pub struct Ctap21Authenticator<'a, T: Token, U: UiCallback> {
33    authenticator: Ctap20Authenticator<'a, T, U>,
34}
35
36/// For backwards compatibility, pretend to be a
37/// [CTAP 2.0 authenticator][Ctap20Authenticator].
38impl<'a, T: Token, U: UiCallback> Deref for Ctap21Authenticator<'a, T, U> {
39    type Target = Ctap20Authenticator<'a, T, U>;
40
41    fn deref(&self) -> &Self::Target {
42        &self.authenticator
43    }
44}
45
46impl<T: Token, U: UiCallback> DerefMut for Ctap21Authenticator<'_, T, U> {
47    fn deref_mut(&mut self) -> &mut Self::Target {
48        &mut self.authenticator
49    }
50}
51
52impl<'a, T: Token, U: UiCallback> CtapAuthenticatorVersion<'a, T, U>
53    for Ctap21Authenticator<'a, T, U>
54{
55    const VERSION: &'static str = "FIDO_2_1";
56    fn new_with_info(info: GetInfoResponse, token: T, ui_callback: &'a U) -> Self {
57        Self {
58            authenticator: Ctap20Authenticator::new_with_info(info, token, ui_callback),
59        }
60    }
61}
62
63impl<'a, T: Token, U: UiCallback> Ctap21Authenticator<'a, T, U> {
64    /// Returns `true` if the authenticator supports configuration commands.
65    ///
66    /// # See also
67    ///
68    /// * [`enable_enterprise_attestation()`][Self::enable_enterprise_attestation]
69    /// * [`set_min_pin_length()`][Self::set_min_pin_length]
70    /// * [`toggle_always_uv()`][Self::toggle_always_uv]
71    #[inline]
72    pub fn supports_config(&self) -> bool {
73        self.info.supports_config()
74    }
75
76    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
77    async fn config(
78        &mut self,
79        sub_command: ConfigSubCommand,
80        toggle_always_uv: bool,
81    ) -> Result<(), WebauthnCError> {
82        if !self.supports_config() {
83            return Err(WebauthnCError::NotSupported);
84        }
85
86        let ui_callback = self.ui_callback;
87
88        let (pin_uv_auth_proto, pin_uv_auth_param) = self
89            .get_pin_uv_auth_token(
90                sub_command.prf().as_slice(),
91                Permissions::AUTHENTICATOR_CONFIGURATION,
92                None,
93                if toggle_always_uv {
94                    UserVerificationPolicy::Discouraged_DO_NOT_USE
95                } else {
96                    UserVerificationPolicy::Required
97                },
98            )
99            .await?
100            .into_pin_uv_params();
101
102        // TODO: handle complex result type
103        self.token
104            .transmit(
105                ConfigRequest::new(sub_command, pin_uv_auth_proto, pin_uv_auth_param),
106                ui_callback,
107            )
108            .await?;
109
110        self.refresh_info().await
111    }
112
113    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
114    /// Toggles the state of the [Always Require User Verification][0] feature.
115    ///
116    /// This is only available on authenticators which
117    /// [support configuration][Self::supports_config].
118    ///
119    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#toggle-alwaysUv
120    pub async fn toggle_always_uv(&mut self) -> Result<(), WebauthnCError> {
121        self.config(ConfigSubCommand::ToggleAlwaysUv, true).await
122    }
123
124    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
125    /// Sets a [minimum PIN length policy][0].
126    ///
127    /// This is only available on authenticators which
128    /// [support configuration][Self::supports_config].
129    ///
130    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#setMinPINLength
131    pub async fn set_min_pin_length(
132        &mut self,
133        new_min_pin_length: Option<u32>,
134        min_pin_length_rpids: Vec<String>,
135        force_change_pin: Option<bool>,
136    ) -> Result<(), WebauthnCError> {
137        self.config(
138            ConfigSubCommand::SetMinPinLength(SetMinPinLengthParams {
139                new_min_pin_length,
140                min_pin_length_rpids,
141                force_change_pin,
142            }),
143            false,
144        )
145        .await
146    }
147
148    /// Returns `true` if the authenticator supports
149    /// [enterprise attestation][0].
150    ///
151    /// # See also
152    ///
153    /// * [`enable_enterprise_attestation()`][Self::enable_enterprise_attestation]
154    ///
155    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-feature-descriptions-enterp-attstn
156    #[inline]
157    pub fn supports_enterprise_attestation(&self) -> bool {
158        self.info.supports_enterprise_attestation()
159    }
160
161    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
162    /// Enables the [Enterprise Attestation][0] feature.
163    ///
164    /// This is only available on authenticators which support
165    /// [configuration][Self::supports_config] and
166    /// [enterprise attestation][Self::supports_enterprise_attestation].
167    ///
168    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-feature-descriptions-enterp-attstn
169    pub async fn enable_enterprise_attestation(&mut self) -> Result<(), WebauthnCError> {
170        if !self.supports_enterprise_attestation() || !self.supports_config() {
171            return Err(WebauthnCError::NotSupported);
172        }
173        self.config(ConfigSubCommand::EnableEnterpriseAttestation, false)
174            .await
175    }
176
177    /// Returns `true` if the authenticator supports
178    /// [CTAP 2.1 credential management][0].
179    ///
180    /// ## See also
181    ///
182    /// * [`CredentialManagementAuthenticator`][]
183    /// * [`update_credential_user`][Self::update_credential_user]
184    ///
185    /// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorCredentialManagement
186    #[inline]
187    pub fn supports_ctap21_credential_management(&self) -> bool {
188        self.info.ctap21_credential_management()
189    }
190
191    /// Updates user information for a discoverable credential.
192    ///
193    /// This is only available on authenticators which support
194    /// [CTAP 2.1 credential management][Self::supports_ctap21_credential_management],
195    /// otherwise it returns [`WebauthnCError::NotSupported`].
196    ///
197    /// ## Note
198    ///
199    /// This function does not provide a "permissions RP ID" with the request,
200    /// as it only works correctly with authenticators supporting the
201    /// `pinUvAuthToken` feature.
202    ///
203    /// ## See also
204    ///
205    /// * [`CredentialManagementAuthenticator`] trait
206    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
207    pub async fn update_credential_user(
208        &mut self,
209        credential_id: PublicKeyCredentialDescriptorCM,
210        user: UserCM,
211    ) -> Result<(), WebauthnCError> {
212        self.check_credential_management_support()?;
213
214        self.cred_mgmt(CredSubCommand::UpdateUserInformation(credential_id, user))
215            .await
216            .map(|_| ())
217    }
218}
219
220impl<T: Token, U: UiCallback> BiometricAuthenticatorInfo<U> for Ctap21Authenticator<'_, T, U> {
221    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
222    type RequestType = BioEnrollmentRequest;
223
224    #[inline]
225    fn biometrics(&self) -> Option<bool> {
226        self.info.ctap21_biometrics()
227    }
228}
229
230impl<T: Token, U: UiCallback> CredentialManagementAuthenticatorInfo<U>
231    for Ctap21Authenticator<'_, T, U>
232{
233    #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
234    type RequestType = CredentialManagementRequest;
235    // HACK: type RequestType = super::commands::PrototypeCredentialManagementRequest;
236
237    #[inline]
238    fn supports_credential_management(&self) -> bool {
239        self.supports_ctap21_credential_management()
240    }
241}