Skip to content

fix(desktop): implement AES-KW instead of BoringSSL-missing aes-256-wrap - #575

Merged
tomymaritano merged 3 commits into
developfrom
fix/aes-key-wrap-boringssl
Aug 22, 2026
Merged

fix(desktop): implement AES-KW instead of BoringSSL-missing aes-256-wrap#575
tomymaritano merged 3 commits into
developfrom
fix/aes-key-wrap-boringssl

Conversation

@tomymaritano

@tomymaritano tomymaritano commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

Creating a sync passphrase fails with Error: Unknown cipher.

encryptionService.wrapKey() called createCipheriv('aes-256-wrap', ...). That cipher exists in OpenSSL but not in BoringSSL, which is what Electron ships. The call therefore always threw in the main process and the CEK could never be wrapped.

Reproduced directly:

Runtime aes-256-wrap
Node 20 (OpenSSL 3.0.17) available
Electron 41.7.1 (BoringSSL, process.versions.openssl === '0.0.0') Unknown cipher

Vitest never caught this because it runs on Node/OpenSSL, where the cipher exists.

Fix

New aesKeyWrap.ts implements RFC 3394 over the raw AES block primitive BoringSSL does expose. encryptionService delegates to it.

Output is byte-identical to aes-256-wrap, so keys wrapped by earlier builds still unwrap — pinned by the RFC 3394 §4.6 known-answer vector (which I generated from OpenSSL's own aes-256-wrap before writing the implementation).

aes-256-ecb here is the single-block AES call the spec is defined on, with padding disabled — not a mode for bulk data. The chaining, counter and integrity check are RFC 3394's. This is documented at the top of the module.

Neither webcrypto.subtle AES-KW (Unrecognized algorithm name) nor any other built-in wrap is available in Electron, so reimplementing was the only option that preserves the on-disk format.

Verification

  • 6 new unit tests: KAT wrap, KAT unwrap, overhead size, tampered ciphertext rejected, wrong KEK rejected, cipher-name guard against a revert to aes-256-wrap.
  • Executed under the Electron 41.7.1 binary (ELECTRON_RUN_AS_NODE=1): wrap matches the vector, round-trip succeeds, wrong key rejected.
  • pnpm test — 19/19 tasks green.
  • pnpm --filter @dripnex/desktop typecheck — clean.
  • eslint — clean.

Note for a separate PR

.nvmrc pins Node 20, but packages/mcp-server needs >= 22.5 (node:sqlite) and packages/api needs >= 22 (wrangler). On Node 20 pnpm test and pnpm dev both fail for reasons unrelated to this change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved encrypted key wrapping and unwrapping compatibility.
    • Corrected passphrase handling so invalid or corrupted encrypted data is reliably rejected.
    • Added validation for malformed keys and tampered encrypted content.
  • Tests

    • Added coverage for standard AES key-wrapping compatibility, successful recovery, wrong passphrases, and modified data.

Creating a sync passphrase failed with `Error: Unknown cipher`.

`encryptionService.wrapKey()` called `createCipheriv('aes-256-wrap', ...)`.
That cipher exists in OpenSSL but not in BoringSSL, which is what Electron
ships — so the call always threw in the main process and the CEK could
never be wrapped. Vitest never caught it because it runs on Node/OpenSSL,
where the cipher is available.

Implement RFC 3394 directly over the raw AES block primitive BoringSSL does
expose. Output is byte-identical to `aes-256-wrap`, verified against the
RFC 3394 §4.6 known-answer vector, so keys wrapped by earlier builds still
unwrap.

Verified under the Electron 41.7.1 binary (BoringSSL): wrap matches the
vector, round-trip succeeds, and a wrong wrapping key is rejected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 141a8743-2b27-4b1b-b710-f7f4e3446b1b

📥 Commits

Reviewing files that changed from the base of the PR and between 268d7c2 and 3b1f672.

📒 Files selected for processing (3)
  • apps/desktop/src/main/services/__tests__/aesKeyWrap.test.ts
  • apps/desktop/src/main/services/aesKeyWrap.ts
  • apps/desktop/src/main/services/encryptionService.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds an RFC 3394 AES-256 key-wrap implementation using BoringSSL-compatible AES-ECB operations. The encryption service now uses the new helpers, with tests covering compatibility, integrity checks, and invalid keys.

Changes

AES key-wrap implementation

Layer / File(s) Summary
AES key-wrap primitives
apps/desktop/src/main/services/aesKeyWrap.ts
Defines the AES-ECB cipher constant, RFC 3394 constants, block operations, counter handling, and semiblock utilities.
RFC 3394 wrapping and validation
apps/desktop/src/main/services/aesKeyWrap.ts, apps/desktop/src/main/services/__tests__/aesKeyWrap.test.ts
Implements wrapping and unwrapping with size validation and default-IV verification. Tests cover the RFC vector, output overhead, tampering, wrong keys, and the cipher constant.
Encryption service integration
apps/desktop/src/main/services/encryptionService.ts
Replaces Node’s AES key-wrap cipher calls with the new helpers and removes the local IV constant. Unwrap errors now propagate from the helpers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 3b1f6

This PR replaces the unavailable desktop cipher implementation with a compatible AES key-wrapping implementation while preserving existing wrapped-key output; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes replacing the unavailable BoringSSL aes-256-wrap cipher with an AES-KW implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/aes-key-wrap-boringssl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tomymaritano
tomymaritano enabled auto-merge (squash) August 22, 2026 20:58
@tomymaritano
tomymaritano merged commit d35220f into develop Aug 22, 2026
16 checks passed
@tomymaritano
tomymaritano deleted the fix/aes-key-wrap-boringssl branch August 22, 2026 21:08
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.17.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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.

1 participant