Skip to content
Open
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
8 changes: 1 addition & 7 deletions crates/bitwarden-core/src/client/test_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use bitwarden_test::MemoryRepository;
use crate::{
Client, ClientSettings, UserId,
key_management::{
LocalUserDataKeyState, MasterPasswordUnlockData, UserKeyState,
LocalUserDataKeyState, MasterPasswordUnlockData,
account_cryptographic_state::WrappedAccountCryptographicState,
crypto::{InitOrgCryptoRequest, InitUserCryptoMethod, InitUserCryptoRequest},
},
Expand All @@ -23,12 +23,6 @@ impl Client {
/// Does not initialize any crypto state.
pub fn new_test(settings: Option<ClientSettings>) -> Self {
let client = Client::new(settings);
client
.platform()
.state()
.register_client_managed(std::sync::Arc::new(
MemoryRepository::<UserKeyState>::default(),
));
client
.platform()
.state()
Expand Down
8 changes: 3 additions & 5 deletions crates/bitwarden-core/src/key_management/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ use tracing::info;
use {tsify::Tsify, wasm_bindgen::prelude::*};

#[cfg(feature = "wasm")]
use crate::key_management::wasm_unlock_state::{
copy_user_key_to_client_managed_state, get_user_key_from_client_managed_state,
};
use crate::key_management::wasm_unlock_state::{copy_user_key_to_state, get_user_key_from_state};
use crate::{
Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
client::{
Expand Down Expand Up @@ -241,7 +239,7 @@ pub(super) async fn initialize_user_crypto(
}
#[cfg(feature = "wasm")]
InitUserCryptoMethod::ClientManagedState {} => {
let user_key = get_user_key_from_client_managed_state(client)
let user_key = get_user_key_from_state(client)
.await
.map_err(|_| EncryptionSettingsError::UserKeyStateRetrievalFailed)?;
client.internal.initialize_user_crypto_decrypted_key(
Expand Down Expand Up @@ -387,7 +385,7 @@ pub(super) async fn initialize_user_crypto(

#[cfg(feature = "wasm")]
if should_copy_user_key {
copy_user_key_to_client_managed_state(client)
copy_user_key_to_state(client)
.await
.map_err(|_| EncryptionSettingsError::UserKeyStateUpdateFailed)?;
}
Expand Down
27 changes: 1 addition & 26 deletions crates/bitwarden-core/src/key_management/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@
//! [CompositeEncryptable](bitwarden_crypto::CompositeEncryptable), and
//! [Decryptable](bitwarden_crypto::Decryptable).

use bitwarden_crypto::{
EncString, KeyStore, SymmetricCryptoKey, key_slot_ids, safe::PasswordProtectedKeyEnvelope,
};
use bitwarden_crypto::{EncString, KeyStore, SymmetricCryptoKey, key_slot_ids};

#[cfg(feature = "internal")]
pub mod account_cryptographic_state;
#[cfg(feature = "internal")]
pub mod crypto;
#[cfg(feature = "internal")]
mod crypto_client;
use bitwarden_encoding::B64;
#[cfg(feature = "internal")]
pub use crypto_client::CryptoClient;

Expand Down Expand Up @@ -68,18 +65,6 @@ pub mod state_bridge;

use crate::{OrganizationId, UserId};

/// Represents the decrypted symmetric user-key of a user. This is held in ephemeral state of the
/// client.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[repr(transparent)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct UserKeyState {
decrypted_user_key: B64,
}

bitwarden_state::register_repository_item!(String => UserKeyState, "UserKey");

/// Represents the local user data key, wrapped by user key.
/// This key is used to encrypt local user data (e.g., password generator history).
#[derive(Serialize, Deserialize, Debug, Clone)]
Expand All @@ -91,16 +76,6 @@ pub struct LocalUserDataKeyState {

bitwarden_state::register_repository_item!(UserId => LocalUserDataKeyState, "LocalUserDataKey");

/// Represents the PIN envelope in memory, when ephemeral PIN unlock is used.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct EphemeralPinEnvelopeState {
pin_envelope: PasswordProtectedKeyEnvelope,
}

bitwarden_state::register_repository_item!(String => EphemeralPinEnvelopeState, "EphemeralPinEnvelope");

key_slot_ids! {
#[symmetric]
pub enum SymmetricKeySlotId {
Expand Down
96 changes: 29 additions & 67 deletions crates/bitwarden-core/src/key_management/wasm_unlock_state.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
//! The WASM sdk currently does not hold persistent SDK instances and instead re-createds SDK
//! instances frequently. The unlock-state is lost, since the user-key is only held in the SDK. This
//! file implements setting the user-key to WASM client-managed ephemeral state, so that
//! SDK-re-creations have access to the user-key.
//! file implements setting the user-key into the KM state bridge, so that SDK-re-creations have
//! access to the user-key.
//!
//! This is not required on UNIFFI since there SDK instances live as long as the client is unlocked.
//! Eventually, the WASM sdk will also hold SDK instances like described above.

use bitwarden_crypto::SymmetricCryptoKey;
use tracing::info;

use crate::{
Client,
key_management::{self, SymmetricKeySlotId},
};

// The repository pattern requires us to specify a key. Here we use an empty string as the only
// key for this repository map.
const USER_KEY_REPOSITORY_KEY: &str = "";
use crate::{Client, key_management::SymmetricKeySlotId};

/// Error indicating inability to set the user key into state
pub(crate) struct UnableToSetError;
/// Sets the decrypted user key into the client-managed state, so that it survives re-creation of
/// Sets the decrypted user key into the KM state bridge, so that it survives re-creation of
/// the SDK
pub(crate) async fn copy_user_key_to_client_managed_state(
client: &Client,
) -> Result<(), UnableToSetError> {
pub(crate) async fn copy_user_key_to_state(client: &Client) -> Result<(), UnableToSetError> {
// Read the user-key from key-store. There should be no other reason to do this in other parts
// of the SDK. Do not use this as an example.
let user_key = {
Expand All @@ -36,67 +27,38 @@ pub(crate) async fn copy_user_key_to_client_managed_state(
.clone()
};

if let Ok(user_key_repository) = client
.platform()
.state()
.get::<key_management::UserKeyState>()
{
// We do not want to set the user-key if it is already set as that may trigger an observable
// loop in the client side which subscribes to the state
if let Ok(Some(existing_key)) = user_key_repository
.get(USER_KEY_REPOSITORY_KEY.to_string())
.await
{
if SymmetricCryptoKey::try_from(existing_key.decrypted_user_key)
.map_err(|_| UnableToSetError)?
== user_key
{
info!("User-key in client managed state is already up to date, skipping set");
return Ok(());
} else {
info!("User-key in client managed state is outdated, updating it");
}
} else {
info!("No user-key in client managed state, setting it");
let bridge = client.km_state_bridge();
if !bridge.is_bridge_registered() {
// No state bridge registered, older clients should just return gracefully.
info!("No state bridge registered, exiting gracefully");
return Ok(());
}

// We do not want to set the user-key if it is already set as that may trigger an observable
// loop in the client side which subscribes to the state
if let Some(existing_key) = bridge.get_user_key().await {
if existing_key == user_key {
info!("User-key in state bridge is already up to date, skipping set");
return Ok(());
}
info!("User-key in state bridge is outdated, updating it");
} else {
// UserKeyState repository does not exist, older clients should
// just return gracefully.
info!("No UserKeyState repository exists in client managed state, exiting gracefully");
return Ok(());
info!("No user-key in state bridge, setting it");
}

info!("Setting the user-key to client managed-state from SDK");
// Set the user-key into the state repository.
client
.platform()
.state()
.get::<key_management::UserKeyState>()
.map_err(|_| UnableToSetError)?
.set(
USER_KEY_REPOSITORY_KEY.to_string(),
key_management::UserKeyState {
decrypted_user_key: user_key.to_base64(),
},
)
.await
.map_err(|_| UnableToSetError)
info!("Setting the user-key to the state bridge from SDK");
bridge.set_user_key(&user_key).await;
Ok(())
}

pub(crate) struct UnableToGetError;
pub(crate) async fn get_user_key_from_client_managed_state(
pub(crate) async fn get_user_key_from_state(
client: &Client,
) -> Result<SymmetricCryptoKey, UnableToGetError> {
info!("Getting the user-key from client managed-state in SDK");
// Get the user-key from the state repository.
let user_key_state = client
.platform()
.state()
.get::<key_management::UserKeyState>()
.map_err(|_| UnableToGetError)?
.get(USER_KEY_REPOSITORY_KEY.to_string())
info!("Getting the user-key from the state bridge in SDK");
client
.km_state_bridge()
.get_user_key()
.await
.map_err(|_| UnableToGetError)?
.ok_or(UnableToGetError)?;
SymmetricCryptoKey::try_from(user_key_state.decrypted_user_key).map_err(|_| UnableToGetError)
.ok_or(UnableToGetError)
}
15 changes: 1 addition & 14 deletions crates/bitwarden-core/tests/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async fn test_register_initialize_crypto() {
use bitwarden_core::{
Client, UserId,
key_management::{
EphemeralPinEnvelopeState, MasterPasswordUnlockData, UserKeyState,
MasterPasswordUnlockData,
account_cryptographic_state::WrappedAccountCryptographicState,
crypto::{InitUserCryptoMethod, InitUserCryptoRequest},
},
Expand All @@ -21,26 +21,13 @@ async fn test_register_initialize_crypto() {

let client = Client::new(None);

let user_key_repository = MemoryRepository::<UserKeyState>::default();
client
.platform()
.state()
.register_client_managed(std::sync::Arc::new(user_key_repository));

client
.platform()
.state()
.register_client_managed(std::sync::Arc::new(
MemoryRepository::<LocalUserDataKeyState>::default(),
));

client
.platform()
.state()
.register_client_managed(std::sync::Arc::new(MemoryRepository::<
EphemeralPinEnvelopeState,
>::default()));

let email = "test@bitwarden.com";
let password = "test123";
let kdf = Kdf::PBKDF2 {
Expand Down
7 changes: 1 addition & 6 deletions crates/bitwarden-pm/src/migrations.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Manages repository migrations for the Bitwarden SDK.

use bitwarden_core::{
client::persisted_state::OrganizationSharedKey, key_management::UserKeyState,
};
use bitwarden_core::client::persisted_state::OrganizationSharedKey;
use bitwarden_state::{
SettingItem,
repository::{RepositoryItem, RepositoryMigrationStep, RepositoryMigrations},
Expand All @@ -17,7 +15,6 @@ pub fn get_sdk_managed_migrations() -> RepositoryMigrations {
// requires a separate migration step using `Remove(...)`.
Add(Cipher::data()),
Add(Folder::data()),
Add(UserKeyState::data()),
Add(SettingItem::data()),
Add(OrganizationSharedKey::data()),
])
Expand All @@ -36,9 +33,7 @@ macro_rules! create_client_managed_repositories {
// <fully qualified path to the item>, <item type idenfier>, <field name>, <name of the repository implementation>
::bitwarden_vault::Cipher, Cipher, cipher, CipherRepository;
::bitwarden_vault::Folder, Folder, folder, FolderRepository;
::bitwarden_core::key_management::UserKeyState, UserKeyState, user_key_state, UserKeyStateRepository;
::bitwarden_core::key_management::LocalUserDataKeyState, LocalUserDataKeyState, local_user_data_key_state, LocalUserDataKeyStateRepository;
::bitwarden_core::key_management::EphemeralPinEnvelopeState, EphemeralPinEnvelopeState, ephemeral_pin_envelope_state, EphemeralPinEnvelopeStateRepository;
::bitwarden_core::client::persisted_state::OrganizationSharedKey, OrganizationSharedKey, organization_shared_key, OrganizationSharedKeyRepository;
}
};
Expand Down
Loading