Skip to content

[PM-32695] Cipher blob encryption#1122

Draft
shane-melton wants to merge 9 commits into
vault/pm-32696/blob-aware-decryptfrom
vault/pm-32695/blob-encrypt
Draft

[PM-32695] Cipher blob encryption#1122
shane-melton wants to merge 9 commits into
vault/pm-32696/blob-aware-decryptfrom
vault/pm-32695/blob-encrypt

Conversation

@shane-melton
Copy link
Copy Markdown
Member

@shane-melton shane-melton commented May 22, 2026

🎟️ Tracking

PM-32695

📔 Objective

Potential alternative to #1076.

Routes blob-encrypted ciphers through the encrypt path symmetrically to the decrypt-side dispatch added in PM-32696. A new EncryptMode<CipherView> selects per-cipher whether to seal sensitive content into the blob
format or take the legacy field-level path, with CiphersClient choosing based on the user's security state version. Builds on #1121.

Key changes

  • New EncryptMode<T> enum (Blob(T) / Legacy(T)) with CompositeEncryptable<…, Cipher> impl on EncryptMode<CipherView>. The blob arm calls encrypt_blob_cipher; the legacy arm delegates to the existing
    CompositeEncryptable impl on CipherView. IdentifyKey delegates to the inner view so encrypt_list still picks the correct scope key.
  • CiphersClient::should_use_blob_encryption (lifted to a free function so admin paths can share it) gates dispatch: personal-vault ciphers qualify once the user's security state reaches BLOB_SECURITY_VERSION;
    organization ciphers stay legacy until PM-32430.
  • All encrypt entry points now wrap views in EncryptMode before calling key_store.encrypt: CiphersClient::encrypt, encrypt_list, encrypt_cipher_for_rotation, create_cipher / edit_cipher, and the admin
    equivalents.
  • New encrypt_blob_cipher_with_wrapping_key variant for encrypt_cipher_for_rotation, which installs the new user key under a Local slot rather than the view's natural User/Organization slot.
  • Cipher.name becomes Option<EncString> — blob-encrypted ciphers don't carry a top-level name (it lives inside the sealed blob). Strict-decrypt and legacy paths still treat a missing name as an error.
  • Decrypt-side dispatch refactored: is_blob_encrypted removed; replaced by try_parse_blob that parses the SealedCipherBlob once and passes it into decrypt_blob_cipher, so the legacy/blob branch and the unseal
    share work.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

🔍 SDK Breaking Change Detection

SDK Version: vault/pm-32695/blob-encrypt (f9c229f)

⚠️ 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 ❌ Breaking changes detected Compilation failed with new SDK version. A corresponding pull request addressing the breaking changes must be ready for merge in bitwarden/android. - 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

❌ Patch coverage is 92.40838% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.94%. Comparing base (c61e067) to head (f53c43c).
⚠️ Report is 15 commits behind head on vault/pm-32696/blob-aware-decrypt.

Files with missing lines Patch % Lines
crates/bitwarden-vault/src/cipher/cipher.rs 92.51% 11 Missing ⚠️
...es/bitwarden-vault/src/cipher/cipher_client/mod.rs 94.50% 5 Missing ⚠️
...arden-vault/src/cipher/cipher_client/admin/edit.rs 69.23% 4 Missing ⚠️
...den-vault/src/cipher/cipher_client/admin/create.rs 62.50% 3 Missing ⚠️
...bitwarden-vault/src/cipher/cipher_client/create.rs 70.00% 3 Missing ⚠️
...s/bitwarden-vault/src/cipher/cipher_client/edit.rs 92.30% 3 Missing ⚠️
Additional details and impacted files
@@                          Coverage Diff                          @@
##           vault/pm-32696/blob-aware-decrypt    #1122      +/-   ##
=====================================================================
+ Coverage                              83.92%   83.94%   +0.01%     
=====================================================================
  Files                                    435      435              
  Lines                                  56736    56918     +182     
=====================================================================
+ Hits                                   47614    47778     +164     
- Misses                                  9122     9140      +18     

☔ 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.

Comment on lines +1751 to +1757
pub(crate) fn blob_err_to_crypto(err: BlobEncryptionError) -> CryptoError {
tracing::warn!(error = %err, error_debug = ?err, "blob decryption failed");
match err {
BlobEncryptionError::Crypto(c) => c,
BlobEncryptionError::SealedBlob(_) | BlobEncryptionError::NoBlobData => {
CryptoError::Decrypt
BlobEncryptionError::SealedBlob(_) => CryptoError::Decrypt,
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would work better as impl From<BlobEncryptionError> for CryptoError{...}, which also would let you just use ? anywhere you need this conversion.

Comment on lines +49 to +57
if organization_id.is_some() {
return false;
}
client
.internal
.get_key_store()
.context()
.get_security_state_version()
>= BLOB_SECURITY_VERSION
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it work to put this logic inside the CompositeEncryptable impl for Cipher? Since it uses the KeyStoreContext internally and not feature flags this logic would still be compatible without needing CiphersClient internals.

@sonarqubecloud
Copy link
Copy Markdown

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.

2 participants