Documentation
¶
Overview ¶
Package keyring provides envelope encryption and audit-log signing as a single deep module.
Callers exchange plaintext for an Envelope (DekID + Ciphertext + Nonce) and back. The KEK chain, DEK lifecycle, AEAD cipher selection, KMS-rooted master key chain, and HMAC-SHA256 signing all live behind this interface.
Two encryption shapes are supported:
- Fresh-DEK envelope (Encrypt/Decrypt) — a new DEK is created for each call. Used by the secrets and tokenization features.
- Persistent DEK (AllocateDek/EncryptWith/DecryptWith) — one DEK is allocated once and reused across many encrypt/decrypt calls. Used by the transit feature where a named key wraps user payloads repeatedly.
Signing (SignWithKey/VerifyWithKey) uses HKDF-SHA256 to derive a purpose- specific key from the active KEK, keeping raw key material inside the module.
All methods honor an ambient transaction propagated via context; persistence joins the caller's tx when one is present (see ADR-0005).
Index ¶
- Variables
- func Zero(b []byte)
- type Algorithm
- type DekHandle
- type Envelope
- type Fake
- func (f *Fake) ActiveKekID() uuid.UUID
- func (f *Fake) AllocateDek(_ context.Context, _ Algorithm) (DekHandle, error)
- func (f *Fake) Decrypt(_ context.Context, env Envelope) ([]byte, error)
- func (f *Fake) DecryptWith(_ context.Context, handle DekHandle, ciphertext, _ []byte, aad []byte) ([]byte, error)
- func (f *Fake) Encrypt(_ context.Context, plaintext []byte) (Envelope, error)
- func (f *Fake) EncryptWith(_ context.Context, handle DekHandle, plaintext, aad []byte) (ciphertext, nonce []byte, err error)
- func (f *Fake) Rewrap(_ context.Context, dekID uuid.UUID) error
- func (f *Fake) RewrapAll(_ context.Context, _ int) (int, error)
- func (f *Fake) SignWithKey(data []byte) ([]byte, uuid.UUID, error)
- func (f *Fake) VerifyWithKey(_ uuid.UUID, data, sig []byte) error
- type KMSKeeper
- type KMSService
- type KekUseCase
- type KeySigner
- type Keyring
- type MasterKey
- type MasterKeyChain
- type NullSigner
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUnsupportedAlgorithm is returned when an unknown Algorithm value is used. ErrUnsupportedAlgorithm = apperrors.Wrap(apperrors.ErrInvalidInput, "unsupported algorithm") // ErrInvalidKeySize is returned when a key has an unexpected byte length. ErrInvalidKeySize = apperrors.Wrap(apperrors.ErrInvalidInput, "invalid key size") // ErrDecryptionFailed is returned when ciphertext cannot be authenticated or decrypted. ErrDecryptionFailed = apperrors.Wrap(apperrors.ErrInvalidInput, "decryption failed") // ErrMasterKeysNotSet is returned when the MASTER_KEYS environment variable is empty. ErrMasterKeysNotSet = apperrors.Wrap(apperrors.ErrInvalidInput, "MASTER_KEYS not set") // ErrActiveMasterKeyIDNotSet is returned when ACTIVE_MASTER_KEY_ID is not configured. ErrActiveMasterKeyIDNotSet = apperrors.Wrap(apperrors.ErrInvalidInput, "ACTIVE_MASTER_KEY_ID not set") // ErrInvalidMasterKeysFormat is returned when a MASTER_KEYS entry cannot be parsed. ErrInvalidMasterKeysFormat = apperrors.Wrap(apperrors.ErrInvalidInput, "invalid MASTER_KEYS format") // ErrInvalidMasterKeyBase64 is returned when a master key value is not valid base64. ErrInvalidMasterKeyBase64 = apperrors.Wrap(apperrors.ErrInvalidInput, "invalid master key base64") // ErrActiveMasterKeyNotFound is returned when ACTIVE_MASTER_KEY_ID is not present in the chain. ErrActiveMasterKeyNotFound = apperrors.Wrap(apperrors.ErrInvalidInput, "active master key not found") // ErrMasterKeyNotFound is returned when a KEK references a master key absent from the chain. ErrMasterKeyNotFound = apperrors.Wrap(apperrors.ErrNotFound, "master key not found") // ErrDekNotFound is returned when a DEK lookup by ID yields no row. ErrDekNotFound = apperrors.Wrap(apperrors.ErrNotFound, "dek not found") // ErrKekNotFound is returned when no KEK exists for the given ID, or no KEKs at all. ErrKekNotFound = apperrors.Wrap(apperrors.ErrNotFound, "kek not found") // ErrKMSProviderNotSet is returned when KMS_PROVIDER is empty. ErrKMSProviderNotSet = apperrors.Wrap( apperrors.ErrInvalidInput, "KMS_PROVIDER is required but not configured (use 'localsecrets' for local development)", ) // ErrKMSKeyURINotSet is returned when KMS_KEY_URI is empty. ErrKMSKeyURINotSet = apperrors.Wrap( apperrors.ErrInvalidInput, "KMS_KEY_URI is required but not configured", ) // ErrKMSDecryptionFailed is returned when the KMS cannot decrypt a master key. ErrKMSDecryptionFailed = apperrors.Wrap(apperrors.ErrInvalidInput, "KMS decryption failed") // ErrKMSOpenKeeperFailed is returned when the KMS keeper cannot be opened. ErrKMSOpenKeeperFailed = apperrors.Wrap(apperrors.ErrInvalidInput, "failed to open KMS keeper") // ErrSignatureInvalid is returned by VerifyWithKey when the HMAC does not match. ErrSignatureInvalid = errors.New("keyring: signature invalid") )
Sentinel errors returned by the keyring package. Callers should use errors.Is to test them.
Functions ¶
Types ¶
type Algorithm ¶
type Algorithm string
Algorithm is the AEAD algorithm used to wrap a DEK and to encrypt under it.
type DekHandle ¶
DekHandle is an opaque reference to a persistent DEK allocated via AllocateDek. Callers store only the DekID and reload the handle on demand.
type Envelope ¶
Envelope is the result of Keyring.Encrypt and the input to Keyring.Decrypt. Callers persist the three fields exactly as they would today; nothing about the KEK or DEK material is exposed.
type Fake ¶
type Fake struct {
// FailEncrypt, FailDecrypt, FailAllocate, FailRewrap, FailSign, when
// non-nil, make the matching method return the stored error. Useful for
// failure-path tests in callers.
FailEncrypt error
FailDecrypt error
FailAllocate error
FailRewrap error
FailSign error
// contains filtered or unexported fields
}
Fake is an in-memory Keyring suitable for feature unit tests.
It does not perform real cryptography: ciphertext is a deterministic transformation of the plaintext keyed by the DekID. The point is to give features a real second adapter so that Keyring becomes a real seam, and to let feature tests assert behaviour (a value can be encrypted, persisted, and decrypted back) without touching the database or KMS.
Concurrency-safe.
func (*Fake) ActiveKekID ¶
ActiveKekID returns the zero UUID for the Fake; the value is only used by the rotation worker to verify the operator-provided KEK ID matches.
func (*Fake) AllocateDek ¶
AllocateDek returns a fresh handle. Algorithm is ignored.
func (*Fake) DecryptWith ¶
func (f *Fake) DecryptWith( _ context.Context, handle DekHandle, ciphertext, _ []byte, aad []byte, ) ([]byte, error)
DecryptWith reverses EncryptWith.
func (*Fake) Encrypt ¶
Encrypt returns an Envelope whose Ciphertext is a reversible XOR of the plaintext with a DekID-derived stream. The DekID is allocated and tracked.
func (*Fake) EncryptWith ¶
func (f *Fake) EncryptWith( _ context.Context, handle DekHandle, plaintext, aad []byte, ) (ciphertext, nonce []byte, err error)
EncryptWith XORs plaintext with a stream derived from the handle's DekID and the optional aad.
func (*Fake) Rewrap ¶
Rewrap is a no-op for the Fake (there is no KEK chain) but honors FailRewrap and validates the DekID is known.
func (*Fake) RewrapAll ¶
RewrapAll returns 0 by default for the Fake — there is no notion of a stale KEK in-memory. Tests that exercise the rotation worker should stub this behaviour by counting tracked DEKs.
func (*Fake) SignWithKey ¶
SignWithKey computes HMAC-SHA256 of data using a fixed zero key and returns uuid.Nil as the KEK ID. Intended for tests; does not use real key material.
type KMSKeeper ¶
type KMSKeeper interface {
Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error)
Close() error
}
KMSKeeper wraps a gocloud.dev/secrets.Keeper to expose only the operations the keyring needs: decryption and resource cleanup.
type KMSService ¶
type KMSService interface {
// OpenKeeper opens the KMS keeper at keyURI and returns it.
// The caller must call Close on the returned keeper when done.
OpenKeeper(ctx context.Context, keyURI string) (KMSKeeper, error)
}
KMSService opens cloud KMS keepers by URI. Implementations register themselves via gocloud.dev URL openers (see blank imports below).
func NewKMSService ¶
func NewKMSService() KMSService
NewKMSService returns a KMSService backed by the gocloud.dev URL opener registry.
type KekUseCase ¶
type KekUseCase interface {
// Create generates a new KEK wrapped under the active master key and persists it.
Create(ctx context.Context, masterKeyChain *MasterKeyChain, alg Algorithm) error
// Rotate creates a new KEK version wrapped under the active master key,
// incrementing the version counter relative to the current highest KEK.
Rotate(ctx context.Context, masterKeyChain *MasterKeyChain, alg Algorithm) error
}
KekUseCase manages KEK lifecycle operations. It is the only caller that writes KEK rows; all read access goes through Bootstrap.
func NewKekUseCase ¶
func NewKekUseCase(txManager database.TxManager, db *sql.DB) KekUseCase
NewKekUseCase returns a KekUseCase backed by the given transaction manager and database.
type KeySigner ¶
type KeySigner interface {
// SignWithKey signs data using a key derived from the active KEK via HKDF-SHA256.
// Returns the 32-byte HMAC-SHA256 signature and the ID of the KEK used.
SignWithKey(data []byte) (sig []byte, kekID uuid.UUID, err error)
// VerifyWithKey verifies data against sig using the KEK identified by kekID.
// Returns ErrSignatureInvalid if the signature does not match.
// Returns ErrKekNotFound if the KEK is not present in the chain.
VerifyWithKey(kekID uuid.UUID, data, sig []byte) error
}
KeySigner signs and verifies arbitrary byte payloads using KEK-derived HMAC-SHA256 keys. Key material never leaves the keyring.
type Keyring ¶
type Keyring interface {
KeySigner
// Encrypt creates a fresh DEK, persists it, and uses it to encrypt
// plaintext exactly once. The returned Envelope contains everything a
// future Decrypt call needs.
Encrypt(ctx context.Context, plaintext []byte) (Envelope, error)
// Decrypt reverses Encrypt for an Envelope produced by this Keyring.
Decrypt(ctx context.Context, env Envelope) ([]byte, error)
// AllocateDek creates and persists a new DEK and returns a handle to it.
// The DEK is wrapped under the active KEK with the given algorithm.
AllocateDek(ctx context.Context, alg Algorithm) (DekHandle, error)
// EncryptWith encrypts plaintext under the DEK identified by handle and
// authenticates the optional aad. Reuses the DEK across many calls.
EncryptWith(
ctx context.Context,
handle DekHandle,
plaintext, aad []byte,
) (ciphertext, nonce []byte, err error)
// DecryptWith reverses EncryptWith for ciphertext produced under the
// same DekHandle with the same aad.
DecryptWith(
ctx context.Context,
handle DekHandle,
ciphertext, nonce, aad []byte,
) ([]byte, error)
// Rewrap re-encrypts an existing DEK under the active KEK without
// changing the underlying key material. Used by KEK rotation.
Rewrap(ctx context.Context, dekID uuid.UUID) error
// RewrapAll re-encrypts, in batches, every DEK not already under the
// active KEK. Returns the total number of DEKs rewrapped across all
// batches. Used by the KEK rotation worker.
RewrapAll(ctx context.Context, batchSize int) (int, error)
// ActiveKekID returns the ID of the KEK currently used for new
// encryption. Exposed so the rotation worker can confirm it is
// targeting the same KEK the application is using.
ActiveKekID() uuid.UUID
}
Keyring is the single seam features use for encryption and signing. It embeds KeySigner so any Keyring implementation also satisfies signing without a separate interface or runtime type assertion.
Implementations must be safe for concurrent use.
func Bootstrap ¶
func Bootstrap( ctx context.Context, masterKeyChain *MasterKeyChain, db *sql.DB, alg Algorithm, ) (Keyring, error)
Bootstrap loads all KEKs from the database, decrypts them using masterKeyChain, and returns a ready-to-use Keyring. alg is the default Algorithm used for new DEKs. Returns ErrKekNotFound if no KEKs exist or ErrMasterKeyNotFound if a KEK references a master key that is absent from the chain.
type MasterKey ¶
MasterKey is a plaintext root key used to wrap KEKs. Key material must be zeroed via Zero when no longer needed.
type MasterKeyChain ¶
type MasterKeyChain struct {
// contains filtered or unexported fields
}
MasterKeyChain is an in-memory store of one or more MasterKeys, one of which is designated active. The active key is used for new KEK operations; all keys are available for decryption of existing KEKs. Concurrency-safe.
func LoadMasterKeyChain ¶
func LoadMasterKeyChain( ctx context.Context, cfg *config.Config, kmsService KMSService, logger *slog.Logger, ) (*MasterKeyChain, error)
LoadMasterKeyChain reads MASTER_KEYS and ACTIVE_MASTER_KEY_ID from the environment, decrypts each key via the KMS, and returns a populated chain. KMS_PROVIDER and KMS_KEY_URI must be set in cfg.
func NewMasterKeyChain ¶
func NewMasterKeyChain(activeID string) *MasterKeyChain
NewMasterKeyChain returns an empty MasterKeyChain whose active key ID is activeID. Keys must be added via direct store calls before passing the chain to Bootstrap.
func (*MasterKeyChain) ActiveMasterKeyID ¶
func (m *MasterKeyChain) ActiveMasterKeyID() string
ActiveMasterKeyID returns the ID of the master key used for new operations.
func (*MasterKeyChain) Close ¶
func (m *MasterKeyChain) Close()
Close zeroes all key material in the chain and removes every entry. After Close the chain must not be used.
type NullSigner ¶
type NullSigner struct{}
NullSigner is a no-op KeySigner for tests that do not exercise signing behaviour. SignWithKey returns a 32-byte zero signature and uuid.Nil; VerifyWithKey always returns nil.
func (NullSigner) SignWithKey ¶
func (NullSigner) VerifyWithKey ¶
func (NullSigner) VerifyWithKey(_ uuid.UUID, _, _ []byte) error