Skip to content

feat(crypto): add user-key id tracking - #1307

Draft
quexten wants to merge 12 commits into
km/sync-handlerfrom
km/user-id
Draft

feat(crypto): add user-key id tracking#1307
quexten wants to merge 12 commits into
km/sync-handlerfrom
km/user-id

Conversation

@quexten

@quexten quexten commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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

bitwarden/server#8076
#1307
bitwarden/clients#22128

We want to track the current key ID of the user-key. This is required for encryption stability. With follow-up work this will allow us to:

  • Reject KDF Changes, Password changes, Account Recovery Enrollments, Emergency Access Enrollements, Vault Item Modifications, Vault Item Creations, that have a wrong user-key contained / used for encryption. The cryptographic metadata is compared against the stored key id.
  • Find bugs easily leading to the above encryption stability

The user key id is set as part of the registration for new users. For existing users an upgrade endpoint that can be used once is provided. Key rotations will also set the key id.

Password changes, and kdf changes may not change the key ID and will reject in case the user had a wrong user-key in their client.

SDK Specific notes

We do not want to add syncing logic in Typescript for this. A new crate is introduced "km sync handler" which from now on will handle receiving a sync, and setting data to state. Further, it may react to events -- in this case no key-id being present on the sync; in which case it will post up a new key id to the server.

Please note that AES256_CBC_HMAC_SHA256 keys do not currently support key ids. This will be added in a future ticket.

@quexten quexten changed the title Km/user feat(crypto): add user-key id tracking Jul 29, 2026
@quexten quexten added the t:feat label Jul 29, 2026
kdf,
master_key_wrapped_user_key: user_key.clone(),
salt: email.to_string(),
user_key_id: None,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thought: Would be nice if we didn't explicitly define this everywhere. Currently any change to unlock methods requires approval from like 4 teams.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🔍 SDK Breaking Change Detection

SDK Version: km/user-id (d9f7f5a)

⚠️ If breaking changes are detected, a corresponding pull request addressing them must be ready for merge in the affected client repository.

Client Status Details
typescript ❌ Breaking changes detected Compilation failed with new SDK version. A corresponding pull request addressing the breaking changes must be ready for merge in bitwarden/clients. - View Details
android ✅ No breaking changes detected Compilation passed with new SDK version - View Details

Breaking change detection uses the build of the SDK from this branch, including any incompatibities pre-existing on or merged into this branch. Check the workflow logs to confirm.
Results update as workflows complete.

}

#[cfg(feature = "wasm")]
impl TryFrom<wasm_bindgen::JsValue> for Kdf {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note: will collide with #1297

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.81743% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.79%. Comparing base (f3dcd8e) to head (3d134d4).

Files with missing lines Patch % Lines
crates/bitwarden-crypto/src/keys/key_id.rs 79.26% 17 Missing ⚠️
...itwarden-user-crypto-management/src/user_key_id.rs 73.68% 15 Missing ⚠️
...den-crypto-sync-handler/src/crypto_sync_handler.rs 91.80% 5 Missing ⚠️
crates/bitwarden-core/src/client/test_accounts.rs 50.00% 1 Missing ⚠️
crates/bitwarden-crypto/src/uniffi_support.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                 @@
##           km/sync-handler    #1307      +/-   ##
===================================================
+ Coverage            85.76%   85.79%   +0.02%     
===================================================
  Files                  494      495       +1     
  Lines                71013    71253     +240     
===================================================
+ Hits                 60906    61130     +224     
- Misses               10107    10123      +16     

☔ View full report in Codecov by Harness.
📢 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.

@quexten quexten added the ai-review Request a Claude code review label Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the user-key-id tracking change against base km/sync-handler: the new KeyId string representation (hex, serde, WASM/UniFFI custom type), the user_key_id field threaded through MasterPasswordUnlockData and the two key-rotation request models, the new post_user_key_id backfill in bitwarden-user-crypto-management, and its invocation from handle_user_key_id in the crypto sync handler. Traced the rotation paths and confirmed the new user key is always XAes256Gcm, so rotations always carry a key id; the registration path deliberately omits it and relies on the sync backfill instead. The key store context in current_user_key_id is scoped and dropped before the await, matching the crypto-crate rule, and the WASM ABI/Tagged<string> impls follow the existing EncString/SymmetricCryptoKey pattern. Integration tests cover the report/no-report cases end to end with the fetch mock, including the V1 (Aes256CbcHmac, no key id) and locked-client cases.

Code Review Details

No new findings this round.

Previously raised items were handled by the author: the backfill retry behaviour on crypto_sync_handler.rs:118-128 was explicitly accepted, and the network-reaching unit test called out earlier was removed in 3d134d4. The remaining handle_user_key_id unit tests exercise only the early-return paths, so they make no API calls.

Dependency Changes

No third-party dependency changes. bitwarden-crypto-sync-handler gains a workspace-internal dependency on bitwarden-user-crypto-management (plus matching wasm/uniffi feature wiring), with Cargo.lock updated to match. No dependency cycle is introduced — bitwarden-user-crypto-management does not depend back on the sync-handler crate.

Comment thread crates/bitwarden-km-sync-handler/src/km_sync_handler.rs Outdated
Comment thread crates/bitwarden-km-sync-handler/src/lib.rs Outdated
Comment thread crates/bitwarden-core/src/key_management/master_password.rs
Comment thread crates/bitwarden-pm/src/lib.rs
@quexten
quexten changed the base branch from main to km/sync-handler July 31, 2026 09:35
Comment thread crates/bitwarden-pm/src/lib.rs Outdated
Comment thread crates/bitwarden-km-sync-handler/src/km_sync_handler.rs Outdated
Comment on lines +118 to +128
async fn handle_user_key_id(client: &Client, data: &CryptoSyncData) {
let server_user_key_id = data
.user_decryption
.as_ref()
.and_then(|d| d.user_key_id.as_ref());
if server_user_key_id.is_some() {
return;
}

info!("Server has no user key id; attempting to report the current one");
match client.user_crypto_management().post_user_key_id().await {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: The backfill is attempted on every sync with no memoization or capability check, so accounts that can never supply a key id retry forever.

Details and fix

Two paths repeat indefinitely because nothing records that the attempt already happened:

  1. V1 accounts (the majority today). An Aes256CbcHmac user key carries no key id, which the PR description calls out as unsupported for now. Every sync logs Server has no user key id; attempting to report the current one followed by No user key id available to report. Two INFO lines per sync, per account, forever.
  2. Servers without POST /accounts/key-management/user-key-id. Self-hosted deployments lag client releases, so user_key_id stays absent in the sync response and each sync spends a round trip on a 404 plus a warn!.

Cheapest fix for the first path is to check whether the key can supply an id before announcing the attempt, so the log only fires when there is something to report:

// Nothing to report for key algorithms that carry no key id (V1 accounts).
match client.user_crypto_management().post_user_key_id().await {
    Ok(()) => info!("Reported the user key id to the server"),
    Err(PostUserKeyIdError::UserKeyNotAvailable | PostUserKeyIdError::NoKeyId) => {
        debug!("No user key id available to report");
    }
    Err(e) => warn!("Failed to report the user key id: {e:?}"),
}

The second path needs the attempt to be remembered (state bridge flag, or a once-per-session guard on the handler) or gated on server capability, otherwise every sync on an older server pays for a request that cannot succeed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accepted.

Comment thread crates/bitwarden-crypto-sync-handler/src/crypto_sync_handler.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review breaking-change t:feat

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant