bitwarden_uniffi/platform/fido2/
mod.rs1mod device_auth_key;
2
3use std::sync::Arc;
4
5use bitwarden_fido::{
6 CheckUserOptions, ClientData, Fido2CallbackError as BitFido2CallbackError,
7 Fido2CredentialAutofillView, GetAssertionRequest, GetAssertionResult, MakeCredentialRequest,
8 MakeCredentialResult, Origin, PublicKeyCredentialAuthenticatorAssertionResponse,
9 PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity,
10 PublicKeyCredentialUserEntity,
11};
12use bitwarden_vault::{CipherListView, CipherView, EncryptionContext, Fido2CredentialNewView};
13use device_auth_key::{ClientDeviceAuthKeyAuthenticator, DeviceAuthKeyStore};
14
15use crate::error::{Error, Result};
16
17#[derive(uniffi::Object)]
18pub struct ClientFido2(pub(crate) bitwarden_fido::ClientFido2);
19
20#[uniffi::export]
21impl ClientFido2 {
22 pub fn authenticator(
23 &self,
24 user_interface: Arc<dyn Fido2UserInterface>,
25 credential_store: Arc<dyn Fido2CredentialStore>,
26 ) -> Arc<ClientFido2Authenticator> {
27 Arc::new(ClientFido2Authenticator(
28 self.0.clone(),
29 user_interface,
30 credential_store,
31 ))
32 }
33
34 pub fn device_auth_key_authenticator(
35 &self,
36 credential_store: Arc<dyn DeviceAuthKeyStore>,
37 ) -> Arc<ClientDeviceAuthKeyAuthenticator> {
38 Arc::new(ClientDeviceAuthKeyAuthenticator {
39 client: self.0.clone(),
40 store: credential_store,
41 })
42 }
43
44 pub fn client(
45 &self,
46 user_interface: Arc<dyn Fido2UserInterface>,
47 credential_store: Arc<dyn Fido2CredentialStore>,
48 ) -> Arc<ClientFido2Client> {
49 Arc::new(ClientFido2Client(ClientFido2Authenticator(
50 self.0.clone(),
51 user_interface,
52 credential_store,
53 )))
54 }
55
56 pub fn decrypt_fido2_autofill_credentials(
57 &self,
58 cipher_view: CipherView,
59 ) -> Result<Vec<Fido2CredentialAutofillView>> {
60 let result = self
61 .0
62 .decrypt_fido2_autofill_credentials(cipher_view)
63 .map_err(Error::DecryptFido2AutofillCredentials)?;
64
65 Ok(result)
66 }
67}
68
69#[derive(uniffi::Object)]
70pub struct ClientFido2Authenticator(
71 pub(crate) bitwarden_fido::ClientFido2,
72 pub(crate) Arc<dyn Fido2UserInterface>,
73 pub(crate) Arc<dyn Fido2CredentialStore>,
74);
75
76#[uniffi::export(async_runtime = "tokio")]
77impl ClientFido2Authenticator {
78 pub async fn make_credential(
79 &self,
80 request: MakeCredentialRequest,
81 ) -> Result<MakeCredentialResult> {
82 let ui = UniffiTraitBridge(self.1.as_ref());
83 let cs = UniffiTraitBridge(self.2.as_ref());
84 let mut auth = self.0.create_authenticator(&ui, &cs);
85
86 let result = auth
87 .make_credential(request)
88 .await
89 .map_err(Error::MakeCredential)?;
90 Ok(result)
91 }
92
93 pub async fn get_assertion(&self, request: GetAssertionRequest) -> Result<GetAssertionResult> {
94 let ui = UniffiTraitBridge(self.1.as_ref());
95 let cs = UniffiTraitBridge(self.2.as_ref());
96 let mut auth = self.0.create_authenticator(&ui, &cs);
97
98 let result = auth
99 .get_assertion(request)
100 .await
101 .map_err(Error::GetAssertion)?;
102 Ok(result)
103 }
104
105 pub async fn silently_discover_credentials(
106 &self,
107 rp_id: String,
108 user_handle: Option<Vec<u8>>,
109 ) -> Result<Vec<Fido2CredentialAutofillView>> {
110 let ui = UniffiTraitBridge(self.1.as_ref());
111 let cs = UniffiTraitBridge(self.2.as_ref());
112 let mut auth = self.0.create_authenticator(&ui, &cs);
113
114 let result = auth
115 .silently_discover_credentials(rp_id, user_handle)
116 .await
117 .map_err(Error::SilentlyDiscoverCredentials)?;
118 Ok(result)
119 }
120
121 pub async fn credentials_for_autofill(&self) -> Result<Vec<Fido2CredentialAutofillView>> {
122 let ui = UniffiTraitBridge(self.1.as_ref());
123 let cs = UniffiTraitBridge(self.2.as_ref());
124 let mut auth = self.0.create_authenticator(&ui, &cs);
125
126 let result = auth
127 .credentials_for_autofill()
128 .await
129 .map_err(Error::CredentialsForAutofill)?;
130 Ok(result)
131 }
132}
133
134#[derive(uniffi::Object)]
135pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator);
136
137#[uniffi::export(async_runtime = "tokio")]
138impl ClientFido2Client {
139 pub async fn register(
140 &self,
141 origin: Origin,
142 request: String,
143 client_data: ClientData,
144 ) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse> {
145 let ui = UniffiTraitBridge(self.0.1.as_ref());
146 let cs = UniffiTraitBridge(self.0.2.as_ref());
147 let mut client = self.0.0.create_client(&ui, &cs);
148
149 let result = client
150 .register(origin, request, client_data)
151 .await
152 .map_err(Error::Fido2Client)?;
153 Ok(result)
154 }
155
156 pub async fn authenticate(
157 &self,
158 origin: Origin,
159 request: String,
160 client_data: ClientData,
161 ) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse> {
162 let ui = UniffiTraitBridge(self.0.1.as_ref());
163 let cs = UniffiTraitBridge(self.0.2.as_ref());
164 let mut client = self.0.0.create_client(&ui, &cs);
165
166 let result = client
167 .authenticate(origin, request, client_data)
168 .await
169 .map_err(Error::Fido2Client)?;
170 Ok(result)
171 }
172}
173
174#[allow(dead_code)]
178#[derive(uniffi::Record)]
179pub struct CheckUserResult {
180 user_present: bool,
181 user_verified: bool,
182}
183
184impl From<CheckUserResult> for bitwarden_fido::CheckUserResult {
185 fn from(val: CheckUserResult) -> Self {
186 Self {
187 user_present: val.user_present,
188 user_verified: val.user_verified,
189 }
190 }
191}
192
193#[allow(dead_code)]
194#[derive(uniffi::Record)]
195pub struct CheckUserAndPickCredentialForCreationResult {
196 cipher: CipherViewWrapper,
197 check_user_result: CheckUserResult,
198}
199
200#[derive(Debug, thiserror::Error, uniffi::Error)]
201pub enum Fido2CallbackError {
202 #[error("The operation requires user interaction")]
203 UserInterfaceRequired,
204
205 #[error("The operation was cancelled by the user")]
206 OperationCancelled,
207
208 #[error("Unknown error: {reason}")]
209 Unknown { reason: String },
210}
211
212impl From<uniffi::UnexpectedUniFFICallbackError> for Fido2CallbackError {
216 fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
217 Self::Unknown { reason: e.reason }
218 }
219}
220
221impl From<Fido2CallbackError> for BitFido2CallbackError {
222 fn from(val: Fido2CallbackError) -> Self {
223 match val {
224 Fido2CallbackError::UserInterfaceRequired => Self::UserInterfaceRequired,
225 Fido2CallbackError::OperationCancelled => Self::OperationCancelled,
226 Fido2CallbackError::Unknown { reason } => Self::Unknown(reason),
227 }
228 }
229}
230
231#[uniffi::export(with_foreign)]
232#[async_trait::async_trait]
233pub trait Fido2UserInterface: Send + Sync {
234 async fn check_user(
235 &self,
236 options: CheckUserOptions,
237 hint: UIHint,
238 ) -> Result<CheckUserResult, Fido2CallbackError>;
239 async fn pick_credential_for_authentication(
240 &self,
241 available_credentials: Vec<CipherView>,
242 ) -> Result<CipherViewWrapper, Fido2CallbackError>;
243 async fn check_user_and_pick_credential_for_creation(
244 &self,
245 options: CheckUserOptions,
246 new_credential: Fido2CredentialNewView,
247 ) -> Result<CheckUserAndPickCredentialForCreationResult, Fido2CallbackError>;
248 fn is_verification_enabled(&self) -> bool;
249}
250
251#[uniffi::export(with_foreign)]
252#[async_trait::async_trait]
253pub trait Fido2CredentialStore: Send + Sync {
254 async fn find_credentials(
255 &self,
256 ids: Option<Vec<Vec<u8>>>,
257 rip_id: String,
258 user_handle: Option<Vec<u8>>,
259 ) -> Result<Vec<CipherView>, Fido2CallbackError>;
260
261 async fn all_credentials(&self) -> Result<Vec<CipherListView>, Fido2CallbackError>;
262
263 async fn save_credential(&self, cred: EncryptionContext) -> Result<(), Fido2CallbackError>;
264}
265
266struct UniffiTraitBridge<T>(T);
271
272#[async_trait::async_trait]
273impl bitwarden_fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2CredentialStore> {
274 async fn find_credentials(
275 &self,
276 ids: Option<Vec<Vec<u8>>>,
277 rip_id: String,
278 user_handle: Option<Vec<u8>>,
279 ) -> Result<Vec<CipherView>, BitFido2CallbackError> {
280 self.0
281 .find_credentials(ids, rip_id, user_handle)
282 .await
283 .map_err(Into::into)
284 }
285
286 async fn all_credentials(&self) -> Result<Vec<CipherListView>, BitFido2CallbackError> {
287 self.0.all_credentials().await.map_err(Into::into)
288 }
289
290 async fn save_credential(&self, cred: EncryptionContext) -> Result<(), BitFido2CallbackError> {
291 self.0.save_credential(cred).await.map_err(Into::into)
292 }
293}
294
295#[derive(uniffi::Record)]
300pub struct CipherViewWrapper {
301 cipher: CipherView,
302}
303
304#[derive(uniffi::Enum)]
305pub enum UIHint {
306 InformExcludedCredentialFound(CipherView),
307 InformNoCredentialsFound,
308 RequestNewCredential(PublicKeyCredentialUserEntity, PublicKeyCredentialRpEntity),
309 RequestExistingCredential(CipherView),
310}
311
312impl From<bitwarden_fido::UiHint<'_, CipherView>> for UIHint {
313 fn from(hint: bitwarden_fido::UiHint<'_, CipherView>) -> Self {
314 use bitwarden_fido::UiHint as BWUIHint;
315 match hint {
316 BWUIHint::InformExcludedCredentialFound(cipher) => {
317 UIHint::InformExcludedCredentialFound(cipher.clone())
318 }
319 BWUIHint::InformNoCredentialsFound => UIHint::InformNoCredentialsFound,
320 BWUIHint::RequestNewCredential(user, rp) => UIHint::RequestNewCredential(
321 PublicKeyCredentialUserEntity {
322 id: user.id.clone().into(),
323 name: user.name.clone().unwrap_or_default(),
324 display_name: user.display_name.clone().unwrap_or_default(),
325 },
326 PublicKeyCredentialRpEntity {
327 id: rp.id.clone(),
328 name: rp.name.clone(),
329 },
330 ),
331 BWUIHint::RequestExistingCredential(cipher) => {
332 UIHint::RequestExistingCredential(cipher.clone())
333 }
334 }
335 }
336}
337
338#[async_trait::async_trait]
339impl bitwarden_fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> {
340 async fn check_user<'a>(
341 &self,
342 options: CheckUserOptions,
343 hint: bitwarden_fido::UiHint<'a, CipherView>,
344 ) -> Result<bitwarden_fido::CheckUserResult, BitFido2CallbackError> {
345 self.0
346 .check_user(options.clone(), hint.into())
347 .await
348 .map(Into::into)
349 .map_err(Into::into)
350 }
351 async fn pick_credential_for_authentication(
352 &self,
353 available_credentials: Vec<CipherView>,
354 ) -> Result<CipherView, BitFido2CallbackError> {
355 self.0
356 .pick_credential_for_authentication(available_credentials)
357 .await
358 .map(|v| v.cipher)
359 .map_err(Into::into)
360 }
361 async fn check_user_and_pick_credential_for_creation(
362 &self,
363 options: CheckUserOptions,
364 new_credential: Fido2CredentialNewView,
365 ) -> Result<(CipherView, bitwarden_fido::CheckUserResult), BitFido2CallbackError> {
366 self.0
367 .check_user_and_pick_credential_for_creation(options, new_credential)
368 .await
369 .map(|v| (v.cipher.cipher, v.check_user_result.into()))
370 .map_err(Into::into)
371 }
372 fn is_verification_enabled(&self) -> bool {
373 self.0.is_verification_enabled()
374 }
375}