Skip to content

Conversation

@dani-garcia
Copy link
Member

@dani-garcia dani-garcia commented May 12, 2025

🎟️ Tracking

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

📔 Objective

Currently any function that adds/decrypts a key needs to provide the KeyId under which it can be stored. This is fairly cumbersome and error prone as it can lead to accidentally using the wrong key.

This PR changes all the functions that store a key in a context to instead generate and return a unique ID, which simplifies the code and makes the code less error prone.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation
    team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed
    issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@dani-garcia dani-garcia changed the title Make context local Ids autogenerated [PM-18102] Use opaque autogenerated local key ids May 12, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 12, 2025

Logo
Checkmarx One – Scan Summary & Details1b57de8c-e495-4613-9b5e-6f491197227c

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

@codecov
Copy link

codecov bot commented May 12, 2025

Codecov Report

❌ Patch coverage is 97.54098% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.36%. Comparing base (d553d37) to head (60e0b6a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...bitwarden-vault/src/cipher/cipher_client/create.rs 0.00% 1 Missing ⚠️
...s/bitwarden-vault/src/cipher/cipher_client/edit.rs 0.00% 1 Missing ⚠️
...es/bitwarden-vault/src/cipher/cipher_client/mod.rs 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #274      +/-   ##
==========================================
- Coverage   78.37%   78.36%   -0.01%     
==========================================
  Files         291      291              
  Lines       29391    29343      -48     
==========================================
- Hits        23034    22994      -40     
+ Misses       6357     6349       -8     

☔ 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-crypto/src/store/context.rs
#	crates/bitwarden-crypto/src/traits/key_id.rs
# Conflicts:
#	crates/bitwarden-crypto/src/store/context.rs
#	crates/bitwarden-vault/src/cipher/attachment.rs
#	crates/bitwarden-wasm-internal/src/pure_crypto.rs
@sonarqubecloud
Copy link

# Conflicts:
#	crates/bitwarden-core/src/key_management/crypto.rs
#	crates/bitwarden-crypto/src/store/context.rs
#	crates/bitwarden-vault/src/cipher/cipher.rs
#	crates/bitwarden-wasm-internal/src/pure_crypto.rs
@sonarqubecloud
Copy link

# Conflicts:
#	crates/bitwarden-crypto/src/safe/password_protected_key_envelope.rs
// New user key
let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
#[allow(deprecated)]
ctx.set_symmetric_key(temporary_user_key_id, user_key.clone())?;
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that this key was set but never used, so I've removed it here

@quexten quexten self-assigned this Oct 13, 2025
@claude
Copy link

claude bot commented Oct 13, 2025

Claude finished @dani-garcia's task —— View job


Code Review Complete

This PR refactors key management to use opaque autogenerated local key identifiers instead of requiring callers to provide KeyIds when adding/decrypting keys. This is a solid improvement that reduces error-proneness and simplifies the codebase.

Summary of Changes

The PR introduces LocalId (an opaque UUID wrapper) and modifies key storage functions to:

  • Return autogenerated LocalId values instead of requiring callers to provide them
  • Add add_local_symmetric_key(), add_local_asymmetric_key(), and add_local_signing_key() methods
  • Deprecate set_*_key() methods (marked with #[deprecated])
  • Update all call sites to use the new pattern

Net Impact: -41 lines of code, improved safety, better developer ergonomics.


Critical Issues

⚠️ 1. Missing test coverage for new_local() panic path

Location: crates/bitwarden-crypto/src/traits/key_id.rs:118-122

The key_ids! macro generates a new_local() implementation that may panic at runtime if called on a non-local variant:

fn new_local(id: LocalId) -> Self {
    $(
        { key_ids!(@new_local $variant  id $( $variant_tag )? ) }
    )*
}

The macro expansion produces empty blocks {} for non-local variants, which could cause unexpected behavior. While the current usage pattern makes this unlikely, there are no tests verifying this edge case.

Recommendation: Add a test that verifies calling TestSymmKey::new_local() works correctly for local variants and consider documenting the panic behavior or making this safer (e.g., returning Option<Self>).


Security Assessment

No security issues identified

The changes align well with CLAUDE.md security requirements:

  • No secrets logged in errors or debug output (LocalId is just a UUID)
  • Backward compatible - no changes to encryption/decryption algorithms
  • No changes to serialization format of encrypted data
  • Changes are internal to the SDK, not user-facing API changes

The move to opaque IDs actually improves security posture by:

  1. Reducing the chance of key ID collisions
  2. Making it harder to accidentally use the wrong key
  3. Centralizing key ID generation in trusted code paths

Suggested Improvements

📝 2. Inconsistent use of #[allow(deprecated)] annotations

The PR deprecates several functions but inconsistently applies #[allow(deprecated)] at call sites:

  • ✅ Used in tests: context.rs:576, cipher.rs:1233, attachment.rs:218
  • ❌ Missing in production code that still calls deprecated functions

Locations needing review:

  • crates/bitwarden-crypto/src/store/context.rs:192-193 - calls set_symmetric_key()
  • crates/bitwarden-crypto/src/store/context.rs:268-269 - calls set_symmetric_key()
  • crates/bitwarden-crypto/src/store/context.rs:321-322 - calls set_symmetric_key()
  • crates/bitwarden-crypto/src/store/context.rs:350-354 - calls set_symmetric_key()

Recommendation: Either add #[allow(deprecated)] to these locations or refactor them to use the new API. The follow-up work mentioned by @quexten should clean these up.

🎨 3. Follow-up cleanup needed (already noted in review)

As mentioned by @mzieniukbw and @quexten in the review comments, follow-up work should:

  • Remove deprecated set_symmetric_key(), set_asymmetric_key(), set_signing_key() functions
  • Remove local key ID support from the deprecated functions
  • Consider whether add_local_* should be the only way to add local keys

This is tracked as acceptable follow-up work.

ℹ️ 4. Documentation could clarify LocalId lifecycle

Location: crates/bitwarden-crypto/src/traits/key_id.rs:43-52

The LocalId type has a comment about potential future scope information:

/// An opaque identifier for a local key. Currently only contains a unique ID, but it can be
/// extended to contain scope information to allow cleanup on scope exit.

Recommendation: Consider adding documentation about when LocalId values become invalid (when context is dropped/cleared) to help future maintainers understand the lifecycle.


Good Practices Observed

👍 Well-structured refactoring - Changes are focused and consistent across the codebase
👍 Backward compatibility maintained - Deprecated functions still work for existing code
👍 Test coverage - Comprehensive tests verify new behavior
👍 Clean abstractions - LocalId is properly encapsulated as an opaque type


Action Items for Author

  1. Optional: Add test coverage for new_local() edge cases (low priority - unlikely to cause issues in practice)
  2. 📋 Follow-up ticket: Remove deprecated functions and complete cleanup (already tracked by team)
  3. ℹ️ Optional: Add lifecycle documentation for LocalId (nice-to-have)

Conclusion

This is a well-executed refactoring that meaningfully improves code safety and ergonomics. The changes are backward compatible, properly tested, and aligned with security best practices. The critical path has no blocking issues.

Recommendation: ✅ Approve with minor suggestions for follow-up work


@quexten
Copy link
Contributor

quexten commented Oct 13, 2025

Noting for reviewers: I'll take over this PR for the time being (https://bitwarden.atlassian.net/browse/PM-18102?focusedCommentId=113387)

@quexten quexten marked this pull request as ready for review October 13, 2025 10:56
@quexten quexten requested a review from a team as a code owner October 13, 2025 10:56
@quexten quexten requested review from a team as code owners October 13, 2025 10:56
@quexten quexten requested a review from gbubemismith October 13, 2025 10:56
coroiu
coroiu previously approved these changes Oct 13, 2025
gbubemismith
gbubemismith previously approved these changes Oct 13, 2025
Copy link
Contributor

@gbubemismith gbubemismith left a comment

Choose a reason for hiding this comment

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

Vault changes look good

mzieniukbw
mzieniukbw previously approved these changes Oct 15, 2025
Comment on lines 190 to 196
let new_key_id = Ids::Symmetric::new_local(LocalId::new());

#[allow(deprecated)]
self.set_symmetric_key(new_key_id, key)?;

// Returning the new key identifier for convenience
Ok(new_key_id)
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Are we going to cleanup the deprecated functions with an equivalent add_local_symmetric_key later (same for asymmetric and signing keys) and remove the local key id support from set_symmetric_key function ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think that makes sense. Considering this is a fairly small nit I'll leave it as follow-up work.

@quexten quexten dismissed stale reviews from mzieniukbw, gbubemismith, and coroiu via 93fb29e October 22, 2025 16:26
@quexten
Copy link
Contributor

quexten commented Oct 22, 2025

Had to resolve some merge conflicts.

@sonarqubecloud
Copy link

Copy link
Member

@Hinton Hinton left a comment

Choose a reason for hiding this comment

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

Do we ever need to clear the local keys?

@quexten quexten merged commit 79eb8c4 into main Oct 23, 2025
53 checks passed
@quexten quexten deleted the ps/context-local-uuids branch October 23, 2025 14:34
@quexten
Copy link
Contributor

quexten commented Oct 23, 2025

Do we ever need to clear the local keys?

@Hinton

Considering the key ids are randomly generated now and the collision chance is basically zero, I don't think we need to. Dropping the context will also drop the keys.

I will defer to @dani-garcia though, maybe he has thought about other use-cases here before.

bw-ghapp bot pushed a commit to bitwarden/sdk-swift that referenced this pull request Oct 23, 2025
@dani-garcia
Copy link
Member Author

Do we ever need to clear the local keys?

@Hinton

Considering the key ids are randomly generated now and the collision chance is basically zero, I don't think we need to. Dropping the context will also drop the keys.

I will defer to @dani-garcia though, maybe he has thought about other use-cases here before.

@quexten

That's correct security-wise, the current UUIDs basically guarantee no posibility of collision so clearing them is not needed as the context teardown will take care of them all eventually.

There's still a small concern that if a lot of operations are done on that context, the keys will keep growing until the context is dropped. For example, if you were decrypting 5K ciphers individually, you might end up with 5K local keys at the end. This is why the encrypt_list and decrypt_list operations clear the local keys themselves, and why we recommend using those high level functions instead of operating on the context directly:

result.push(item.encrypt_composite(&mut ctx, key));
ctx.clear_local();

Ideally I'd like this to be handled automatically by introducing some scope concept to the keystore context and key identifiers, but I haven't found a satisfying solution just yet. Something like:

ctx.scope(|| {
    let key = ctx.decrypt_key(somedata);

   use_the_key(ctx, key);
   // Key gets automatically deleted from the store at the end of this scope
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants