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