Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-7840] Implement the stubbed out Passkey uniffi API #779

Merged
merged 41 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
419101d
Improvements to Passkey uniffi API
dani-garcia May 3, 2024
d2041a3
Here we go again with the uniffi workaround
dani-garcia May 13, 2024
07642dc
Remove stub credential
dani-garcia May 13, 2024
9e704a4
Add a warning about silent discover
dani-garcia May 13, 2024
c21f94a
Changes based on review
dani-garcia May 14, 2024
26a3c63
Fmt
dani-garcia May 14, 2024
16b8b03
Change API based on latest spec
dani-garcia May 14, 2024
27ca115
Update passkey-rs and respect the UV value provided in get_assertion …
dani-garcia May 14, 2024
c63d806
Update passkey-rs for async is_verification_enabled
dani-garcia May 14, 2024
82dac1b
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia May 14, 2024
3a82eec
Handle URL parse error
dani-garcia May 14, 2024
77f07a0
Return valid authenticator data
dani-garcia May 14, 2024
499ebb9
Use valid error codes
dani-garcia May 14, 2024
5478d3c
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia May 14, 2024
3a03ba5
Remove unneeded require_resident_key field
dani-garcia May 15, 2024
2832bcf
Change where we transform UV (#786)
coroiu May 15, 2024
ad3e4de
Actually call pick_credential_for_authentication
dani-garcia May 15, 2024
1e8c37f
Remove crypto copied from passkey-rs
dani-garcia May 17, 2024
55938d6
Fix some review comments
dani-garcia May 17, 2024
aa48fce
Implement UIHint
dani-garcia May 20, 2024
ea7ca9e
Formatting
dani-garcia May 20, 2024
e537b6f
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia May 23, 2024
1d90784
Improve error handling, don't panic when an exception is thrown
dani-garcia May 23, 2024
54f511a
Make p256 optional
dani-garcia May 23, 2024
01b12f1
Improve ClientData comment
dani-garcia May 23, 2024
5c5c386
Update swift example
dani-garcia May 23, 2024
10fa9f7
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia May 24, 2024
2a448bd
Remove some todos
dani-garcia May 24, 2024
a7b21d3
Remove some more todos
dani-garcia May 24, 2024
8caaeed
requested_uv and uv conversion
dani-garcia May 24, 2024
5143d1d
Remove comments
dani-garcia May 27, 2024
c9fa74b
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia May 29, 2024
432e06a
Remove some unnecessary locks and takes
dani-garcia May 29, 2024
99b7f56
Simplify ClientData
dani-garcia May 29, 2024
7a4b8b1
Update pick_credential_for_creation, move functions to check_user, in…
dani-garcia May 30, 2024
2d297b7
Support b64 credential IDs
dani-garcia May 30, 2024
acafb94
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia Jun 3, 2024
7a2680f
Update iOS example
dani-garcia Jun 3, 2024
e111192
Merge branch 'main' into ps/uniffi-passkey-improvements
dani-garcia Jun 3, 2024
75dc8d3
Update passkey-rs
dani-garcia Jun 3, 2024
f852dbf
Validate that the provided credential ID matches what is stored
dani-garcia Jun 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/bitwarden-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [
log = "0.4.20"
env_logger = "0.11.1"
schemars = { version = ">=0.8, <0.9", optional = true }
thiserror = ">=1.0.40, <2.0"
uniffi = "=0.27.2"
uuid = ">=1.3.3, <2"

Expand Down
17 changes: 0 additions & 17 deletions crates/bitwarden-uniffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,6 @@ 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
149 changes: 109 additions & 40 deletions crates/bitwarden-uniffi/src/platform/fido2.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
use std::sync::Arc;

use bitwarden::{
error::Result as BitResult,
platform::fido2::{
CheckUserOptions, ClientData, GetAssertionRequest, GetAssertionResult,
MakeCredentialRequest, MakeCredentialResult,
CheckUserOptions, ClientData, Fido2CallbackError as BitFido2CallbackError,
GetAssertionRequest, GetAssertionResult, MakeCredentialRequest, MakeCredentialResult,
PublicKeyCredentialAuthenticatorAssertionResponse,
PublicKeyCredentialAuthenticatorAttestationResponse,
PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
},
vault::{Cipher, CipherView, Fido2Credential, Fido2CredentialView},
vault::{Cipher, CipherView, Fido2CredentialNewView, 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>,
user_interface: Arc<dyn Fido2UserInterface>,
credential_store: Arc<dyn Fido2CredentialStore>,

Check warning on line 24 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L23-L24

Added lines #L23 - L24 were not covered by tests
) -> Arc<ClientFido2Authenticator> {
Arc::new(ClientFido2Authenticator(
self.0.clone(),
Expand All @@ -34,8 +32,8 @@

pub fn client(
self: Arc<Self>,
user_interface: Arc<dyn UserInterface>,
credential_store: Arc<dyn CredentialStore>,
user_interface: Arc<dyn Fido2UserInterface>,
credential_store: Arc<dyn Fido2CredentialStore>,

Check warning on line 36 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L35-L36

Added lines #L35 - L36 were not covered by tests
) -> Arc<ClientFido2Client> {
Arc::new(ClientFido2Client(ClientFido2Authenticator(
self.0.clone(),
Expand All @@ -48,8 +46,8 @@
#[derive(uniffi::Object)]
pub struct ClientFido2Authenticator(
pub(crate) Arc<Client>,
pub(crate) Arc<dyn UserInterface>,
pub(crate) Arc<dyn CredentialStore>,
pub(crate) Arc<dyn Fido2UserInterface>,
pub(crate) Arc<dyn Fido2CredentialStore>,
);

#[uniffi::export]
Expand Down Expand Up @@ -152,35 +150,67 @@
user_verified: bool,
}

#[derive(Debug, thiserror::Error, uniffi::Error)]

Check warning on line 153 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L153

Added line #L153 was not covered by tests
pub enum Fido2CallbackError {
#[error("The operation requires user interaction")]
UserInterfaceRequired,

#[error("The operation was cancelled by the user")]
OperationCancelled,

#[error("Unknown error: {reason}")]
Unknown { reason: String },
dani-garcia marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 Fido2CallbackError {
fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
Self::Unknown { reason: e.reason }
}

Check warning on line 171 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L169-L171

Added lines #L169 - L171 were not covered by tests
}

impl From<Fido2CallbackError> for BitFido2CallbackError {
fn from(val: Fido2CallbackError) -> Self {
match val {
Fido2CallbackError::UserInterfaceRequired => Self::UserInterfaceRequired,
Fido2CallbackError::OperationCancelled => Self::OperationCancelled,
Fido2CallbackError::Unknown { reason } => Self::Unknown(reason),

Check warning on line 179 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L175-L179

Added lines #L175 - L179 were not covered by tests
}
}

Check warning on line 181 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L181

Added line #L181 was not covered by tests
}

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

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

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

// Because uniffi doesn't support external traits, we have to make a copy of the trait here.
Expand All @@ -190,19 +220,21 @@
struct UniffiTraitBridge<T>(T);

#[async_trait::async_trait]
impl bitwarden::platform::fido2::CredentialStore for UniffiTraitBridge<&dyn CredentialStore> {
impl bitwarden::platform::fido2::Fido2CredentialStore
for UniffiTraitBridge<&dyn Fido2CredentialStore>
{
async fn find_credentials(
&self,
ids: Option<Vec<Vec<u8>>>,
rip_id: String,
) -> BitResult<Vec<Cipher>> {
) -> Result<Vec<CipherView>, BitFido2CallbackError> {

Check warning on line 230 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L230

Added line #L230 was not covered by tests
self.0
.find_credentials(ids, rip_id)
.await
.map_err(Into::into)
}

async fn save_credential(&self, cred: Cipher) -> BitResult<()> {
async fn save_credential(&self, cred: Cipher) -> Result<(), BitFido2CallbackError> {

Check warning on line 237 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L237

Added line #L237 was not covered by tests
self.0.save_credential(cred).await.map_err(Into::into)
}
}
Expand All @@ -216,15 +248,49 @@
cipher: CipherView,
}

#[derive(uniffi::Enum)]
pub enum UIHint {
InformExcludedCredentialFound(CipherView),
InformNoCredentialsFound,
RequestNewCredential(PublicKeyCredentialUserEntity, PublicKeyCredentialRpEntity),
RequestExistingCredential(CipherView),
}

impl From<bitwarden::platform::fido2::UIHint<'_, CipherView>> for UIHint {
fn from(hint: bitwarden::platform::fido2::UIHint<'_, CipherView>) -> Self {
use bitwarden::platform::fido2::UIHint as BWUIHint;
match hint {
BWUIHint::InformExcludedCredentialFound(cipher) => {
UIHint::InformExcludedCredentialFound(cipher.clone())

Check warning on line 264 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L260-L264

Added lines #L260 - L264 were not covered by tests
}
BWUIHint::InformNoCredentialsFound => UIHint::InformNoCredentialsFound,
BWUIHint::RequestNewCredential(user, rp) => UIHint::RequestNewCredential(
PublicKeyCredentialUserEntity {
id: user.id.clone().into(),
name: user.name.clone().unwrap_or_default(),
display_name: user.display_name.clone().unwrap_or_default(),
},
PublicKeyCredentialRpEntity {
id: rp.id.clone(),
name: rp.name.clone(),
},
),
BWUIHint::RequestExistingCredential(cipher) => {
UIHint::RequestExistingCredential(cipher.clone())

Check warning on line 279 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L266-L279

Added lines #L266 - L279 were not covered by tests
}
}
}

Check warning on line 282 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L282

Added line #L282 was not covered by tests
}

#[async_trait::async_trait]
impl bitwarden::platform::fido2::UserInterface for UniffiTraitBridge<&dyn UserInterface> {
async fn check_user(
impl bitwarden::platform::fido2::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> {
async fn check_user<'a>(
&self,
options: CheckUserOptions,
credential: Option<CipherView>,
) -> BitResult<bitwarden::platform::fido2::CheckUserResult> {
hint: bitwarden::platform::fido2::UIHint<'a, CipherView>,
) -> Result<bitwarden::platform::fido2::CheckUserResult, BitFido2CallbackError> {

Check warning on line 291 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L291

Added line #L291 was not covered by tests
self.0
.check_user(options, credential)
.check_user(options.clone(), hint.into())

Check warning on line 293 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L293

Added line #L293 was not covered by tests
.await
.map(|r| bitwarden::platform::fido2::CheckUserResult {
user_present: r.user_present,
Expand All @@ -234,23 +300,26 @@
}
async fn pick_credential_for_authentication(
&self,
available_credentials: Vec<Cipher>,
) -> BitResult<CipherView> {
available_credentials: Vec<CipherView>,
) -> Result<CipherView, BitFido2CallbackError> {

Check warning on line 304 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L304

Added line #L304 was not covered by tests
self.0
.pick_credential_for_authentication(available_credentials)
.await
.map(|v| v.cipher)
.map_err(Into::into)
}
async fn pick_credential_for_creation(
async fn check_user_and_pick_credential_for_creation(
&self,
available_credentials: Vec<Cipher>,
new_credential: Fido2Credential,
) -> BitResult<CipherView> {
options: CheckUserOptions,
new_credential: Fido2CredentialNewView,
) -> Result<CipherView, BitFido2CallbackError> {

Check warning on line 315 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L315

Added line #L315 was not covered by tests
self.0
.pick_credential_for_creation(available_credentials, new_credential)
.check_user_and_pick_credential_for_creation(options, new_credential)

Check warning on line 317 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L317

Added line #L317 was not covered by tests
.await
.map(|v| v.cipher)
.map_err(Into::into)
}
async fn is_verification_enabled(&self) -> bool {
self.0.is_verification_enabled().await
}

Check warning on line 324 in crates/bitwarden-uniffi/src/platform/fido2.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-uniffi/src/platform/fido2.rs#L322-L324

Added lines #L322 - L324 were not covered by tests
}
7 changes: 6 additions & 1 deletion crates/bitwarden/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ uniffi = [
"bitwarden-crypto/uniffi",
"bitwarden-generators/uniffi",
"dep:uniffi",
"dep:passkey",
"dep:coset",
"dep:p256",
] # Uniffi bindings
secrets = [] # Secrets manager API
wasm-bindgen = ["chrono/wasmbind"]
Expand All @@ -44,11 +47,13 @@ chrono = { version = ">=0.4.26, <0.5", features = [
"serde",
"std",
], default-features = false }
coset = { version = "0.3.7", optional = true }
# We don't use this directly (it's used by rand), but we need it here to enable WASM support
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" }
p256 = { version = ">=0.13.2, <0.14", optional = true }
passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "c48c2ddfd6b884b2d754432576c66cb2b1985a3a", optional = true }
rand = ">=0.8.5, <0.9"
reqwest = { version = ">=0.12, <0.13", features = [
"http2",
Expand Down
Loading
Loading