Skip to content

Commit

Permalink
Merge branch 'main' into ps/sensitive-test
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia authored May 3, 2024
2 parents 418a3c4 + 7e57f19 commit 5558dcd
Show file tree
Hide file tree
Showing 32 changed files with 1,796 additions and 310 deletions.
315 changes: 313 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
17 changes: 17 additions & 0 deletions crates/bitwarden-uniffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ impl From<bitwarden::error::Error> for BitwardenError {
}
}

impl From<BitwardenError> for bitwarden::error::Error {
fn from(val: BitwardenError) -> Self {
match val {
BitwardenError::E(e) => e,
}
}
}

// Need to implement this From<> impl in order to handle unexpected callback errors. See the
// following page in the Uniffi user guide:
// <https://mozilla.github.io/uniffi-rs/foreign_traits.html#error-handling>
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
256 changes: 256 additions & 0 deletions crates/bitwarden-uniffi/src/platform/fido2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
use std::sync::Arc;

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

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

/// At the moment this is just a stub implementation that doesn't do anything. It's here to make
/// it possible to check the usability API on the native clients.
#[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 ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs)?;

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 ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs)?;

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 ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs)?;

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 ui = UniffiTraitBridge(self.0 .1.as_ref());
let cs = UniffiTraitBridge(self.0 .2.as_ref());
let mut client = fido2.create_client(&ui, &cs)?;

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 ui = UniffiTraitBridge(self.0 .1.as_ref());
let cs = UniffiTraitBridge(self.0 .2.as_ref());
let mut client = fido2.create_client(&ui, &cs)?;

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.

#[allow(dead_code)]
#[derive(uniffi::Record)]
pub struct CheckUserResult {
user_present: bool,
user_verified: bool,
}

#[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<CipherViewWrapper>;
async fn pick_credential_for_creation(
&self,
available_credentials: Vec<Cipher>,
new_credential: Fido2Credential,
) -> Result<CipherViewWrapper>;
}

#[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<()>;
}

// Because uniffi doesn't support external traits, we have to make a copy of the trait here.
// Ideally we'd want to implement the original trait for every item that implements our local copy,
// but the orphan rules don't allow us to blanket implement an external trait. So we have to wrap
// the trait in a newtype and implement the trait for the newtype.
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)
}
}

// Uniffi seems to have trouble generating code for Android when a local trait returns a type from
// an external crate. If the type is small we can just copy it over and convert back and forth, but
// Cipher is too big for that to be practical. So we wrap it in a newtype, which is local to the
// trait and so we can sidestep the Uniffi issue
#[derive(uniffi::Record)]
pub struct CipherViewWrapper {
cipher: CipherView,
}

#[async_trait::async_trait]
impl bitwarden::platform::fido2::UserInterface for UniffiTraitBridge<&dyn UserInterface> {
async fn check_user(
&self,
options: CheckUserOptions,
credential: Option<CipherView>,
) -> BitResult<bitwarden::platform::fido2::CheckUserResult> {
self.0
.check_user(options, credential)
.await
.map(|r| bitwarden::platform::fido2::CheckUserResult {
user_present: r.user_present,
user_verified: r.user_verified,
})
.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(|v| v.cipher)
.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(|v| v.cipher)
.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
10 changes: 9 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,14 @@ impl<'a> ClientPlatform<'a> {
pub fn user_fingerprint(self, fingerprint_material: String) -> Result<String> {
generate_user_fingerprint(self.client, fingerprint_material)
}

/// At the moment this is just a stub implementation that doesn't do anything. It's here to make
/// it possible to check the usability API on the native clients.
pub fn fido2(&'a mut self) -> ClientFido2<'a> {
ClientFido2 {
client: self.client,
}
}
}

impl<'a> Client {
Expand Down
Loading

0 comments on commit 5558dcd

Please sign in to comment.