Skip to content

Commit

Permalink
Passkey Uniffi API layer
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed May 3, 2024
1 parent a2495b7 commit 7873401
Show file tree
Hide file tree
Showing 22 changed files with 1,242 additions and 22 deletions.
314 changes: 312 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/bitwarden-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bench = false

[dependencies]
async-lock = "3.3.0"
async-trait = "0.1.80"
bitwarden = { workspace = true, features = ["mobile", "internal"] }
bitwarden-crypto = { workspace = true, features = ["mobile"] }
bitwarden-generators = { workspace = true, features = ["mobile"] }
Expand Down
16 changes: 16 additions & 0 deletions crates/bitwarden-uniffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ impl From<bitwarden::error::Error> for BitwardenError {
}
}

impl Into<bitwarden::error::Error> for BitwardenError {
fn into(self) -> bitwarden::error::Error {
match self {
BitwardenError::E(e) => e,
}
}
}

// Need to implement this From<> impl in order to handle unexpected callback errors. See the
// Callback Interfaces section of the handbook for more info.
impl From<uniffi::UnexpectedUniFFICallbackError> for BitwardenError {
fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
Self::E(bitwarden::error::Error::UniffiCallback(e))
}
}

impl Display for BitwardenError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
233 changes: 233 additions & 0 deletions crates/bitwarden-uniffi/src/platform/fido2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
use std::sync::Arc;

use bitwarden::{
error::Result as BitResult,
platform::fido2::{
CheckUserOptions, CheckUserResult, ClientData, GetAssertionRequest, GetAssertionResult,
MakeCredentialRequest, MakeCredentialResult,
PublicKeyCredentialAuthenticatorAssertionResponse,
PublicKeyCredentialAuthenticatorAttestationResponse,
},
vault::{Cipher, CipherView, Fido2Credential, Fido2CredentialView},
};

use crate::{error::Result, Client};

#[derive(uniffi::Object)]
pub struct ClientFido2(pub(crate) Arc<Client>);

#[uniffi::export]
impl ClientFido2 {
pub fn authenticator(
self: Arc<Self>,
user_interface: Arc<dyn UserInterface>,
credential_store: Arc<dyn CredentialStore>,
) -> Arc<ClientFido2Authenticator> {
Arc::new(ClientFido2Authenticator(
self.0.clone(),
user_interface,
credential_store,
))
}

pub fn client(
self: Arc<Self>,
user_interface: Arc<dyn UserInterface>,
credential_store: Arc<dyn CredentialStore>,
) -> Arc<ClientFido2Client> {
Arc::new(ClientFido2Client(ClientFido2Authenticator(
self.0.clone(),
user_interface,
credential_store,
)))
}
}

#[derive(uniffi::Object)]
pub struct ClientFido2Authenticator(
pub(crate) Arc<Client>,
pub(crate) Arc<dyn UserInterface>,
pub(crate) Arc<dyn CredentialStore>,
);

#[uniffi::export]
impl ClientFido2Authenticator {
pub async fn make_credential(
&self,
request: MakeCredentialRequest,
) -> Result<MakeCredentialResult> {
let mut client = self.0 .0.write().await;

let mut platform = client.platform();
let mut fido2 = platform.fido2();
let mut auth = fido2.create_authenticator(
UniffiTraitBridge(self.1.as_ref()),
UniffiTraitBridge(self.2.as_ref()),
)?;

let result = auth.make_credential(request).await?;
Ok(result)
}

pub async fn get_assertion(&self, request: GetAssertionRequest) -> Result<GetAssertionResult> {
let mut client = self.0 .0.write().await;

let mut platform = client.platform();
let mut fido2 = platform.fido2();
let mut auth = fido2.create_authenticator(
UniffiTraitBridge(self.1.as_ref()),
UniffiTraitBridge(self.2.as_ref()),
)?;

let result = auth.get_assertion(request).await?;
Ok(result)
}

pub async fn silently_discover_credentials(
&self,
rp_id: String,
) -> Result<Vec<Fido2CredentialView>> {
let mut client = self.0 .0.write().await;

let mut platform = client.platform();
let mut fido2 = platform.fido2();
let mut auth = fido2.create_authenticator(
UniffiTraitBridge(self.1.as_ref()),
UniffiTraitBridge(self.2.as_ref()),
)?;

let result = auth.silently_discover_credentials(rp_id).await?;
Ok(result)
}
}

#[derive(uniffi::Object)]
pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator);

#[uniffi::export]
impl ClientFido2Client {
pub async fn register(
&self,
origin: String,
request: String,
client_data: ClientData,
) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse> {
let mut client = self.0 .0 .0.write().await;

let mut platform = client.platform();
let mut fido2 = platform.fido2();
let mut client = fido2.create_client(
UniffiTraitBridge(self.0 .1.as_ref()),
UniffiTraitBridge(self.0 .2.as_ref()),
)?;

let result = client.register(origin, request, client_data).await?;
Ok(result)
}

pub async fn authenticate(
&self,
origin: String,
request: String,
client_data: ClientData,
) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse> {
let mut client = self.0 .0 .0.write().await;

let mut platform = client.platform();
let mut fido2 = platform.fido2();
let mut client = fido2.create_client(
UniffiTraitBridge(self.0 .1.as_ref()),
UniffiTraitBridge(self.0 .2.as_ref()),
)?;

let result = client.authenticate(origin, request, client_data).await?;
Ok(result)
}
}

// Note that uniffi doesn't support external traits for now it seems, so we have to duplicate them
// here.

#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait UserInterface: Send + Sync {
async fn check_user(
&self,
options: CheckUserOptions,
credential: Option<CipherView>,
) -> Result<CheckUserResult>;
async fn pick_credential_for_authentication(
&self,
available_credentials: Vec<Cipher>,
) -> Result<CipherView>;
async fn pick_credential_for_creation(
&self,
available_credentials: Vec<Cipher>,
new_credential: Fido2Credential,
) -> Result<CipherView>;
}

#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait CredentialStore: Send + Sync {
async fn find_credentials(
&self,
ids: Option<Vec<Vec<u8>>>,
rip_id: String,
) -> Result<Vec<Cipher>>;

async fn save_credential(&self, cred: Cipher) -> Result<()>;
}

struct UniffiTraitBridge<T>(T);

#[async_trait::async_trait]
impl bitwarden::platform::fido2::CredentialStore for UniffiTraitBridge<&dyn CredentialStore> {
async fn find_credentials(
&self,
ids: Option<Vec<Vec<u8>>>,
rip_id: String,
) -> BitResult<Vec<Cipher>> {
self.0
.find_credentials(ids, rip_id)
.await
.map_err(Into::into)
}

async fn save_credential(&self, cred: Cipher) -> BitResult<()> {
self.0.save_credential(cred).await.map_err(Into::into)
}
}

#[async_trait::async_trait]
impl bitwarden::platform::fido2::UserInterface for UniffiTraitBridge<&dyn UserInterface> {
async fn check_user(
&self,
options: CheckUserOptions,
credential: Option<CipherView>,
) -> BitResult<CheckUserResult> {
self.0
.check_user(options, credential)
.await
.map_err(Into::into)
}
async fn pick_credential_for_authentication(
&self,
available_credentials: Vec<Cipher>,
) -> BitResult<CipherView> {
self.0
.pick_credential_for_authentication(available_credentials)
.await
.map_err(Into::into)
}
async fn pick_credential_for_creation(
&self,
available_credentials: Vec<Cipher>,
new_credential: Fido2Credential,
) -> BitResult<CipherView> {
self.0
.pick_credential_for_creation(available_credentials, new_credential)
.await
.map_err(Into::into)
}
}
7 changes: 7 additions & 0 deletions crates/bitwarden-uniffi/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use bitwarden::platform::FingerprintRequest;

use crate::{error::Result, Client};

mod fido2;

#[derive(uniffi::Object)]
pub struct ClientPlatform(pub(crate) Arc<Client>);

Expand Down Expand Up @@ -37,4 +39,9 @@ impl ClientPlatform {
self.0 .0.write().await.load_flags(flags);
Ok(())
}

/// FIDO2 operations
pub fn fido2(self: Arc<Self>) -> Arc<fido2::ClientFido2> {
Arc::new(fido2::ClientFido2(self.0.clone()))
}
}
2 changes: 2 additions & 0 deletions crates/bitwarden/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mobile = [
wasm-bindgen = ["chrono/wasmbind"]

[dependencies]
async-trait = ">=0.1.80, <0.2"
base64 = ">=0.21.2, <0.22"
bitwarden-api-api = { workspace = true }
bitwarden-api-identity = { workspace = true }
Expand All @@ -46,6 +47,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [
getrandom = { version = ">=0.2.9, <0.3", features = ["js"] }
hmac = ">=0.12.1, <0.13"
log = ">=0.4.18, <0.5"
passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "12da886102707f87ad97e499c857c0857ece0b85" }
rand = ">=0.8.5, <0.9"
reqwest = { version = ">=0.12, <0.13", features = [
"http2",
Expand Down
14 changes: 14 additions & 0 deletions crates/bitwarden/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bitwarden_api_identity::apis::Error as IdentityError;
use bitwarden_exporters::ExportError;
#[cfg(feature = "internal")]
use bitwarden_generators::{PassphraseError, PasswordError, UsernameError};
use passkey::client::WebauthnError;
use reqwest::StatusCode;
use thiserror::Error;

Expand Down Expand Up @@ -68,10 +69,23 @@ pub enum Error {
#[error(transparent)]
ExportError(#[from] ExportError),

#[error("Webauthn error: {0:?}")]
WebauthnError(passkey::client::WebauthnError),

#[cfg(feature = "mobile")]
#[error("Uniffi callback error: {0}")]
UniffiCallback(#[from] uniffi::UnexpectedUniFFICallbackError),

#[error("Internal error: {0}")]
Internal(Cow<'static, str>),
}

impl From<WebauthnError> for Error {
fn from(e: WebauthnError) -> Self {
Self::WebauthnError(e)
}
}

impl From<String> for Error {
fn from(s: String) -> Self {
Self::Internal(s.into())
Expand Down
8 changes: 7 additions & 1 deletion crates/bitwarden/src/platform/client_platform.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
generate_fingerprint::{generate_fingerprint, generate_user_fingerprint},
FingerprintRequest, FingerprintResponse,
ClientFido2, FingerprintRequest, FingerprintResponse,
};
use crate::{error::Result, Client};

Expand All @@ -16,6 +16,12 @@ impl<'a> ClientPlatform<'a> {
pub fn user_fingerprint(self, fingerprint_material: String) -> Result<String> {
generate_user_fingerprint(self.client, fingerprint_material)
}

pub fn fido2(&'a mut self) -> ClientFido2<'a> {
ClientFido2 {
client: self.client,
}
}
}

impl<'a> Client {
Expand Down
Loading

0 comments on commit 7873401

Please sign in to comment.