bitwarden_fido/
client.rs

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