Documentation
¶
Overview ¶
Package crypto implements envelope encryption.
Data is encrypted with a Data Encryption Key (DEK) using AES-256-GCM. Each DEK is in turn encrypted ("wrapped") by a Key Encryption Key (KEK), typically customer-managed and backed by a cloud KMS. The wrapped DEK and the ID of the KEK that wrapped it are carried together as DEKMaterial, allowing the DEK to be recovered and the data decrypted later.
A KEKRegistry manages the set of available KEKs, selecting the appropriate key by namespace for encryption and by key ID for decryption. KEKs themselves are opened from key URIs by a KeyFactory, which handles the cloud KMS schemes directly and can be extended with schemes of the caller's own.
A Vault reports what it does to an Observer as one of a closed set of Event types: a CacheEvent for a DEK cache hit or miss, one EnvelopeEvent per Seal and Open carrying both the end-to-end and the AES-only duration and distinguishing a failure inside AES from a failure elsewhere in the operation, and a RotationEvent whenever a namespace's DEK is replaced. Observers are always called outside the Vault's internal lock, so an implementation may call back into the Vault, but it must not block, and it must not re-enter the operation that produced the event: calling Seal from Observe, for example, recurses without bound and a stack overflow cannot be recovered from.
Index ¶
- Variables
- func DefaultSchemes() []string
- type CacheEvent
- type CloudKey
- type DEK
- type DEKMaterial
- type EnvelopeEvent
- type Event
- type KEK
- type KEKRegistry
- type KEKRegistryOption
- type KeyConfig
- type KeyFactory
- type KeyFactoryFunc
- type KeyFactoryOption
- type Message
- type NamespacedVault
- type Observer
- type Operation
- type RotationEvent
- type RotationReason
- type Vault
- type VaultOption
Constants ¶
This section is empty.
Variables ¶
var ErrMalformedCipherText = errors.New("invalid ciphertext, not encrypted with a DEK")
ErrMalformedCipherText indicates that the ciphertext was not created by a call to Encrypt, or that it was otherwise tampered with.
Functions ¶
func DefaultSchemes ¶ added in v0.3.0
func DefaultSchemes() []string
DefaultSchemes lists the URI schemes a KeyFactory serves with NewCloudKey before any option is applied, lowercased as KeyFactory.Create matches them. It is useful for validating a key URI ahead of opening it, so a typo in a scheme can be reported alongside the rest of a config rather than at the point the key is first needed.
Each call returns a fresh slice; the caller may sort or filter it freely without disturbing the factory.
Types ¶
type CacheEvent ¶
type CacheEvent struct {
// Hit is true when the wrapped DEK was already cached.
Hit bool
// Size is the number of entries in the DEK cache after the access.
Size int
}
CacheEvent describes a DEK cache access.
type CloudKey ¶ added in v0.3.0
CloudKey is a KEK backed by a cloud KMS key. The embedded secrets.Keeper supplies Decrypt and Close; CloudKey adds the ID and the namespace-aware Encrypt that KEK requires.
A Keeper addresses a single fixed key, so one CloudKey wraps DEKs for every namespace it is handed. Open one CloudKey per KMS key and let a KEKRegistry decide which namespaces map onto which key.
type DEK ¶
type DEK struct {
// contains filtered or unexported fields
}
DEK defines a Data Encryption Key used to encrypt/decrypt payloads.
func (*DEK) Decrypt ¶
Decrypt decrypts the ciphertext ct using AES-256-GCM. The ciphertext must be prefixed with the nonce, as produced by DEK.Encrypt.
type DEKMaterial ¶
type DEKMaterial struct {
Version byte
KEKID string // The ID/URI of the KEK the encrypted the DEK.
EncryptedDEK string // The base64-encoded encrypted DEK.
}
DEKMaterial defines the material needed in order to decrypt a payload.
type EnvelopeEvent ¶ added in v0.3.0
type EnvelopeEvent struct {
// Op is OpEncrypt for Seal, OpDecrypt for Open.
Op Operation
// Namespace is set on OpEncrypt. It is always empty on OpDecrypt: Open
// selects its KEK by ID from the message material and never learns a
// namespace.
Namespace string
// Err is the error the operation returned, or nil.
Err error
// CryptoAttempted reports whether the AES-256-GCM step ran. It is the
// only reliable signal for that, and Crypto is not: a small payload can
// complete inside the clock's resolution, so a step that did run can
// still report a zero duration. Crypto and CryptoErr are meaningful only
// when this is true.
CryptoAttempted bool
// CryptoErr is the error from the AES-256-GCM step alone, or nil when
// that step succeeded. Err, by contrast, is the whole operation's error,
// which may come from a KEK wrap or a cache-miss unwrap rather than from
// AES, so the two differ whenever a Seal encrypts successfully and then
// fails to wrap its DEK.
CryptoErr error
// Total covers the whole operation, including any KEK wrap on the first
// Seal after a rotation and any unwrap on a cache miss.
Total time.Duration
// Crypto covers only the AES-256-GCM step, and may be zero for a step
// that ran. Total minus Crypto is the cost of everything around AES, and
// is meaningful only when CryptoAttempted is true.
Crypto time.Duration
}
EnvelopeEvent describes one completed envelope operation, successful or not. Exactly one is reported per Vault.Seal and per Vault.Open, on every path including early failures.
type Event ¶ added in v0.3.0
type Event interface {
// contains filtered or unexported methods
}
Event is one thing a Vault reports to an Observer. The set is closed: only this package can define one, so an Observer's type switch can be written against a known set of cases.
type KEK ¶
type KEK interface {
io.Closer
// ID returns a unique ID for this KEK, e.g. a KMS ARN.
ID() string
// Encrypt encrypts a DEK for the given namespace, returning the ciphertext.
// The namespace lets a KEK select a per-namespace key; implementations backed
// by a single fixed key ignore it.
Encrypt(ctx context.Context, ns string, dek []byte) ([]byte, error)
// Decrypt decrypts a DEK previously produced by Encrypt. No namespace is
// required: the KEK is selected by ID from the DEK material.
Decrypt(ctx context.Context, dek []byte) ([]byte, error)
}
KEK defines an interface for a Key Encryption Keys. These keys are used to encrypt/decrypt DEKs and are customer-managed (e.g. via AWS/GCP KMS).
func NewCloudKey ¶ added in v0.3.0
NewCloudKey opens the cloud KMS key addressed by uri, which must use one of the schemes listed in NewKeyFactory. Close the returned key when it is no longer needed; a KEKRegistry does that for the keys it holds.
The "testing://" scheme is rewritten to gocloud's "base64key://" local keeper so tests and local runs need no cloud KMS at all. Everything after that scheme is the base64-encoded 32-byte key; pass a bare "testing://" to get a random one. Key material is kept out of the errors this function returns, but it still reaches the key's ID, and therefore every DEK the key wraps, which is one more reason to keep the scheme away from production.
For every other scheme the ID is just uri, so it is stable across processes and identifies the key again on the decrypt path. Schemes are matched without regard to case, so the ID of a testing key is the same however its scheme was spelled.
type KEKRegistry ¶
type KEKRegistry struct {
// contains filtered or unexported fields
}
KEKRegistry holds the set of KEKs available for encrypting and decrypting DEKs. It is keyed by namespace (for encryption) and by key ID (for decryption). Close must be called when the registry is no longer needed to release KEK resources.
func NewKEKRegistry ¶
func NewKEKRegistry(opts ...KEKRegistryOption) (*KEKRegistry, error)
NewKEKRegistry constructs a KEKRegistry, applying opts in order. A default key is required (see WithDefaultKey); construction fails if one is not provided. The key-ID index used by Decrypt is built after all options are applied.
func (*KEKRegistry) Close ¶
func (r *KEKRegistry) Close() error
Close closes all registered KEKs and releases their resources. Subsequent calls return the same error as the first call.
func (*KEKRegistry) Decrypt ¶
func (r *KEKRegistry) Decrypt(ctx context.Context, m *DEKMaterial) (*DEK, error)
Decrypt decrypts the DEK described by m using the KEK identified by m.KEKID.
func (*KEKRegistry) Encrypt ¶
func (r *KEKRegistry) Encrypt(ctx context.Context, ns string, dek *DEK) (*DEKMaterial, error)
Encrypt encrypts the given DEK using the KEK registered for the specified namespace. It returns DEKMaterial containing the KEK ID and the base64-encoded ciphertext.
type KEKRegistryOption ¶
type KEKRegistryOption interface {
// contains filtered or unexported methods
}
KEKRegistryOption configures a KEKRegistry during construction.
func WithDecryptOnlyKey ¶
func WithDecryptOnlyKey(k KEK) KEKRegistryOption
WithDecryptOnlyKey registers k for decryption only. It is added to the key-ID index so that DEKs encrypted with k can still be opened, but k is never selected for new DEK encryption. This is typically used for keys that have been rotated out of active use.
func WithDefaultKey ¶
func WithDefaultKey(k KEK) KEKRegistryOption
WithDefaultKey sets the fallback KEK used when no namespace-specific key is registered. A default key is required: NewKEKRegistry returns an error if one is not provided.
func WithKeyForNamespace ¶
func WithKeyForNamespace(ns string, k KEK) KEKRegistryOption
WithKeyForNamespace registers k for ns, used when encrypting or decrypting DEKs for that namespace.
type KeyConfig ¶
type KeyConfig struct {
// Duration is how long a DEK is valid before it must be rotated.
Duration time.Duration
// RenewBefore causes a DEK to be treated as expired this long before
// Duration elapses, so it can be rotated ahead of its actual expiry.
RenewBefore time.Duration
}
KeyConfig controls the lifetime of a namespace's DEK.
type KeyFactory ¶ added in v0.3.0
type KeyFactory struct {
// contains filtered or unexported fields
}
KeyFactory opens [KEK]s from key URIs, choosing an opener by URI scheme. It handles the cloud KMS schemes out of the box (see NewKeyFactory) and takes additional or replacement schemes through WithKeyFactoryFuncForScheme, so a caller can serve keys from its own key store without the code consuming those KEKs knowing where they come from.
Schemes are registered during construction only, so a KeyFactory never changes afterwards and one may be shared by any number of goroutines opening keys at once.
func NewKeyFactory ¶ added in v0.3.0
func NewKeyFactory(opts ...KeyFactoryOption) *KeyFactory
NewKeyFactory returns a KeyFactory that opens cloud KMS keys, then applies opts in order. The schemes registered up front are DefaultSchemes, all served by NewCloudKey:
awskms:// AWS KMS azurekeyvault:// Azure Key Vault gcpkms:// Google Cloud KMS testing:// a local in-process key, for tests and local runs only
The driver behind each scheme is linked in by importing this package, so no further imports are needed to use them.
opts are applied after those defaults, which means WithKeyFactoryFuncForScheme can replace any of them as well as add schemes of its own.
func (*KeyFactory) Create ¶ added in v0.3.0
Create opens the key addressed by uri with the KeyFactoryFunc registered for its scheme. It fails if uri does not parse or if no opener is registered for the scheme. Beyond that the opener decides: an unreachable or misconfigured key surfaces as whatever error that opener returns.
type KeyFactoryFunc ¶ added in v0.3.0
KeyFactoryFunc opens the key addressed by a URI. It is called only for URIs whose scheme it was registered under, and receives the URI verbatim as it was passed to KeyFactory.Create, scheme included.
type KeyFactoryOption ¶ added in v0.3.0
type KeyFactoryOption interface {
// contains filtered or unexported methods
}
KeyFactoryOption configures a KeyFactory during construction.
func WithKeyFactoryFuncForScheme ¶ added in v0.3.0
func WithKeyFactoryFuncForScheme(scheme string, fn KeyFactoryFunc) KeyFactoryOption
WithKeyFactoryFuncForScheme registers fn as the opener for scheme, replacing whatever was registered for it before, including the built-in cloud KMS schemes. URI schemes are case-insensitive, so scheme is lowercased on the way in and matches however a caller spells it in a URI.
type Message ¶
type Message struct {
Ciphertext []byte
KeyMaterial *DEKMaterial
}
Message is the result of sealing plaintext: the AES-256-GCM ciphertext together with the wrapped DEK (DEKMaterial) required to open it.
type NamespacedVault ¶
type NamespacedVault struct {
// contains filtered or unexported fields
}
NamespacedVault is a Vault bound to a single namespace so callers can Seal and Open without passing the namespace on every call. Obtain one via Vault.ForNamespace.
func (*NamespacedVault) Open ¶
Open decrypts msg within the bound namespace. See Vault.Open.
func (*NamespacedVault) Seal ¶
Seal encrypts data within the bound namespace. See Vault.Seal.
type Observer ¶
type Observer interface {
Observe(Event)
}
Observer receives notifications about Vault-internal events for telemetry. It is called with a CacheEvent from Open only, an EnvelopeEvent from both Seal and Open, and a RotationEvent from both Seal and Refresh. Implementations must be safe for concurrent use, must not block, and must not re-enter the operation that produced the event. A nil Observer is never used; the Vault substitutes a no-op (see WithObserver).
type Operation ¶ added in v0.3.0
type Operation uint8
Operation names the envelope operation an EnvelopeEvent describes.
type RotationEvent ¶ added in v0.3.0
type RotationEvent struct {
Namespace string
Reason RotationReason
}
RotationEvent reports that a namespace's DEK was replaced.
type RotationReason ¶ added in v0.3.0
type RotationReason uint8
RotationReason says why a namespace's DEK was replaced.
const ( // RotationScheduled is a rotation performed by [Vault.Refresh], off the // request path. RotationScheduled RotationReason = iota // RotationOnDemand is a rotation [Vault.Seal] performed because it found the // DEK already expired, which means Refresh has fallen behind. RotationOnDemand // RotationInitial is the first DEK for a namespace that had no explicit // [WithKeyConfig], created on its first Seal. RotationInitial )
func (RotationReason) String ¶ added in v0.3.0
func (r RotationReason) String() string
String returns the metric label value for r. An unrecognized value returns "unknown" so a label is never blank if a value is added without updating this method.
type Vault ¶
type Vault struct {
// contains filtered or unexported fields
}
Vault provides envelope encryption scoped by namespace. It keeps a sliding Data Encryption Key (DEK) per namespace, wrapping each DEK with the KEK selected for that namespace by a KEKRegistry. DEKs are rotated on a sliding schedule (see KeyConfig) and decrypted DEKs are cached to avoid repeated KMS calls on Open. A Vault is safe for concurrent use.
func NewVault ¶
func NewVault(r *KEKRegistry, opts ...VaultOption) (*Vault, error)
NewVault constructs a Vault backed by r, applying opts in order. A DEK is pre-generated for every namespace registered via WithKeyConfig. NewVault returns an error if any option is invalid (for example, a duplicate namespace config) or if key/cache setup fails.
func (*Vault) ForNamespace ¶
func (v *Vault) ForNamespace(ns string) *NamespacedVault
ForNamespace returns a NamespacedVault that seals and opens within ns.
func (*Vault) Open ¶
Open decrypts msg, which must have been produced by Vault.Seal (or NamespacedVault.Seal). The wrapped DEK is unwrapped via the KEKRegistry using the KEK identified by the material carried in msg, served from the decrypted-DEK cache when it is enabled.
Exactly one EnvelopeEvent is reported to the Observer, on every path including failures. It carries no namespace: the KEK is selected by ID from the material, so Open never learns one.
func (*Vault) Refresh ¶
Refresh rotates every namespace DEK that has reached its renewal threshold. It is meant to be called periodically. Seal also rotates an expired DEK on demand, so Refresh is an optimization that keeps rotation off the request path rather than a correctness requirement.
One RotationEvent with RotationScheduled is reported per key rotated.
func (*Vault) Seal ¶
Seal encrypts data for ns, returning the ciphertext together with the wrapped DEK required to Open it. The active DEK for ns is created or rotated on demand. Concurrent first-time seals holding the same DEK are coalesced into a single KEK (KMS) call.
Exactly one EnvelopeEvent is reported to the Observer, on every path including failures.
type VaultOption ¶
type VaultOption func(*vaultOptions)
VaultOption configures a Vault during construction.
func WithCacheSize ¶
func WithCacheSize(n int) VaultOption
WithCacheSize sets the maximum number of decrypted DEKs retained in the Open cache. A value of zero or less disables the cache, so every Open unwraps its DEK via the KEKRegistry.
func WithDefaultKeyConfig ¶
func WithDefaultKeyConfig(cfg KeyConfig) VaultOption
WithDefaultKeyConfig sets the KeyConfig used for namespaces that have no explicit WithKeyConfig. Without a default, sealing an unconfigured namespace fails; with one, a DEK is created for such namespaces on first use.
func WithKeyConfig ¶
func WithKeyConfig(ns string, cfg KeyConfig) VaultOption
WithKeyConfig sets the KeyConfig for a specific namespace. Registering the same namespace more than once is an error surfaced by NewVault.
func WithNowFunc ¶
func WithNowFunc(fn func() time.Time) VaultOption
WithNowFunc overrides the clock used to evaluate DEK expiry. It is primarily useful in tests. A nil function is rejected by NewVault.
func WithObserver ¶
func WithObserver(o Observer) VaultOption
WithObserver sets the Observer notified of Vault-internal events: a CacheEvent from Open, an EnvelopeEvent from both Seal and Open, and a RotationEvent from Seal and Refresh. A nil Observer is replaced with a no-op, so no Vault call site needs to nil-check. Without this option no events are emitted.