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 ) -> Result<Vec<Fido2CredentialAutofillView>> {
96 let ui = UniffiTraitBridge(self.1.as_ref());
97 let cs = UniffiTraitBridge(self.2.as_ref());
98 let mut auth = self.0.create_authenticator(&ui, &cs);
99
100 let result = auth
101 .silently_discover_credentials(rp_id)
102 .await
103 .map_err(Error::SilentlyDiscoverCredentials)?;
104 Ok(result)
105 }
106
107 pub async fn credentials_for_autofill(&self) -> Result<Vec<Fido2CredentialAutofillView>> {
108 let ui = UniffiTraitBridge(self.1.as_ref());
109 let cs = UniffiTraitBridge(self.2.as_ref());
110 let mut auth = self.0.create_authenticator(&ui, &cs);
111
112 let result = auth
113 .credentials_for_autofill()
114 .await
115 .map_err(Error::CredentialsForAutofill)?;
116 Ok(result)
117 }
118}
119
120#[derive(uniffi::Object)]
121pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator);
122
123#[uniffi::export]
124impl ClientFido2Client {
125 pub async fn register(
126 &self,
127 origin: Origin,
128 request: String,
129 client_data: ClientData,
130 ) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse> {
131 let ui = UniffiTraitBridge(self.0 .1.as_ref());
132 let cs = UniffiTraitBridge(self.0 .2.as_ref());
133 let mut client = self.0 .0.create_client(&ui, &cs);
134
135 let result = client
136 .register(origin, request, client_data)
137 .await
138 .map_err(Error::Fido2Client)?;
139 Ok(result)
140 }
141
142 pub async fn authenticate(
143 &self,
144 origin: Origin,
145 request: String,
146 client_data: ClientData,
147 ) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse> {
148 let ui = UniffiTraitBridge(self.0 .1.as_ref());
149 let cs = UniffiTraitBridge(self.0 .2.as_ref());
150 let mut client = self.0 .0.create_client(&ui, &cs);
151
152 let result = client
153 .authenticate(origin, request, client_data)
154 .await
155 .map_err(Error::Fido2Client)?;
156 Ok(result)
157 }
158}
159
160#[allow(dead_code)]
164#[derive(uniffi::Record)]
165pub struct CheckUserResult {
166 user_present: bool,
167 user_verified: bool,
168}
169
170impl From<CheckUserResult> for bitwarden_fido::CheckUserResult {
171 fn from(val: CheckUserResult) -> Self {
172 Self {
173 user_present: val.user_present,
174 user_verified: val.user_verified,
175 }
176 }
177}
178
179#[allow(dead_code)]
180#[derive(uniffi::Record)]
181pub struct CheckUserAndPickCredentialForCreationResult {
182 cipher: CipherViewWrapper,
183 check_user_result: CheckUserResult,
184}
185
186#[derive(Debug, thiserror::Error, uniffi::Error)]
187pub enum Fido2CallbackError {
188 #[error("The operation requires user interaction")]
189 UserInterfaceRequired,
190
191 #[error("The operation was cancelled by the user")]
192 OperationCancelled,
193
194 #[error("Unknown error: {reason}")]
195 Unknown { reason: String },
196}
197
198impl From<uniffi::UnexpectedUniFFICallbackError> for Fido2CallbackError {
202 fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
203 Self::Unknown { reason: e.reason }
204 }
205}
206
207impl From<Fido2CallbackError> for BitFido2CallbackError {
208 fn from(val: Fido2CallbackError) -> Self {
209 match val {
210 Fido2CallbackError::UserInterfaceRequired => Self::UserInterfaceRequired,
211 Fido2CallbackError::OperationCancelled => Self::OperationCancelled,
212 Fido2CallbackError::Unknown { reason } => Self::Unknown(reason),
213 }
214 }
215}
216
217#[uniffi::export(with_foreign)]
218#[async_trait::async_trait]
219pub trait Fido2UserInterface: Send + Sync {
220 async fn check_user(
221 &self,
222 options: CheckUserOptions,
223 hint: UIHint,
224 ) -> Result<CheckUserResult, Fido2CallbackError>;
225 async fn pick_credential_for_authentication(
226 &self,
227 available_credentials: Vec<CipherView>,
228 ) -> Result<CipherViewWrapper, Fido2CallbackError>;
229 async fn check_user_and_pick_credential_for_creation(
230 &self,
231 options: CheckUserOptions,
232 new_credential: Fido2CredentialNewView,
233 ) -> Result<CheckUserAndPickCredentialForCreationResult, Fido2CallbackError>;
234 async fn is_verification_enabled(&self) -> bool;
235}
236
237#[uniffi::export(with_foreign)]
238#[async_trait::async_trait]
239pub trait Fido2CredentialStore: Send + Sync {
240 async fn find_credentials(
241 &self,
242 ids: Option<Vec<Vec<u8>>>,
243 rip_id: String,
244 ) -> Result<Vec<CipherView>, Fido2CallbackError>;
245
246 async fn all_credentials(&self) -> Result<Vec<CipherListView>, Fido2CallbackError>;
247
248 async fn save_credential(&self, cred: EncryptionContext) -> Result<(), Fido2CallbackError>;
249}
250
251struct UniffiTraitBridge<T>(T);
256
257#[async_trait::async_trait]
258impl bitwarden_fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2CredentialStore> {
259 async fn find_credentials(
260 &self,
261 ids: Option<Vec<Vec<u8>>>,
262 rip_id: String,
263 ) -> Result<Vec<CipherView>, BitFido2CallbackError> {
264 self.0
265 .find_credentials(ids, rip_id)
266 .await
267 .map_err(Into::into)
268 }
269
270 async fn all_credentials(&self) -> Result<Vec<CipherListView>, BitFido2CallbackError> {
271 self.0.all_credentials().await.map_err(Into::into)
272 }
273
274 async fn save_credential(&self, cred: EncryptionContext) -> Result<(), BitFido2CallbackError> {
275 self.0.save_credential(cred).await.map_err(Into::into)
276 }
277}
278
279#[derive(uniffi::Record)]
284pub struct CipherViewWrapper {
285 cipher: CipherView,
286}
287
288#[derive(uniffi::Enum)]
289pub enum UIHint {
290 InformExcludedCredentialFound(CipherView),
291 InformNoCredentialsFound,
292 RequestNewCredential(PublicKeyCredentialUserEntity, PublicKeyCredentialRpEntity),
293 RequestExistingCredential(CipherView),
294}
295
296impl From<bitwarden_fido::UIHint<'_, CipherView>> for UIHint {
297 fn from(hint: bitwarden_fido::UIHint<'_, CipherView>) -> Self {
298 use bitwarden_fido::UIHint as BWUIHint;
299 match hint {
300 BWUIHint::InformExcludedCredentialFound(cipher) => {
301 UIHint::InformExcludedCredentialFound(cipher.clone())
302 }
303 BWUIHint::InformNoCredentialsFound => UIHint::InformNoCredentialsFound,
304 BWUIHint::RequestNewCredential(user, rp) => UIHint::RequestNewCredential(
305 PublicKeyCredentialUserEntity {
306 id: user.id.clone().into(),
307 name: user.name.clone().unwrap_or_default(),
308 display_name: user.display_name.clone().unwrap_or_default(),
309 },
310 PublicKeyCredentialRpEntity {
311 id: rp.id.clone(),
312 name: rp.name.clone(),
313 },
314 ),
315 BWUIHint::RequestExistingCredential(cipher) => {
316 UIHint::RequestExistingCredential(cipher.clone())
317 }
318 }
319 }
320}
321
322#[async_trait::async_trait]
323impl bitwarden_fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> {
324 async fn check_user<'a>(
325 &self,
326 options: CheckUserOptions,
327 hint: bitwarden_fido::UIHint<'a, CipherView>,
328 ) -> Result<bitwarden_fido::CheckUserResult, BitFido2CallbackError> {
329 self.0
330 .check_user(options.clone(), hint.into())
331 .await
332 .map(Into::into)
333 .map_err(Into::into)
334 }
335 async fn pick_credential_for_authentication(
336 &self,
337 available_credentials: Vec<CipherView>,
338 ) -> Result<CipherView, BitFido2CallbackError> {
339 self.0
340 .pick_credential_for_authentication(available_credentials)
341 .await
342 .map(|v| v.cipher)
343 .map_err(Into::into)
344 }
345 async fn check_user_and_pick_credential_for_creation(
346 &self,
347 options: CheckUserOptions,
348 new_credential: Fido2CredentialNewView,
349 ) -> Result<(CipherView, bitwarden_fido::CheckUserResult), BitFido2CallbackError> {
350 self.0
351 .check_user_and_pick_credential_for_creation(options, new_credential)
352 .await
353 .map(|v| (v.cipher.cipher, v.check_user_result.into()))
354 .map_err(Into::into)
355 }
356 async fn is_verification_enabled(&self) -> bool {
357 self.0.is_verification_enabled().await
358 }
359}