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