Skip to content

feat(cachet): add encrypt feature for authenticated value encryption#558

Open
schgoo wants to merge 12 commits into
mainfrom
cachet_encrypt
Open

feat(cachet): add encrypt feature for authenticated value encryption#558
schgoo wants to merge 12 commits into
mainfrom
cachet_encrypt

Conversation

@schgoo

@schgoo schgoo commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an optional encrypt feature to cachet that encrypts cache values
before they reach a fallback/remote tier, binding each value to its storage key so
it cannot be read back under a different key.

The feature ships only the encryption mechanism and carries no cryptographic
dependency of its own
— callers plug in a cipher backed by their approved
cryptographic library via .encrypt_with(cipher):

let cache = Cache::builder::<String, String>(clock)
    .memory()
    .serialize()
    .encrypt_with(my_cipher)   // any `AeadCipher` implementation
    .fallback(remote)
    .build();

Motivation

When a cache tiers down to an untrusted store (Redis, S3, etc.), values are
exposed at rest. This feature keeps the ergonomic typed cache API while
transparently encrypting values on the way out and decrypting them on the way
back, with no hand-rolled serialization/encryption pipeline. Shipping the
mechanism without any bundled crypto lets each consumer satisfy its own
cryptographic-library compliance requirements and keeps the crate
dependency-free and portable across all CI targets.

What it does

  • .encrypt_with(cipher) — available after .serialize() (once values are
    BytesView). Encrypts each value with a caller-supplied AeadCipher.
  • Key binding — the storage key is authenticated as associated data (AAD), so
    a ciphertext produced for one key fails to decrypt under any other key,
    preventing an attacker with write access to the backing store from relocating
    or swapping ciphertexts between keys.
  • Keys are not encrypted — they stay serialized-but-unencrypted to remain
    deterministic and lookupable, so secrets/PII must not be placed in cache keys.
  • Undecryptable entries read as a miss — a value that fails authentication
    (corrupt, truncated, wrong key, tampered, or relocated) is treated as a cache
    miss (Ok(None)) and emits a cache.decrypt_failed telemetry event so
    tampering is observable, consistent with the serialization codec's
    soft-failure behavior.

Design

  • Encryption is applied by an internal EncryptedTier installed at the storage
    boundary, where both the key and value are in scope — this is what makes
    key-as-AAD binding possible.
  • .encrypt_with() returns a dedicated EncryptedTransformBuilder (storage types
    fixed to BytesView) supporting .fallback() and .build(), mirroring
    TransformBuilder. It lives in its own builder/encrypt.rs, matching the
    serialize feature's file layout.
  • The public AeadCipher trait is the pluggable seam; the crate ships no
    implementation. A complete reference AeadCipher backed by SymCrypt
    (FIPS-certifiable AES-256-GCM) is included in the crate-level docs as a
    copy-paste example, rather than as a compiled feature — SymCrypt needs a native
    library at build/run time, which would force it onto every consumer and CI job.

Dependencies

None. The encrypt feature adds no cryptographic dependency; consumers bring
their own approved library.

Testing

  • Unit tests for the mechanism.
  • Integration tests drive the full .serialize().encrypt_with().fallback()
    pipeline through a crypto-free dummy AeadCipher (nonce + AAD authentication +
    a reversible keystream transform): stored bytes are ciphertext with the
    plaintext never appearing verbatim, values round-trip through the encrypted
    tier, a fresh nonce is used per insert, the boundary is reachable on a
    FallbackBuilder and through chained post-transform fallbacks, and a ciphertext
    relocated to a different key reads as a miss.

Copilot AI review requested due to automatic review settings July 9, 2026 18:24

Copilot AI left a comment

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.

Pull request overview

Adds an optional encrypt feature to cachet that introduces an authenticated encryption boundary for cache values (AES-256-GCM) before data reaches an untrusted fallback tier, binding ciphertext to the storage key via GCM AAD.

Changes:

  • Introduces AeadCipher/Aes256GcmCipher and an EncryptedTier that encrypts values and treats undecryptable entries as cache misses.
  • Adds .encrypt(&[u8; 32]) to the serialized builder pipeline via EncryptedTransformBuilder, supporting fallback chaining and build().
  • Adds unit + integration tests, docs/README updates, and feature-gated dependencies (aes-gcm, getrandom).

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/cachet/tests/encrypt.rs Integration tests for .serialize().encrypt().fallback() behavior and key-binding relocation defense.
crates/cachet/src/transform/mod.rs Wires in the encrypt transform module behind the encrypt feature gate.
crates/cachet/src/transform/encrypt.rs Implements AES-256-GCM cipher + EncryptedTier wrapper for value encryption/decryption at the storage boundary.
crates/cachet/src/lib.rs Documents the new encrypt feature and re-exports EncryptedTransformBuilder when enabled.
crates/cachet/src/builder/transform.rs Adjusts TransformBuilder field visibility to enable the .encrypt() builder transition.
crates/cachet/src/builder/mod.rs Adds the encrypt builder module and exports EncryptedTransformBuilder behind the feature.
crates/cachet/src/builder/encrypt.rs Implements .encrypt(&key) and the EncryptedTransformBuilder fallback/build pipeline.
crates/cachet/README.md Regenerated README content to document the new feature.
crates/cachet/Cargo.toml Adds the encrypt feature and its optional deps.
Cargo.toml Adds workspace dependency entries for aes-gcm and getrandom.
Cargo.lock Locks new crypto/randomness transitive dependencies.
.spelling Adds crypto-related terminology used by new docs/tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/cachet/src/builder/transform.rs Outdated
Comment on lines +197 to +200
// The storage key is authenticated as AAD, so a value planted under the
// wrong key fails decryption and is treated as a miss.
match self.cipher.decrypt(&key.to_vec(), &value)? {
DecodeOutcome::Value(value) => {
Comment thread crates/cachet/src/transform/encrypt.rs
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (0107169) to head (42c7fa9).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #558    +/-   ##
========================================
  Coverage   100.0%   100.0%            
========================================
  Files         360      362     +2     
  Lines       27886    28197   +311     
========================================
+ Hits        27886    28197   +311     

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

Comment thread crates/cachet/src/transform/encrypt.rs Outdated

use std::borrow::Cow;

use aes_gcm::aead::{Aead, AeadInPlace, KeyInit, Payload};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

From a compliance perspective, should this code really come from that crate? Would love to hear Sergey's input here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've converted it to take an AES cipher implementation as input, so we don't have to be opinionated about the crypto library. But I also added a symcrypt implementation behind a feature gate, rather than use aes_gcm.

@ralfbiedert ralfbiedert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Adding a blocker for now until we know more about that)

Copilot AI review requested due to automatic review settings July 14, 2026 16:02

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Comment thread crates/cachet/src/transform/symcrypt_cipher.rs Outdated
Comment thread crates/cachet/Cargo.toml Outdated
Copilot AI review requested due to automatic review settings July 15, 2026 16:17

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comment thread crates/cachet/src/builder/encrypt.rs
Comment thread crates/cachet/src/telemetry/cache.rs
Comment thread crates/cachet/tests/encrypt.rs Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread crates/cachet/src/telemetry/cache.rs
Comment thread crates/cachet/src/builder/encrypt.rs Outdated
Copilot AI review requested due to automatic review settings July 15, 2026 18:51

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread crates/cachet/src/builder/encrypt.rs Outdated
Copilot AI review requested due to automatic review settings July 15, 2026 21:10
@schgoo schgoo enabled auto-merge (squash) July 15, 2026 21:10
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

use std::sync::atomic::{AtomicU32, Ordering};

use bytesbuf::BytesView;
use cachet::{AeadCipher, Cache, CacheEntry, CacheOp, CacheTier, DecodeOutcome, Error, MockCache};
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.

3 participants