Skip to main content

bitwarden_uniffi/platform/fido2/
device_auth_key.rs

1use std::sync::Arc;
2
3use bitwarden_core::platform::SecretVerificationRequest;
4use bitwarden_crypto::Kdf;
5use bitwarden_fido::{
6    DeviceAuthKeyError as BitDeviceAuthKeyError, DeviceAuthKeyGetAssertionResult,
7    DeviceAuthKeyMetadata, DeviceAuthKeyRecord, GetAssertionRequest,
8};
9
10use super::UniffiTraitBridge;
11use crate::error::{Error, Result};
12
13#[derive(uniffi::Object)]
14pub struct ClientDeviceAuthKeyAuthenticator {
15    pub(super) client: bitwarden_fido::ClientFido2,
16    pub(super) store: Arc<dyn DeviceAuthKeyStore>,
17}
18
19#[uniffi::export(async_runtime = "tokio")]
20impl ClientDeviceAuthKeyAuthenticator {
21    /// Create a device auth key by registering an unlock passkey and PRF keyset with the server.
22    /// The passkey private key and metadata will be stored on the device using the provided trait
23    /// implementation.
24    pub async fn create_device_auth_key(
25        &self,
26        client_name: String,
27        web_vault_url: String,
28        email: String,
29        secret_verification_request: SecretVerificationRequest,
30        kdf: Kdf,
31    ) -> Result<()> {
32        let mut store = UniffiTraitBridge(self.store.as_ref());
33        let mut authenticator = self.client.create_device_key_authenticator(&mut store);
34        authenticator
35            .create_device_auth_key(
36                client_name,
37                web_vault_url,
38                email,
39                secret_verification_request,
40                kdf,
41            )
42            .await
43            .map_err(Error::DeviceAuthKey)
44    }
45
46    /// Uses a device auth key to respond to the provided WebAuthn assertion request.
47    /// Satisfy the given FIDO assertion `request` using the device auth key.
48    /// The device auth key will be looked up from the
49    /// [ClientDeviceAuthKeyAuthenticator::store] provided in the initializer.
50    async fn assert_device_auth_key(
51        &self,
52        request: GetAssertionRequest,
53    ) -> Result<DeviceAuthKeyGetAssertionResult> {
54        let mut store = UniffiTraitBridge(self.store.as_ref());
55        let mut authenticator = self.client.create_device_key_authenticator(&mut store);
56        authenticator
57            .assert_device_auth_key(request)
58            .await
59            .map_err(Error::DeviceAuthKey)
60    }
61
62    /// Deletes a device auth key and unregisters it from the server.
63    async fn unregister_device_auth_key(
64        &self,
65        email: String,
66        secret_verification_request: SecretVerificationRequest,
67        kdf: Kdf,
68    ) -> Result<()> {
69        let mut store = UniffiTraitBridge(self.store.as_ref());
70        let mut authenticator = self.client.create_device_key_authenticator(&mut store);
71        authenticator
72            .unregister_device_auth_key(email, secret_verification_request, kdf)
73            .await
74            .map_err(Error::DeviceAuthKey)
75    }
76}
77
78#[uniffi::export(with_foreign)]
79#[async_trait::async_trait]
80pub trait DeviceAuthKeyStore: Send + Sync {
81    async fn create_record(
82        &self,
83        record: DeviceAuthKeyRecord,
84    ) -> Result<(), DeviceAuthKeyCallbackError>;
85    async fn create_metadata(
86        &self,
87        metadata: DeviceAuthKeyMetadata,
88    ) -> Result<(), DeviceAuthKeyCallbackError>;
89    async fn get_metadata(
90        &self,
91    ) -> Result<Option<DeviceAuthKeyMetadata>, DeviceAuthKeyCallbackError>;
92    async fn get_record(&self) -> Result<Option<DeviceAuthKeyRecord>, DeviceAuthKeyCallbackError>;
93    async fn delete_record_and_metadata(&self) -> Result<(), DeviceAuthKeyCallbackError>;
94}
95
96/// Copy of the DeviceAuthKeyStore trait for UniFFI purposes.
97/// See note on [UniffiTraitBridge].
98#[async_trait::async_trait]
99impl bitwarden_fido::DeviceAuthKeyStore for UniffiTraitBridge<&dyn DeviceAuthKeyStore> {
100    async fn create_record(
101        &mut self,
102        record: DeviceAuthKeyRecord,
103    ) -> Result<(), BitDeviceAuthKeyError> {
104        self.0.create_record(record).await.map_err(Into::into)
105    }
106
107    async fn create_metadata(
108        &mut self,
109        metadata: DeviceAuthKeyMetadata,
110    ) -> Result<(), BitDeviceAuthKeyError> {
111        self.0.create_metadata(metadata).await.map_err(Into::into)
112    }
113
114    async fn get_metadata(&self) -> Result<Option<DeviceAuthKeyMetadata>, BitDeviceAuthKeyError> {
115        self.0.get_metadata().await.map_err(Into::into)
116    }
117
118    async fn get_record(&self) -> Result<Option<DeviceAuthKeyRecord>, BitDeviceAuthKeyError> {
119        self.0.get_record().await.map_err(Into::into)
120    }
121
122    async fn delete_record_and_metadata(&mut self) -> Result<(), BitDeviceAuthKeyError> {
123        self.0
124            .delete_record_and_metadata()
125            .await
126            .map_err(Into::into)
127    }
128}
129
130/// Errors related to processing the device auth key.
131#[derive(Debug, thiserror::Error, uniffi::Error)]
132pub enum DeviceAuthKeyCallbackError {
133    /// Authenticator failed to produce a valid response.
134    #[error("The authenticator failed to produce a valid response")]
135    AuthenticatorFailure,
136
137    /// Failed to convert between Rust types.
138    #[error("Failed to convert between Rust types")]
139    Conversion,
140
141    /// Credential excluded.
142    #[error("The existing device auth key is already registered on the server.")]
143    CredentialExcluded,
144
145    /// The record identifier stored in metadata is not a valid UUID.
146    #[error("The record identifier is not a valid UUID")]
147    InvalidRecordIdentifier,
148
149    /// Invalid Web Vault URL specified.
150    #[error("Invalid Web Vault URL specified")]
151    InvalidWebVaultUrl,
152
153    /// No device auth key exists on this device.
154    #[error("No device auth key exists on this device")]
155    MissingDeviceAuthKey,
156
157    /// Failed to unregister device auth key from server.
158    #[error("Failed to unregister device auth key from server")]
159    UnregisterFailure,
160
161    /// Failed to de-/serialize COSE key data.
162    #[error("Failed to de-/serialize COSE key data")]
163    InvalidCoseKey,
164
165    /// An invalid public key credential descriptor was passed in the allow list.
166    #[error("An invalid public key credential descriptor was passed in the allow list")]
167    InvalidPublicKeyCredentialDescriptor,
168
169    /// A master password hash could not be generated for the given master password.
170    #[error("A master password hash could not be generated for the given master password")]
171    MasterPasswordHash,
172
173    /// Credential ID was not returned in the response and was not passed in the request.
174    #[error(
175        "No credential ID was returned in the response nor was a single credential ID passed in the request"
176    )]
177    MissingCredentialId,
178
179    /// No HMAC secret was returned with the credential.
180    #[error("No HMAC secret was returned with the credential")]
181    MissingHmacSecret,
182
183    /// User handle was not returned in the response.
184    #[error("User handle was not returned in the response")]
185    MissingUserHandle,
186
187    /// Feature is not yet implemented.
188    #[error("Feature is not yet implemented")]
189    NotImplemented,
190
191    /// Failed to retrieve the registration options from the server.
192    #[error("Failed to retrieve the registration options from the server")]
193    RetrieveRegistrationOptionsFailure,
194
195    /// Failed to generate rotateable key set from PRF output.
196    #[error("Failed to generate rotateable key set from PRF output")]
197    PrfFailure,
198
199    /// Failed to submit registration request to the server.
200    #[error("Failed to submit registration request to the server")]
201    SubmitRegistrationFailure,
202
203    /// User cancelled the operation.
204    #[error("User cancelled the operation")]
205    UserCancelled,
206
207    /// An unknown error occurred.
208    #[error("An unknown error occurred")]
209    Unknown {
210        /// Reason for the error.
211        reason: String,
212    },
213}
214
215// Need to implement this From<> impl in order to handle unexpected callback errors.  See the
216// following page in the Uniffi user guide:
217// <https://mozilla.github.io/uniffi-rs/foreign_traits.html#error-handling>
218impl From<uniffi::UnexpectedUniFFICallbackError> for DeviceAuthKeyCallbackError {
219    fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
220        Self::Unknown { reason: e.reason }
221    }
222}
223
224impl From<DeviceAuthKeyCallbackError> for BitDeviceAuthKeyError {
225    fn from(val: DeviceAuthKeyCallbackError) -> Self {
226        match val {
227            DeviceAuthKeyCallbackError::AuthenticatorFailure => Self::AuthenticatorFailure,
228            DeviceAuthKeyCallbackError::Conversion => Self::Conversion,
229            DeviceAuthKeyCallbackError::CredentialExcluded => Self::CredentialExcluded,
230            DeviceAuthKeyCallbackError::InvalidRecordIdentifier => Self::InvalidRecordIdentifier,
231            DeviceAuthKeyCallbackError::InvalidWebVaultUrl => Self::InvalidWebVaultUrl,
232            DeviceAuthKeyCallbackError::MissingDeviceAuthKey => Self::MissingDeviceAuthKey,
233            DeviceAuthKeyCallbackError::UnregisterFailure => Self::UnregisterFailure,
234            DeviceAuthKeyCallbackError::InvalidCoseKey => Self::InvalidCoseKey,
235            DeviceAuthKeyCallbackError::InvalidPublicKeyCredentialDescriptor => {
236                Self::InvalidPublicKeyCredentialDescriptor
237            }
238            DeviceAuthKeyCallbackError::MasterPasswordHash => Self::MasterPasswordHash,
239            DeviceAuthKeyCallbackError::MissingCredentialId => Self::MissingCredentialId,
240            DeviceAuthKeyCallbackError::MissingHmacSecret => Self::MissingHmacSecret,
241            DeviceAuthKeyCallbackError::MissingUserHandle => Self::MissingUserHandle,
242            DeviceAuthKeyCallbackError::NotImplemented => Self::NotImplemented,
243            DeviceAuthKeyCallbackError::RetrieveRegistrationOptionsFailure => {
244                Self::RetrieveRegistrationOptionsFailure
245            }
246            DeviceAuthKeyCallbackError::PrfFailure => Self::PrfFailure,
247            DeviceAuthKeyCallbackError::SubmitRegistrationFailure => {
248                Self::SubmitRegistrationFailure
249            }
250            DeviceAuthKeyCallbackError::UserCancelled => Self::UserCancelled,
251            DeviceAuthKeyCallbackError::Unknown { reason } => Self::Unknown { reason },
252        }
253    }
254}
255
256impl From<BitDeviceAuthKeyError> for DeviceAuthKeyCallbackError {
257    fn from(val: BitDeviceAuthKeyError) -> Self {
258        match val {
259            BitDeviceAuthKeyError::AuthenticatorFailure => Self::AuthenticatorFailure,
260            BitDeviceAuthKeyError::Conversion => Self::Conversion,
261            BitDeviceAuthKeyError::CredentialExcluded => Self::CredentialExcluded,
262            BitDeviceAuthKeyError::InvalidRecordIdentifier => Self::InvalidRecordIdentifier,
263            BitDeviceAuthKeyError::InvalidWebVaultUrl => Self::InvalidWebVaultUrl,
264            BitDeviceAuthKeyError::MissingDeviceAuthKey => Self::MissingDeviceAuthKey,
265            BitDeviceAuthKeyError::UnregisterFailure => Self::UnregisterFailure,
266            BitDeviceAuthKeyError::InvalidCoseKey => Self::InvalidCoseKey,
267            BitDeviceAuthKeyError::InvalidPublicKeyCredentialDescriptor => {
268                Self::InvalidPublicKeyCredentialDescriptor
269            }
270            BitDeviceAuthKeyError::MasterPasswordHash => Self::MasterPasswordHash,
271            BitDeviceAuthKeyError::MissingCredentialId => Self::MissingCredentialId,
272            BitDeviceAuthKeyError::MissingHmacSecret => Self::MissingHmacSecret,
273            BitDeviceAuthKeyError::MissingUserHandle => Self::MissingUserHandle,
274            BitDeviceAuthKeyError::NotImplemented => Self::NotImplemented,
275            BitDeviceAuthKeyError::RetrieveRegistrationOptionsFailure => {
276                Self::RetrieveRegistrationOptionsFailure
277            }
278            BitDeviceAuthKeyError::PrfFailure => Self::PrfFailure,
279            BitDeviceAuthKeyError::SubmitRegistrationFailure => Self::SubmitRegistrationFailure,
280            BitDeviceAuthKeyError::UserCancelled => Self::UserCancelled,
281            BitDeviceAuthKeyError::Unknown { reason } => Self::Unknown { reason },
282        }
283    }
284}