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