Skip to content

[PM-31114] Introduce local user data key, set it to state on crypto init#806

Draft
mzieniukbw wants to merge 7 commits intomainfrom
km/pm-31055-local-user-data-key
Draft

[PM-31114] Introduce local user data key, set it to state on crypto init#806
mzieniukbw wants to merge 7 commits intomainfrom
km/pm-31055-local-user-data-key

Conversation

@mzieniukbw
Copy link
Contributor

@mzieniukbw mzieniukbw commented Mar 3, 2026

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-31114

📔 Objective

Introduced LocalUserDataKey, which will be used to decouple the direct use of user key for encryption in tool's code (like encrypted forwarders, password history).
This will allow for user key rotation without logout, since we just have to re-encrypt the local user data key with the new user key, without re-encrypting all other data.
On user crypto init, we create new LocalUserDataKey key, and set it to state, if not already set. For backwards compatibility the local user data key is user key wrapper user key - this allows us to use it as drop in replacement in all client's, which currently encrypt with user key directly. Later, when we start rolling out the encryption v2 (which comes with key rotation in background), the new user key will differ from the one used as local user data key, retaining backwards compatibility, without having to migrate user over.
In the future, the LocalUserDataKey will likely be generated.

🚨 Breaking Changes

Expected to fail to compile, until the new repository is registered in clients, see https://bitwarden.atlassian.net/browse/PM-31111

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Logo
Checkmarx One – Scan Summary & Detailsc00ea2a0-a730-4018-aa5b-ff9febc1549d

Great job! No new security vulnerabilities introduced in this pull request

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

🔍 SDK Breaking Change Detection Results

SDK Version: km/pm-31055-local-user-data-key (7d1c150)
Completed: 2026-03-04 23:42:53 UTC
Total Time: 248s

Client Status Details
typescript ❌ Breaking changes detected TypeScript compilation failed with new SDK version - View Details

Breaking change detection completed. View SDK workflow

@codecov
Copy link

codecov bot commented Mar 3, 2026

Codecov Report

❌ Patch coverage is 75.54348% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.74%. Comparing base (c73f916) to head (7d1c150).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/bitwarden-core/src/key_management/crypto.rs 81.52% 17 Missing ⚠️
...re/src/key_management/local_user_data_key_state.rs 48.48% 17 Missing ⚠️
...s/bitwarden-crypto/src/keys/local_user_data_key.rs 82.75% 10 Missing ⚠️
crates/bitwarden-pm/src/migrations.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #806      +/-   ##
==========================================
- Coverage   81.77%   81.74%   -0.03%     
==========================================
  Files         344      346       +2     
  Lines       41116    41207      +91     
==========================================
+ Hits        33623    33686      +63     
- Misses       7493     7521      +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

# Conflicts:
#	crates/bitwarden-core/src/client/encryption_settings.rs
#	crates/bitwarden-core/src/key_management/crypto.rs
@mzieniukbw mzieniukbw marked this pull request as ready for review March 4, 2026 16:44
@mzieniukbw mzieniukbw requested review from a team as code owners March 4, 2026 16:44
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

);

drop(_span_guard);
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a reason for this block scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The span guard does not have Send trait and can't be used with in async context - hence we have to drop it before first await in this function. This is also the reason why i moved all async code beneath that synchronous code block.
Do you want me to add a comment for clarity ? We could also simply make it a separate function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, makes sense. A drop(_span_guard); should also work, and is possibly easier to read, but it's unfortunate that the span requires so much manual work here..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have an easier time removing the span and instead using #[tracing::instrument] on the function definition, which should also define and manage a span for you.

#[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
#[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
pub fn encrypt_with_user_key(
&self,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Can we make this work on key context instead of taking a key reference?

pub fn encrypt_with_user_key(
&self,
user_key: &SymmetricCryptoKey,
) -> crate::error::Result<EncString> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Just use Result<EncString, CryptoError> or import crate::error::Result

encrypted_key: EncString,
}

bitwarden_state::register_repository_item!(String => LocalUserDataKeyState, "LocalUserDataKey");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we want to make this sdk managed? If so this is the right way to do it: https://github.com/bitwarden/sdk-internal/tree/main/crates/bitwarden-state#settings

///
/// Used for backwards compatibility: data previously encrypted with the user key can be
/// decrypted by the reconstructed `SymmetricCryptoKey`.
DerivedFromUserKey(BitwardenLegacyKeyBytes),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the reason why this is an enum. If we have an encrypted local user data key (encstring) do we still store the information about whether it was a migrated user key?

Besides this, I'd recommend not making another decrypted copy of keys. We generally want keys to live on the key context, so the struct here should be the wrapped version, with functions to set it to the key context. I.e:

pub struct WrappedLocalUserDataKey(EncString);

impl WrappedLocalUserDataKey {
  pub fn from_user_key(user_key_id: [...], ctx: [...])

  pub fn set_to_context(user_key_id: [...], local_user_key_id: [...], ctx: [...])
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, we are storing this in storage, hence it have to be wrapped, not decrypted as symmetric key. I will correct this.

@mzieniukbw mzieniukbw marked this pull request as draft March 5, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants