Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion crates/bitwarden-exporters/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub fn encrypt_import(
if let Some(passkey) = passkey {
let passkeys = passkey.into_iter().map(|p| p.into()).collect();

view.set_new_fido2_credentials(ctx, passkeys)?;
view.set_new_fido2_credentials(passkeys)?;
}

// Select the encryption format based on the account's current security state, matching how
Expand Down
14 changes: 4 additions & 10 deletions crates/bitwarden-exporters/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl crate::Cipher {
let view: CipherView = key_store.decrypt(&cipher)?;

let r = match view.r#type {
CipherType::Login => crate::CipherType::Login(Box::new(from_login(&view, key_store)?)),
CipherType::Login => crate::CipherType::Login(Box::new(from_login(&view)?)),
CipherType::SecureNote => {
let s = require!(view.secure_note);
crate::CipherType::SecureNote(Box::new(s.into()))
Expand Down Expand Up @@ -93,10 +93,7 @@ impl From<PasswordHistoryView> for crate::PasswordHistory {
}

/// Convert a `LoginView` into a `crate::Login`.
fn from_login(
view: &CipherView,
key_store: &KeyStore<KeySlotIds>,
) -> Result<crate::Login, MissingFieldError> {
fn from_login(view: &CipherView) -> Result<crate::Login, MissingFieldError> {
let l = require!(view.login.clone());

Ok(crate::Login {
Expand All @@ -110,7 +107,7 @@ fn from_login(
.collect(),
totp: l.totp,
fido2_credentials: l.fido2_credentials.as_ref().and_then(|_| {
let credentials = view.get_fido2_credentials(&mut key_store.context()).ok()?;
let credentials = view.get_fido2_credentials().ok()?;
if credentials.is_empty() {
None
} else {
Expand Down Expand Up @@ -269,9 +266,6 @@ mod tests {

#[test]
fn test_from_login() {
let key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
let key_store = create_test_crypto_with_user_key(key);

let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
let view = CipherView {
r#type: CipherType::Login,
Expand Down Expand Up @@ -315,7 +309,7 @@ mod tests {
archived_date: None,
};

let login = from_login(&view, &key_store).unwrap();
let login = from_login(&view).unwrap();

assert_eq!(login.username, Some("test_username".to_string()));
assert_eq!(login.password, Some("test_password".to_string()));
Expand Down
62 changes: 21 additions & 41 deletions crates/bitwarden-fido/src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,16 @@ impl<'a> Fido2Authenticator<'a> {
rp_id: String,
user_handle: Option<Vec<u8>>,
) -> Result<Vec<Fido2CredentialAutofillView>, SilentlyDiscoverCredentialsError> {
let key_store = self.client.internal.get_key_store();
let result = self
.credential_store
.find_credentials(None, rp_id, user_handle)
.await?;

let mut ctx = key_store.context();
result
.into_iter()
.map(
|cipher| -> Result<Vec<Fido2CredentialAutofillView>, SilentlyDiscoverCredentialsError> {
Ok(Fido2CredentialAutofillView::from_cipher_view(&cipher, &mut ctx)?)
Ok(Fido2CredentialAutofillView::from_cipher_view(&cipher)?)
},
)
.flatten_ok()
Expand Down Expand Up @@ -326,16 +324,14 @@ impl<'a> Fido2Authenticator<'a> {
pub(super) fn get_selected_credential(
&self,
) -> Result<SelectedCredential, GetSelectedCredentialError> {
let key_store = self.client.internal.get_key_store();

let cipher = self
.selected_cipher
.lock()
.expect("Mutex is not poisoned")
.clone()
.ok_or(GetSelectedCredentialError::NoSelectedCredential)?;

let creds = cipher.decrypt_fido2_credentials(&mut key_store.context())?;
let creds = cipher.decrypt_fido2_credentials();

let credential = creds
.first()
Expand Down Expand Up @@ -400,13 +396,11 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
})
.collect();

let key_store = this.authenticator.client.internal.get_key_store();

// When using the credential for authentication we have to ask the user to pick one.
if this.create_credential {
Ok(creds
.into_iter()
.map(|c| CipherViewContainer::new(c, &mut key_store.context()))
.map(CipherViewContainer::new)
.collect::<Result<_, _>>()?)
} else {
let picked = this
Expand All @@ -422,10 +416,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {
.expect("Mutex is not poisoned")
.replace(picked.clone());

Ok(vec![CipherViewContainer::new(
picked,
&mut key_store.context(),
)?])
Ok(vec![CipherViewContainer::new(picked)?])
}
}

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

let key_store = this.authenticator.client.internal.get_key_store();

selected.set_new_fido2_credentials(&mut key_store.context(), vec![cred])?;
selected.set_new_fido2_credentials(vec![cred])?;

// Store the updated credential for later use
this.authenticator
Expand Down Expand Up @@ -553,10 +542,8 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> {

let cred = fill_with_credential(&selected.credential, cred)?;

let key_store = this.authenticator.client.internal.get_key_store();

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

// Store the updated credential for later use
this.authenticator
Expand Down Expand Up @@ -679,15 +666,12 @@ fn map_ui_hint(hint: UiHint<'_, CipherViewContainer>) -> UiHint<'_, CipherView>
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use bitwarden_core::{
Client,
key_management::{KeySlotIds, SymmetricKeySlotId},
};
use bitwarden_crypto::{KeyStoreContext, PrimitiveEncryptable, SymmetricCryptoKey};
use bitwarden_core::{Client, key_management::SymmetricKeySlotId};
use bitwarden_crypto::SymmetricCryptoKey;
use bitwarden_encoding::B64Url;
use bitwarden_vault::{
CipherListView, CipherRepromptType, CipherType, CipherView, EncryptionContext,
Fido2Credential, Fido2CredentialNewView, LoginView,
Fido2CredentialFullView, Fido2CredentialNewView, LoginView,
};
use passkey::authenticator::UiHint;

Expand Down Expand Up @@ -781,23 +765,22 @@ mod tests {
0x84, 0x05, 0x71,
];

fn create_test_cipher(ctx: &mut KeyStoreContext<KeySlotIds>) -> CipherView {
let key = SymmetricKeySlotId::User;
fn create_test_cipher() -> CipherView {
let key_value = B64Url::from(TEST_FIDO_P256_KEY).to_string();

let fido2_credential = Fido2Credential {
credential_id: TEST_FIDO_CREDENTIAL_ID.encrypt(ctx, key).unwrap(),
key_type: "public-key".to_string().encrypt(ctx, key).unwrap(),
key_algorithm: "ECDSA".to_string().encrypt(ctx, key).unwrap(),
key_curve: "P-256".to_string().encrypt(ctx, key).unwrap(),
key_value: key_value.encrypt(ctx, key).unwrap(),
rp_id: TEST_FIDO_RP_ID.encrypt(ctx, key).unwrap(),
user_handle: Some(TEST_FIDO_USER_HANDLE.encrypt(ctx, key).unwrap()),
let fido2_credential = Fido2CredentialFullView {
credential_id: TEST_FIDO_CREDENTIAL_ID.to_string(),
key_type: "public-key".to_string(),
key_algorithm: "ECDSA".to_string(),
key_curve: "P-256".to_string(),
key_value,
rp_id: TEST_FIDO_RP_ID.to_string(),
user_handle: Some(TEST_FIDO_USER_HANDLE.to_string()),
user_name: None,
counter: "0".to_string().encrypt(ctx, key).unwrap(),
counter: "0".to_string(),
rp_name: None,
user_display_name: None,
discoverable: "true".to_string().encrypt(ctx, key).unwrap(),
discoverable: "true".to_string(),
creation_date: "2024-06-07T14:12:36.150Z".parse().unwrap(),
};

Expand Down Expand Up @@ -865,10 +848,7 @@ mod tests {
.set_symmetric_key(SymmetricKeySlotId::User, user_key)
.unwrap();

let cipher = {
let mut ctx = client.internal.get_key_store().context();
create_test_cipher(&mut ctx)
};
let cipher = create_test_cipher();

let user_interface = MockUserInterface;
let credential_store = MockCredentialStore { cipher };
Expand Down
7 changes: 1 addition & 6 deletions crates/bitwarden-fido/src/client_fido.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,7 @@ impl ClientFido2 {
&self,
cipher_view: CipherView,
) -> Result<Vec<Fido2CredentialAutofillView>, DecryptFido2AutofillCredentialsError> {
let key_store = self.client.internal.get_key_store();

Ok(Fido2CredentialAutofillView::from_cipher_view(
&cipher_view,
&mut key_store.context(),
)?)
Ok(Fido2CredentialAutofillView::from_cipher_view(&cipher_view)?)
}
}

Expand Down
14 changes: 5 additions & 9 deletions crates/bitwarden-fido/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
#![doc = include_str!("../README.md")]

use bitwarden_core::key_management::KeySlotIds;
use bitwarden_crypto::KeyStoreContext;
use bitwarden_encoding::{B64Url, NotB64UrlEncodedError};
use bitwarden_vault::{
CipherError, CipherView, Fido2CredentialFullView, Fido2CredentialNewView, Fido2CredentialView,
};
use bitwarden_vault::{CipherError, CipherView, Fido2CredentialFullView, Fido2CredentialNewView};
use crypto::{CoseKeyToPkcs8Error, PrivateKeyFromSecretKeyError};
use passkey::types::{CredentialExtensions, Passkey, ctap2::Aaguid};

Expand Down Expand Up @@ -61,7 +57,7 @@ const AAGUID: Aaguid = Aaguid([
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct SelectedCredential {
cipher: CipherView,
credential: Fido2CredentialView,
credential: Fido2CredentialFullView,
}

// This container is needed so we can properly implement the TryFrom trait for Passkey
Expand All @@ -73,8 +69,8 @@ pub(crate) struct CipherViewContainer {
}

impl CipherViewContainer {
fn new(cipher: CipherView, ctx: &mut KeyStoreContext<KeySlotIds>) -> Result<Self, CipherError> {
let fido2_credentials = cipher.get_fido2_credentials(ctx)?;
fn new(cipher: CipherView) -> Result<Self, CipherError> {
let fido2_credentials = cipher.get_fido2_credentials()?;
Ok(Self {
cipher,
fido2_credentials,
Expand Down Expand Up @@ -149,7 +145,7 @@ pub enum FillCredentialError {

#[allow(missing_docs)]
pub fn fill_with_credential(
view: &Fido2CredentialView,
view: &Fido2CredentialFullView,
value: Passkey,
) -> Result<Fido2CredentialFullView, FillCredentialError> {
let cred_id: Vec<u8> = value.credential_id.into();
Expand Down
6 changes: 2 additions & 4 deletions crates/bitwarden-fido/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::{borrow::Cow, collections::HashMap};

use bitwarden_core::key_management::KeySlotIds;
use bitwarden_crypto::{CryptoError, KeyStoreContext};
use bitwarden_crypto::CryptoError;
use bitwarden_encoding::{B64Url, NotB64UrlEncodedError};
use bitwarden_vault::{CipherListView, CipherListViewType, CipherView, LoginListView};
use passkey::types::webauthn::UserVerificationRequirement;
Expand Down Expand Up @@ -70,9 +69,8 @@ impl Fido2CredentialAutofillView {
#[allow(missing_docs)]
pub fn from_cipher_view(
cipher: &CipherView,
ctx: &mut KeyStoreContext<KeySlotIds>,
) -> Result<Vec<Fido2CredentialAutofillView>, Fido2CredentialAutofillViewError> {
let credentials = cipher.decrypt_fido2_credentials(ctx)?;
let credentials = cipher.decrypt_fido2_credentials();

credentials
.iter()
Expand Down
4 changes: 2 additions & 2 deletions crates/bitwarden-uniffi/src/vault/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use bitwarden_collections::collection::CollectionId;
use bitwarden_core::OrganizationId;
use bitwarden_vault::{
Cipher, CipherListView, CipherView, DecryptCipherListResult, EncryptionContext,
Fido2CredentialView,
Fido2CredentialFullView,
};

use crate::Result;
Expand Down Expand Up @@ -42,7 +42,7 @@ impl CiphersClient {
pub fn decrypt_fido2_credentials(
&self,
cipher_view: CipherView,
) -> Result<Vec<Fido2CredentialView>> {
) -> Result<Vec<Fido2CredentialFullView>> {
Ok(self.0.decrypt_fido2_credentials(cipher_view)?)
}

Expand Down
21 changes: 8 additions & 13 deletions crates/bitwarden-vault/src/cipher/blob/conversions/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ impl From<&Fido2CredentialDataV1> for Fido2CredentialFullView {

#[cfg(test)]
mod tests {
use bitwarden_crypto::{CompositeEncryptable, Decryptable};
use chrono::{TimeZone, Utc};

use super::super::{CipherBlobV1, CipherTypeDataV1, LoginUriDataV1, test_support::*};
use crate::cipher::{
cipher::CipherType,
field::{FieldType, FieldView},
linked_id::{LinkedIdType, LoginLinkedIdType},
login::{Fido2Credential, Fido2CredentialFullView, LoginUriView, LoginView, UriMatchType},
login::{Fido2CredentialFullView, LoginUriView, LoginView, UriMatchType},
};

#[test]
Expand Down Expand Up @@ -185,7 +184,7 @@ mod tests {
let (key_store, key_id) = create_test_key_store();
let mut ctx = key_store.context_mut();

// Create fido2 credentials by encrypting a FullView
// The decrypted login view holds fully-decrypted FIDO2 credentials.
let fido2_full = Fido2CredentialFullView {
credential_id: "cred-123".to_string(),
key_type: "public-key".to_string(),
Expand All @@ -201,8 +200,6 @@ mod tests {
discoverable: "true".to_string(),
creation_date: Utc.with_ymd_and_hms(2024, 6, 1, 10, 30, 0).unwrap(),
};
let encrypted_fido2: Fido2Credential =
fido2_full.encrypt_composite(&mut ctx, key_id).unwrap();

let original = crate::CipherView {
name: "My Login".to_string(),
Expand All @@ -219,7 +216,7 @@ mod tests {
}]),
totp: Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string()),
autofill_on_page_load: Some(true),
fido2_credentials: Some(vec![encrypted_fido2]),
fido2_credentials: Some(vec![fido2_full]),
}),
fields: Some(vec![FieldView {
name: Some("Custom Field".to_string()),
Expand Down Expand Up @@ -278,15 +275,13 @@ mod tests {
assert_eq!(uris[0].r#match, Some(UriMatchType::Domain));
assert_eq!(uris[0].uri_checksum, None);

// Fido2 credentials should be re-encrypted
// Fido2 credentials round-trip as fully-decrypted views
let fido2 = login.fido2_credentials.unwrap();
assert_eq!(fido2.len(), 1);
// Decrypt to verify content survived the round-trip
let decrypted: Fido2CredentialFullView = fido2[0].decrypt(&mut ctx, key_id).unwrap();
assert_eq!(decrypted.credential_id, "cred-123");
assert_eq!(decrypted.counter, "42");
assert_eq!(decrypted.discoverable, "true");
assert_eq!(decrypted.rp_id, "example.com");
assert_eq!(fido2[0].credential_id, "cred-123");
assert_eq!(fido2[0].counter, "42");
assert_eq!(fido2[0].discoverable, "true");
assert_eq!(fido2[0].rp_id, "example.com");

// Fields and password history
assert_eq!(restored.fields.as_ref().unwrap().len(), 1);
Expand Down
Loading
Loading