bitwarden_fido/
client.rs

1use passkey::client::WebauthnError;
2use thiserror::Error;
3
4use super::{
5    Fido2Authenticator, PublicKeyCredentialAuthenticatorAssertionResponse,
6    PublicKeyCredentialAuthenticatorAttestationResponse,
7    authenticator::GetSelectedCredentialError,
8    get_string_name_from_enum,
9    types::{
10        AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData,
11        ClientExtensionResults, CredPropsResult, Origin,
12    },
13};
14use crate::types::InvalidOriginError;
15
16#[allow(missing_docs)]
17#[derive(Debug, Error)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
19pub enum Fido2ClientError {
20    #[error(transparent)]
21    InvalidOrigin(#[from] InvalidOriginError),
22    #[error(transparent)]
23    Serde(#[from] serde_json::Error),
24    #[error(transparent)]
25    GetSelectedCredential(#[from] GetSelectedCredentialError),
26
27    #[error("Webauthn error: {0:?}")]
28    Webauthn(WebauthnError),
29}
30
31impl From<WebauthnError> for Fido2ClientError {
32    fn from(e: WebauthnError) -> Self {
33        Self::Webauthn(e)
34    }
35}
36
37#[allow(missing_docs)]
38pub struct Fido2Client<'a> {
39    pub authenticator: Fido2Authenticator<'a>,
40}
41
42impl Fido2Client<'_> {
43    #[allow(missing_docs)]
44    pub async fn register(
45        &mut self,
46        origin: Origin,
47        request: String,
48        client_data: ClientData,
49    ) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse, Fido2ClientError> {
50        let origin: passkey::client::Origin = origin.try_into()?;
51        let request: passkey::types::webauthn::CredentialCreationOptions =
52            serde_json::from_str(&request)?;
53
54        // Insert the received UV to be able to return it later in check_user
55        let uv = request
56            .public_key
57            .authenticator_selection
58            .as_ref()
59            .map(|s| s.user_verification.into());
60        *self
61            .authenticator
62            .requested_uv
63            .get_mut()
64            .expect("Mutex is not poisoned") = uv;
65
66        let rp_id = request.public_key.rp.id.clone();
67
68        let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(true));
69        let result = client.register(origin, request, client_data).await?;
70
71        Ok(PublicKeyCredentialAuthenticatorAttestationResponse {
72            id: result.id,
73            raw_id: result.raw_id.into(),
74            ty: get_string_name_from_enum(result.ty)?,
75            authenticator_attachment: result
76                .authenticator_attachment
77                .map(get_string_name_from_enum)
78                .transpose()?,
79            client_extension_results: ClientExtensionResults {
80                cred_props: result.client_extension_results.cred_props.map(Into::into),
81            },
82            response: AuthenticatorAttestationResponse {
83                client_data_json: result.response.client_data_json.into(),
84                authenticator_data: result.response.authenticator_data.into(),
85                public_key: result.response.public_key.map(|x| x.into()),
86                public_key_algorithm: result.response.public_key_algorithm,
87                attestation_object: result.response.attestation_object.into(),
88                transports: if rp_id.unwrap_or_default() == "https://google.com" {
89                    Some(vec!["internal".to_string(), "usb".to_string()])
90                } else {
91                    Some(vec!["internal".to_string()])
92                },
93            },
94            selected_credential: self.authenticator.get_selected_credential()?,
95        })
96    }
97
98    #[allow(missing_docs)]
99    pub async fn authenticate(
100        &mut self,
101        origin: Origin,
102        request: String,
103        client_data: ClientData,
104    ) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse, Fido2ClientError> {
105        let origin: passkey::client::Origin = origin.try_into()?;
106        let request: passkey::types::webauthn::CredentialRequestOptions =
107            serde_json::from_str(&request)?;
108
109        // Insert the received UV to be able to return it later in check_user
110        let uv = request.public_key.user_verification.into();
111        self.authenticator
112            .requested_uv
113            .get_mut()
114            .expect("Mutex is not poisoned")
115            .replace(uv);
116
117        let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(false));
118        let result = client.authenticate(origin, request, client_data).await?;
119
120        Ok(PublicKeyCredentialAuthenticatorAssertionResponse {
121            id: result.id,
122            raw_id: result.raw_id.into(),
123            ty: get_string_name_from_enum(result.ty)?,
124
125            authenticator_attachment: result
126                .authenticator_attachment
127                .map(get_string_name_from_enum)
128                .transpose()?,
129            client_extension_results: ClientExtensionResults {
130                cred_props: result
131                    .client_extension_results
132                    .cred_props
133                    .map(|c| CredPropsResult {
134                        rk: c.discoverable,
135                        authenticator_display_name: c.authenticator_display_name,
136                    }),
137            },
138            response: AuthenticatorAssertionResponse {
139                client_data_json: result.response.client_data_json.into(),
140                authenticator_data: result.response.authenticator_data.into(),
141                signature: result.response.signature.into(),
142                user_handle: result.response.user_handle.unwrap_or_default().into(),
143            },
144            selected_credential: self.authenticator.get_selected_credential()?,
145        })
146    }
147}