crypto

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package crypto wires synapse to per-subject crypto-shredding. It is an optional sibling module: importing it pulls in stdlib's crypto/aes + crypto/cipher; importing only the core synapse module does not.

The model is the standard answer to GDPR Article 17 for an immutable event log: events tagged with a es.Envelope.Subject are stored as ciphertext encrypted with a per-subject Data Encryption Key (DEK) held in a KeyStore. Calling [KeyStore.Shred] destroys the DEK; the ciphertext stays on disk (preserving the audit trail) but is forever unreadable. Replays of shredded events surface a substituted "shredded marker" envelope so iter contracts stay intact; aggregate Load fails loudly via es.ErrShreddedPayload; projections branch via the runner's OnError hook.

Typical wiring:

keys := keystoremem.New()
store := crypto.WrapEventStore(memory.New(), keys)
snaps := crypto.WrapSnapshotStore(snapmem.New(), keys)

Events with Subject == "" pass through unmodified. See ADR-0036 for the design discussion and trust model.

Index

Constants

View Source
const ContentTypeEncrypted es.ContentType = "application/synapse.encrypted+aes-gcm+v1"

ContentTypeEncrypted is the es.ContentType the wrapper substitutes onto an envelope after encrypting its payload. The original ContentType is preserved inside the ciphertext blob and restored on decrypt; an operator inspecting raw storage sees this marker.

The "+v1" suffix is the wire-format version. Future cipher swaps or layout changes bump it.

Variables

View Source
var ErrKeyShredded = errors.New("synapse: crypto: key shredded")

ErrKeyShredded is the sentinel a KeyStore returns when a subject's DEK has been destroyed (or was never created and the caller is trying to read). Get and GetOrCreate both surface it; the wrapper substitutes a shredded-marker envelope rather than terminating iteration when it sees ErrKeyShredded.

Functions

func WrapEventStore

func WrapEventStore(inner es.EventStore, keys KeyStore) es.EventStore

WrapEventStore returns an es.EventStore that encrypts subject- tagged payloads on Append and decrypts on Load/Subscribe. Events with an empty Subject pass through unmodified.

On read, if the subject's DEK has been shredded, the wrapper substitutes a shredded-marker envelope (Payload nil, ContentType = es.ContentTypeShredded) so iter continues; consumers detect this via the substituted ContentType (or, after Repository / projection.Runner decode, via es.ErrShreddedPayload).

The wrapper preserves the inner store's contract for every method: Streams and StreamsBySubject pass through unchanged; es.ErrUnsupported from the inner flows out untouched.

func WrapSnapshotStore

func WrapSnapshotStore(inner es.SnapshotStore, keys KeyStore) es.SnapshotStore

WrapSnapshotStore returns an es.SnapshotStore that encrypts subject-tagged snapshots on Save and decrypts on Latest. Symmetric to WrapEventStore: snapshots with empty Subject pass through; shredded subjects return a marker snapshot whose payload is nil and ContentType is es.ContentTypeShredded. A consumer that attempts to decode the marker through its codec will hit es.ErrShreddedPayload at the es.Repository layer.

A snapshot represents an aggregate's state, which may aggregate data about a single subject. The wrapper handles one subject per snapshot; applications that snapshot multi-subject aggregates need to define their own policy — see ADR-0036.

Types

type KeyStore

type KeyStore interface {
	// GetOrCreate returns the DEK for subject, creating a fresh
	// 32-byte key on first use. Returns ErrKeyShredded if subject was
	// previously [Shred]ded; once shredded, a subject's key cannot be
	// regenerated, matching the semantics of GDPR Article 17.
	GetOrCreate(ctx context.Context, subject string) ([]byte, error)

	// Get returns the DEK for subject. Returns ErrKeyShredded both
	// when the key has been shredded and when no key has ever been
	// created — the two are indistinguishable to a consumer that
	// doesn't hold the key, and conflating them avoids leaking
	// existence information.
	Get(ctx context.Context, subject string) ([]byte, error)

	// Shred destroys the DEK for subject. Idempotent: shredding an
	// unknown or already-shredded subject is a no-op. After Shred,
	// subsequent Get returns ErrKeyShredded and GetOrCreate fails
	// with the same — the subject is tombstoned.
	Shred(ctx context.Context, subject string) error
}

KeyStore is the per-subject DEK lifecycle the crypto wrapper depends on. Implementations live in sibling synapse/keystore/* modules (memory, sqlite, postgres). The interface is small by design — the wrapper does the cipher work; the KeyStore is pure storage of bytes keyed by subject id.

Implementations must be safe for concurrent use. The DEK material returned by Get / GetOrCreate is 32 bytes (AES-256). Callers should treat the returned slice as read-only.

Directories

Path Synopsis
Package keystoretest provides a contract test suite that any implementation of crypto.KeyStore can run to verify the documented behavior.
Package keystoretest provides a contract test suite that any implementation of crypto.KeyStore can run to verify the documented behavior.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL