feat(durable): payload AEAD cipher, AAD binding, max-payload fail-closed (#4945)#4967
Merged
Merged
Conversation
Add the journal payload confidentiality/integrity boundary (spec-064 C2, #4945). zeph-durable (crypto-free contract, INV-1): - PayloadCipher seal/open trait + PayloadAad location binding (execution_id/step_id/entry_kind/idem_key) with a deterministic, injective, versioned canonical_bytes encoding - EntryKindTag discriminator; EntryKind::tag/tag_enum delegate to it (single source of truth for the entry_kind column strings) - CipherError (metadata-only, INV-5) plus a fail-closed From<CipherError> for DurableError (Authentication -> ReplayIntegrity, Malformed/UnknownKeyId -> Decode) - ensure_payload_within_limit read-side max_payload guard (INV-11): O(1), fail-closed before any decode, never panics - DurableConfig::encryption_gate enforces INV-8: AEAD may be disabled only for a single-user local backend (DisabledLocalWarn); shared-DB/Restate reject it zeph-core::durable (concrete implementation, keeps zeph-durable crypto-free): - XChaCha20Poly1305Cipher: fresh 192-bit CSPRNG nonce per seal (INV-7), key_id || nonce(24) || ciphertext || tag(16) layout, one-key rotation window, zeroized transient key material; from_vault_bytes validates key length Keyed from the vault ZEPH_DURABLE_KEY; key-rotation policy documented in the new Durable Journal Encryption security reference page. Closes #4945
32be7f7 to
7e69751
Compare
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #4945 (spec-064 C2) — the confidentiality and integrity boundary for journaled payloads in the durable execution layer. Part of epic #4707.
What
zeph-durable— crypto-free contract (INV-1):PayloadCipherseal/open trait +PayloadAadlocation binding (execution_id/step_id/entry_kind/idem_key) with a deterministic, injective, versionedcanonical_bytesencoding fed to the AEAD associated-data channel.EntryKindTagdiscriminator;EntryKind::tag_enum/tagnow delegate to it (single source of truth for theentry_kindcolumn strings).CipherError(metadata-only, INV-5) + a fail-closedFrom<CipherError> for DurableError(Authentication→ReplayIntegrity,Malformed/UnknownKeyId→Decode).ensure_payload_within_limitread-sidemax_payloadguard (INV-11): O(1), fail-closed before any decode, never panics.DurableConfig::encryption_gateenforces INV-8: AEAD may be disabled only for a single-user local backend (DisabledLocalWarn); shared-DB / Restate reject it withDurableError::EncryptionRequired.zeph-core::durable— concrete implementation (lives outsidezeph-durableto keep it crypto-dependency-free, INV-1):XChaCha20Poly1305Cipher: fresh 192-bit CSPRNG nonce per seal (INV-7),key_id || nonce(24) || ciphertext || tag(16)blob layout, a one-key rotation window (current + optional previous), and zeroized transient key material. Constructed from the vaultZEPH_DURABLE_KEY.Acceptance criteria
nonce || ct || taglayout; round-trip test.open()fails withReplayIntegrity(fail-closed); ciphertext tamper fails authentication.PayloadTooLargein O(1), no decode, no panic.op_fingerprint/payload/AAD carry no resolved secret material (API doc note).encrypt_payload = false; single-user SQLite warns.chacha20poly1305adds nocargo denyadvisory (reuses the existing transitive 0.10.1);clippy -p zeph-durableclean.Out of scope (later issues)
Wiring the cipher into actual journal writes (C3/C4),
ZEPH_DURABLE_KEYvault resolution + startup INV-8 gate emission at the binary (C6/#4949), HMAC for control entries (C3).Tests
zeph-durable: 33 unit + 19 doc-tests.zeph-core::durable: 11 unit + 2 doc-tests (incl. the 10^6-nonce uniqueness test, ~11s, under the 30s slow-timeout with noterminate-after).Verification
cargo +nightly fmt --check— cleancargo clippy -p zeph-durable -p zeph-core --all-targets -- -D warnings— cleanRUSTFLAGS="-D warnings" cargo check --workspace --all-targets --features desktop,ide,server,chat,pdf,scheduler --locked— cleanRUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps -p zeph-durable -p zeph-core— clean (the only remaining warning,BuildError::MissingProviders, pre-exists on main)cargo deny check advisories— only the pre-existing RUSTSEC-2025-0134 (rustls-pemfile, tracked in dep: rustls-pemfile 2.2.0 unmaintained (RUSTSEC-2025-0134) — migrate to rustls-pki-types #3772);chacha20poly1305introduces none.Docs
book/src/reference/security/durable-encryption.md) documenting the cipher, theZEPH_DURABLE_KEYvault key, the INV-8 requirement, and the key-rotation policy.CHANGELOG.md[Unreleased]updated.Closes #4945