bitwarden_uniffi/platform/
fido2.rs

1use 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// Note that uniffi doesn't support external traits for now it seems, so we have to duplicate them
161// here.
162
163#[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
198// Need to implement this From<> impl in order to handle unexpected callback errors.  See the
199// following page in the Uniffi user guide:
200// <https://mozilla.github.io/uniffi-rs/foreign_traits.html#error-handling>
201impl 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
251// Because uniffi doesn't support external traits, we have to make a copy of the trait here.
252// Ideally we'd want to implement the original trait for every item that implements our local copy,
253// but the orphan rules don't allow us to blanket implement an external trait. So we have to wrap
254// the trait in a newtype and implement the trait for the newtype.
255struct 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// Uniffi seems to have trouble generating code for Android when a local trait returns a type from
280// an external crate. If the type is small we can just copy it over and convert back and forth, but
281// Cipher is too big for that to be practical. So we wrap it in a newtype, which is local to the
282// trait and so we can sidestep the Uniffi issue
283#[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}