From 60c8e204dfa202f6c0a1fa898c7952979e7076e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Tue, 2 Jul 2024 16:06:00 +0200 Subject: [PATCH] [WIP] Non blocking passkeys --- Cargo.lock | 1 + crates/bitwarden-uniffi/Cargo.toml | 1 + crates/bitwarden-uniffi/src/platform/fido2.rs | 166 ++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index cc2f034343..ea94cf6b7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -637,6 +637,7 @@ dependencies = [ "log", "schemars", "thiserror", + "tokio", "uniffi", "uuid", ] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 2b2ecd11ea..9b09f521ab 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -35,6 +35,7 @@ log = "0.4.20" env_logger = "0.11.1" schemars = { version = ">=0.8, <0.9", optional = true } thiserror = ">=1.0.40, <2.0" +tokio = { version = "1.36.0" } uniffi = "=0.27.2" uuid = ">=1.3.3, <2" diff --git a/crates/bitwarden-uniffi/src/platform/fido2.rs b/crates/bitwarden-uniffi/src/platform/fido2.rs index e5245faef8..0c1f81f5d2 100644 --- a/crates/bitwarden-uniffi/src/platform/fido2.rs +++ b/crates/bitwarden-uniffi/src/platform/fido2.rs @@ -44,6 +44,18 @@ impl ClientFido2 { ))) } + pub fn nonblocking_client( + self: Arc, + user_interface: Arc, + credential_store: Arc, + ) -> Arc { + Arc::new(ClientFido2NonBlockingClient( + self.0.clone(), + user_interface, + credential_store, + )) + } + pub fn decrypt_fido2_autofill_credentials( self: Arc, cipher_view: CipherView, @@ -170,6 +182,52 @@ impl ClientFido2Client { } } +#[derive(uniffi::Object)] +pub struct ClientFido2NonBlockingClient( + pub(crate) Arc, + pub(crate) Arc, + pub(crate) Arc, +); + +#[uniffi::export] +impl ClientFido2NonBlockingClient { + pub async fn register( + &self, + origin: String, + request: String, + client_data: ClientData, + ) -> Result { + let fido2 = self.0 .0.fido2(); + let ui = UniffiTraitBridge(self.1.as_ref()); + let cs = UniffiTraitBridge(self.2.as_ref()); + let mut client = fido2.create_client(&ui, &cs); + + let result = client + .register(origin, request, client_data) + .await + .map_err(Error::Fido2Client)?; + Ok(result) + } + + pub async fn authenticate( + &self, + origin: String, + request: String, + client_data: ClientData, + ) -> Result { + let fido2 = self.0 .0.fido2(); + let ui = UniffiTraitBridge(self.1.as_ref()); + let cs = UniffiTraitBridge(self.2.as_ref()); + let mut client = fido2.create_client(&ui, &cs); + + let result = client + .authenticate(origin, request, client_data) + .await + .map_err(Error::Fido2Client)?; + Ok(result) + } +} + // Note that uniffi doesn't support external traits for now it seems, so we have to duplicate them // here. @@ -231,6 +289,62 @@ pub trait Fido2UserInterface: Send + Sync { async fn is_verification_enabled(&self) -> bool; } +macro_rules! continuation { + ($name:ident, $type:ty) => { + #[derive(uniffi::Object)] + pub struct $name { + tx: tokio::sync::mpsc::Sender>, + } + + impl $name { + pub fn new() -> ( + std::sync::Arc, + tokio::sync::mpsc::Receiver>, + ) { + let (tx, rx) = tokio::sync::mpsc::channel(1); + (std::sync::Arc::new(Self { tx }), rx) + } + } + + #[uniffi::export(async_runtime = "tokio")] + impl $name { + pub async fn complete(&self, result: $type) { + let _ = self.tx.send(Ok(result)).await; + } + + pub async fn fail(&self, error: Fido2CallbackError) { + let _ = self.tx.send(Err(error)).await; + } + } + }; +} + +continuation!(CompletionObjectCheckUserResult, CheckUserResult); +continuation!(CompletionObjectCipherViewWrapper, CipherViewWrapper); + +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait Fido2NonBlockingUserInterface: Send + Sync { + async fn check_user( + &self, + options: CheckUserOptions, + hint: UIHint, + completion: Arc, + ) -> Result<(), Fido2CallbackError>; + async fn pick_credential_for_authentication( + &self, + available_credentials: Vec, + completion: Arc, + ) -> Result<(), Fido2CallbackError>; + async fn check_user_and_pick_credential_for_creation( + &self, + options: CheckUserOptions, + new_credential: Fido2CredentialNewView, + completion: Arc, + ) -> Result<(), Fido2CallbackError>; + async fn is_verification_enabled(&self) -> bool; +} + #[uniffi::export(with_foreign)] #[async_trait::async_trait] pub trait Fido2CredentialStore: Send + Sync { @@ -357,3 +471,55 @@ impl bitwarden::fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInt self.0.is_verification_enabled().await } } + +#[async_trait::async_trait] +impl bitwarden::fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2NonBlockingUserInterface> { + async fn check_user<'a>( + &self, + options: CheckUserOptions, + hint: bitwarden::fido::UIHint<'a, CipherView>, + ) -> Result { + let (completion, mut rx) = CompletionObjectCheckUserResult::new(); + + self.0 + .check_user(options.clone(), hint.into(), completion) + .await?; + + let result = rx.recv().await.expect("Channel should be open")?; + + Ok(bitwarden::fido::CheckUserResult { + user_present: result.user_present, + user_verified: result.user_verified, + }) + } + async fn pick_credential_for_authentication( + &self, + available_credentials: Vec, + ) -> Result { + let (completion, mut rx) = CompletionObjectCipherViewWrapper::new(); + + self.0 + .pick_credential_for_authentication(available_credentials, completion) + .await?; + + let result = rx.recv().await.expect("Channel should be open")?; + Ok(result.cipher) + } + async fn check_user_and_pick_credential_for_creation( + &self, + options: CheckUserOptions, + new_credential: Fido2CredentialNewView, + ) -> Result { + let (completion, mut rx) = CompletionObjectCipherViewWrapper::new(); + + self.0 + .check_user_and_pick_credential_for_creation(options, new_credential, completion) + .await?; + + let result = rx.recv().await.expect("Channel should be open")?; + Ok(result.cipher) + } + async fn is_verification_enabled(&self) -> bool { + self.0.is_verification_enabled().await + } +}