From 80db14fdf4ad0bfa0dd0e75ef4d5e758ea5f44ee 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 | 173 ++++++++++++++---- 3 files changed, 143 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc2f03434..ea94cf6b7 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 2b2ecd11e..9b09f521a 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 e5245faef..8480abfbe 100644 --- a/crates/bitwarden-uniffi/src/platform/fido2.rs +++ b/crates/bitwarden-uniffi/src/platform/fido2.rs @@ -27,8 +27,8 @@ impl ClientFido2 { ) -> Arc { Arc::new(ClientFido2Authenticator( self.0.clone(), - user_interface, - credential_store, + Box::new(UniffiTraitBridge(user_interface)), + Box::new(UniffiTraitBridge(credential_store)), )) } @@ -37,11 +37,23 @@ impl ClientFido2 { user_interface: Arc, credential_store: Arc, ) -> Arc { - Arc::new(ClientFido2Client(ClientFido2Authenticator( + Arc::new(ClientFido2Client( self.0.clone(), - user_interface, - credential_store, - ))) + Box::new(UniffiTraitBridge(user_interface)), + Box::new(UniffiTraitBridge(credential_store)), + )) + } + + pub fn nonblocking_client( + self: Arc, + user_interface: Arc, + credential_store: Arc, + ) -> Arc { + Arc::new(ClientFido2Client( + self.0.clone(), + Box::new(UniffiTraitBridge(user_interface)), + Box::new(UniffiTraitBridge(credential_store)), + )) } pub fn decrypt_fido2_autofill_credentials( @@ -62,8 +74,8 @@ impl ClientFido2 { #[derive(uniffi::Object)] pub struct ClientFido2Authenticator( pub(crate) Arc, - pub(crate) Arc, - pub(crate) Arc, + pub(crate) Box, + pub(crate) Box, ); #[uniffi::export] @@ -73,9 +85,7 @@ impl ClientFido2Authenticator { request: MakeCredentialRequest, ) -> Result { let fido2 = self.0 .0.fido2(); - let ui = UniffiTraitBridge(self.1.as_ref()); - let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs); + let mut auth = fido2.create_authenticator(self.1.as_ref(), self.2.as_ref()); let result = auth .make_credential(request) @@ -86,9 +96,7 @@ impl ClientFido2Authenticator { pub async fn get_assertion(&self, request: GetAssertionRequest) -> Result { let fido2 = self.0 .0.fido2(); - let ui = UniffiTraitBridge(self.1.as_ref()); - let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs); + let mut auth = fido2.create_authenticator(self.1.as_ref(), self.2.as_ref()); let result = auth .get_assertion(request) @@ -102,10 +110,7 @@ impl ClientFido2Authenticator { rp_id: String, ) -> Result> { let fido2 = self.0 .0.fido2(); - - let ui = UniffiTraitBridge(self.1.as_ref()); - let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs); + let mut auth = fido2.create_authenticator(self.1.as_ref(), self.2.as_ref()); let result = auth .silently_discover_credentials(rp_id) @@ -116,9 +121,7 @@ impl ClientFido2Authenticator { pub async fn credentials_for_autofill(&self) -> Result> { let fido2 = self.0 .0.fido2(); - let ui = UniffiTraitBridge(self.1.as_ref()); - let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs); + let mut auth = fido2.create_authenticator(self.1.as_ref(), self.2.as_ref()); let result = auth .credentials_for_autofill() @@ -129,7 +132,11 @@ impl ClientFido2Authenticator { } #[derive(uniffi::Object)] -pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator); +pub struct ClientFido2Client( + pub(crate) Arc, + pub(crate) Box, + pub(crate) Box, +); #[uniffi::export] impl ClientFido2Client { @@ -139,10 +146,8 @@ impl ClientFido2Client { request: String, client_data: ClientData, ) -> Result { - let fido2 = self.0 .0 .0.fido2(); - let ui = UniffiTraitBridge(self.0 .1.as_ref()); - let cs = UniffiTraitBridge(self.0 .2.as_ref()); - let mut client = fido2.create_client(&ui, &cs); + let fido2 = self.0 .0.fido2(); + let mut client = fido2.create_client(self.1.as_ref(), self.2.as_ref()); let result = client .register(origin, request, client_data) @@ -157,10 +162,8 @@ impl ClientFido2Client { request: String, client_data: ClientData, ) -> Result { - let fido2 = self.0 .0 .0.fido2(); - let ui = UniffiTraitBridge(self.0 .1.as_ref()); - let cs = UniffiTraitBridge(self.0 .2.as_ref()); - let mut client = fido2.create_client(&ui, &cs); + let fido2 = self.0 .0.fido2(); + let mut client = fido2.create_client(self.1.as_ref(), self.2.as_ref()); let result = client .authenticate(origin, request, client_data) @@ -231,6 +234,59 @@ 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 { @@ -252,7 +308,7 @@ pub trait Fido2CredentialStore: Send + Sync { struct UniffiTraitBridge(T); #[async_trait::async_trait] -impl bitwarden::fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2CredentialStore> { +impl bitwarden::fido::Fido2CredentialStore for UniffiTraitBridge> { async fn find_credentials( &self, ids: Option>>, @@ -317,7 +373,7 @@ impl From> for UIHint { } #[async_trait::async_trait] -impl bitwarden::fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> { +impl bitwarden::fido::Fido2UserInterface for UniffiTraitBridge> { async fn check_user<'a>( &self, options: CheckUserOptions, @@ -357,3 +413,56 @@ impl bitwarden::fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInt self.0.is_verification_enabled().await } } + +#[async_trait::async_trait] +impl bitwarden::fido::Fido2UserInterface + for UniffiTraitBridge> +{ + 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 + } +}