Skip to content

Commit

Permalink
[PM-8301] Create bitwarden-fido crate (#840)
Browse files Browse the repository at this point in the history
Create bitwarden-fido for authenticator logic.
  • Loading branch information
Hinton authored Jun 14, 2024
1 parent 20d176a commit 882ccaf
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 87 deletions.
31 changes: 21 additions & 10 deletions .github/workflows/publish-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ on:
required: true
default: true
type: boolean
publish_bitwarden-generators:
description: "Publish bitwarden-generators crate"
publish_bitwarden-exporters:
description: "Publish bitwarden-exporters crate"
required: true
default: true
type: boolean
publish_bitwarden-exporters:
description: "Publish bitwarden-exporters crate"
publish_bitwarden-fido:
description: "Publish bitwarden-fido crate"
required: true
default: true
type: boolean
publish_bitwarden-generators:
description: "Publish bitwarden-generators crate"
required: true
default: true
type: boolean
Expand Down Expand Up @@ -99,8 +104,9 @@ jobs:
PUBLISH_BITWARDEN_CORE: ${{ github.event.inputs.publish_bitwarden-core }}
PUBLISH_BITWARDEN_CRYPTO: ${{ github.event.inputs.publish_bitwarden-crypto }}
PUBLISH_BITWARDEN_CLI: ${{ github.event.inputs.publish_bitwarden-cli }}
PUBLISH_BITWARDEN_GENERATORS: ${{ github.event.inputs.publish_bitwarden-generators }}
PUBLISH_BITWARDEN_EXPORTERS: ${{ github.event.inputs.publish_bitwarden-exporters }}
PUBLISH_BITWARDEN_FIDO: ${{ github.event.inputs.publish_bitwarden-fido }}
PUBLISH_BITWARDEN_GENERATORS: ${{ github.event.inputs.publish_bitwarden-generators }}
PUBLISH_BITWARDEN_SEND: ${{ github.event.inputs.publish_bitwarden-send }}
PUBLISH_BITWARDEN_VAULT: ${{ github.event.inputs.publish_bitwarden-vault }}
run: |
Expand Down Expand Up @@ -144,16 +150,21 @@ jobs:
PACKAGES_LIST="$PACKAGES_LIST bitwarden-cli"
fi
if [[ "$PUBLISH_BITWARDEN_GENERATORS" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-generators"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-generators"
fi
if [[ "$PUBLISH_BITWARDEN_EXPORTERS" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-exporters"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-exporters"
fi
if [[ "$PUBLISH_BITWARDEN_FIDO" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-fido"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-fido"
fi
if [[ "$PUBLISH_BITWARDEN_GENERATORS" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-generators"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-generators"
fi
if [[ "$PUBLISH_BITWARDEN_SEND" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-send"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-send"
Expand Down
27 changes: 24 additions & 3 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bitwarden-cli = { path = "crates/bitwarden-cli", version = "=0.5.0" }
bitwarden-core = { path = "crates/bitwarden-core", version = "=0.5.0" }
bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=0.5.0" }
bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=0.5.0" }
bitwarden-fido = { path = "crates/bitwarden-fido", version = "=0.5.0" }
bitwarden-generators = { path = "crates/bitwarden-generators", version = "=0.5.0" }
bitwarden-send = { path = "crates/bitwarden-send", version = "=0.5.0" }
bitwarden-vault = { path = "crates/bitwarden-vault", version = "=0.5.0" }
Expand Down
41 changes: 41 additions & 0 deletions crates/bitwarden-fido/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "bitwarden-fido"
description = """
Internal crate for the bitwarden crate. Do not use.
"""

version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[features]
uniffi = ["dep:uniffi"]

[dependencies]
async-trait = ">=0.1.80, <0.2"
base64 = ">=0.21.2, <0.23"
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { version = ">=0.4.26, <0.5", features = [
"clock",
"serde",
], default-features = false }
coset = { version = "0.3.7" }
log = ">=0.4.18, <0.5"
p256 = { version = ">=0.13.2, <0.14" }
passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "c48c2ddfd6b884b2d754432576c66cb2b1985a3a" }
reqwest = { version = ">=0.12, <0.13", default-features = false }
serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
thiserror = ">=1.0.40, <2.0"
uniffi = { version = "=0.27.2", optional = true }
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }

[lints]
workspace = true
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Mutex;
use std::sync::{Arc, Mutex};

use bitwarden_core::VaultLocked;
use bitwarden_crypto::{CryptoError, KeyEncryptable};
use bitwarden_crypto::{CryptoError, KeyContainer, KeyEncryptable};
use bitwarden_vault::{CipherError, CipherView, Fido2CredentialView};
use log::error;
use passkey::{
Expand All @@ -18,11 +18,8 @@ use super::{
Fido2CredentialStore, Fido2UserInterface, SelectedCredential, UnknownEnum, AAGUID,
};
use crate::{
platform::fido2::{
fill_with_credential, string_to_guid_bytes, try_from_credential_full, Fido2CallbackError,
FillCredentialError, InvalidGuid,
},
Client,
fill_with_credential, string_to_guid_bytes, try_from_credential_full, Fido2CallbackError,
FillCredentialError, InvalidGuid,
};

#[derive(Debug, Error)]
Expand Down Expand Up @@ -76,16 +73,36 @@ pub enum SilentlyDiscoverCredentialsError {
Fido2CallbackError(#[from] Fido2CallbackError),
}

/// Temporary trait for solving a circular dependency. When moving `Client` to `bitwarden-core`
/// remove this trait.
pub trait FidoEncryptionSettingStore: Send + Sync {
fn get_encryption_settings(&self) -> Result<Arc<dyn KeyContainer>, VaultLocked>;
}

pub struct Fido2Authenticator<'a> {
pub(crate) client: &'a Client,
pub(crate) user_interface: &'a dyn Fido2UserInterface,
pub(crate) credential_store: &'a dyn Fido2CredentialStore,
pub client: &'a dyn FidoEncryptionSettingStore,
pub user_interface: &'a dyn Fido2UserInterface,
pub credential_store: &'a dyn Fido2CredentialStore,

pub(crate) selected_cipher: Mutex<Option<CipherView>>,
pub(crate) requested_uv: Mutex<Option<UV>>,
}

impl<'a> Fido2Authenticator<'a> {
pub fn new(
client: &'a dyn FidoEncryptionSettingStore,
user_interface: &'a dyn Fido2UserInterface,
credential_store: &'a dyn Fido2CredentialStore,
) -> Fido2Authenticator<'a> {
Fido2Authenticator {
client,
user_interface,
credential_store,
selected_cipher: Mutex::new(None),
requested_uv: Mutex::new(None),
}
}

pub async fn make_credential(
&mut self,
request: MakeCredentialRequest,
Expand Down Expand Up @@ -225,7 +242,7 @@ impl<'a> Fido2Authenticator<'a> {

Ok(result
.into_iter()
.flat_map(|c| c.decrypt_fido2_credentials(&enc))
.flat_map(|c| c.decrypt_fido2_credentials(&*enc))
.flatten()
.collect())
}
Expand Down Expand Up @@ -268,7 +285,7 @@ impl<'a> Fido2Authenticator<'a> {
.clone()
.ok_or(GetSelectedCredentialError::NoSelectedCredential)?;

let creds = cipher.decrypt_fido2_credentials(&enc)?;
let creds = cipher.decrypt_fido2_credentials(&*enc)?;

let credential = creds
.first()
Expand Down Expand Up @@ -339,7 +356,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
if this.create_credential {
Ok(creds
.into_iter()
.map(|c| CipherViewContainer::new(c, &enc))
.map(|c| CipherViewContainer::new(c, &*enc))
.collect::<Result<_, _>>()?)
} else {
let picked = this
Expand All @@ -355,7 +372,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
.expect("Mutex is not poisoned")
.replace(picked.clone());

Ok(vec![CipherViewContainer::new(picked, &enc)?])
Ok(vec![CipherViewContainer::new(picked, &*enc)?])
}
}

Expand Down Expand Up @@ -411,7 +428,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
.clone()
.ok_or(InnerError::NoSelectedCredential)?;

selected.set_new_fido2_credentials(&enc, vec![cred])?;
selected.set_new_fido2_credentials(&*enc, vec![cred])?;

// Store the updated credential for later use
this.authenticator
Expand Down Expand Up @@ -481,7 +498,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
let cred = fill_with_credential(&selected.credential, cred)?;

let mut selected = selected.cipher;
selected.set_new_fido2_credentials(&enc, vec![cred])?;
selected.set_new_fido2_credentials(&*enc, vec![cred])?;

// Store the updated credential for later use
this.authenticator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl From<WebauthnError> for Fido2ClientError {
}

pub struct Fido2Client<'a> {
pub(crate) authenticator: Fido2Authenticator<'a>,
pub authenticator: Fido2Authenticator<'a>,
}

impl<'a> Fido2Client<'a> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub enum CoseKeyToPkcs8Error {
FailedToConvertP256PrivateKeyToPkcs8,
}

pub fn cose_key_to_pkcs8(cose_key: &CoseKey) -> Result<Vec<u8>, CoseKeyToPkcs8Error> {
pub(crate) fn cose_key_to_pkcs8(cose_key: &CoseKey) -> Result<Vec<u8>, CoseKeyToPkcs8Error> {
// cose_key.
let secret_key = private_key_from_cose_key(cose_key).map_err(|error| {
log::error!("Failed to extract private key from cose_key: {:?}", error);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::sync::Mutex;

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use bitwarden_crypto::KeyContainer;
use bitwarden_vault::{
Expand All @@ -8,14 +6,17 @@ use bitwarden_vault::{
use crypto::{CoseKeyToPkcs8Error, PrivateKeyFromSecretKeyError};
use passkey::types::{ctap2::Aaguid, Passkey};

#[cfg(feature = "uniffi")]
uniffi::setup_scaffolding!();

mod authenticator;
mod client;
mod crypto;
mod traits;
mod types;

pub use authenticator::{
Fido2Authenticator, GetAssertionError, MakeCredentialError, SilentlyDiscoverCredentialsError,
Fido2Authenticator, FidoEncryptionSettingStore, GetAssertionError, MakeCredentialError,
SilentlyDiscoverCredentialsError,
};
pub use client::{Fido2Client, Fido2ClientError};
pub use passkey::authenticator::UIHint;
Expand All @@ -33,45 +34,13 @@ pub use types::{
};

use self::crypto::{cose_key_to_pkcs8, pkcs8_to_cose_key};
use crate::Client;

// This is the AAGUID for the Bitwarden Passkey provider (d548826e-79b4-db40-a3d8-11116f7e8349)
// It is used for the Relaying Parties to identify the authenticator during registration
const AAGUID: Aaguid = Aaguid([
0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49,
]);

pub struct ClientFido2<'a> {
#[allow(dead_code)]
pub(crate) client: &'a Client,
}

impl<'a> ClientFido2<'a> {
pub fn create_authenticator(
&'a self,
user_interface: &'a dyn Fido2UserInterface,
credential_store: &'a dyn Fido2CredentialStore,
) -> Fido2Authenticator<'a> {
Fido2Authenticator {
client: self.client,
user_interface,
credential_store,
selected_cipher: Mutex::new(None),
requested_uv: Mutex::new(None),
}
}

pub fn create_client(
&'a self,
user_interface: &'a dyn Fido2UserInterface,
credential_store: &'a dyn Fido2CredentialStore,
) -> Fido2Client<'a> {
Fido2Client {
authenticator: self.create_authenticator(user_interface, credential_store),
}
}
}

#[allow(dead_code)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct SelectedCredential {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ pub struct AuthenticatorAssertionResponse {
mod tests {
use serde::{Deserialize, Serialize};

use crate::platform::fido2::types::AndroidClientData;
use super::AndroidClientData;

// This is a stripped down of the passkey-rs implementation, to test the
// serialization of the `ClientData` enum, and to make sure that () and None
Expand Down
9 changes: 9 additions & 0 deletions crates/bitwarden-fido/uniffi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[bindings.kotlin]
package_name = "com.bitwarden.fido"
generate_immutable_records = true
android = true

[bindings.swift]
ffi_module_name = "BitwardenFidoFFI"
module_name = "BitwardenFido"
generate_immutable_records = true
1 change: 1 addition & 0 deletions crates/bitwarden-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async-trait = "0.1.80"
bitwarden = { workspace = true, features = ["internal", "uniffi"] }
bitwarden-core = { workspace = true, features = ["uniffi"] }
bitwarden-crypto = { workspace = true, features = ["uniffi"] }
bitwarden-fido = { workspace = true, features = ["uniffi"] }
bitwarden-generators = { workspace = true, features = ["uniffi"] }
bitwarden-send = { workspace = true, features = ["uniffi"] }
bitwarden-vault = { workspace = true, features = ["uniffi"] }
Expand Down
Loading

0 comments on commit 882ccaf

Please sign in to comment.