Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ go get github.com/hyperscale-stack/enigma
- `recipient`: recipient abstractions and capability model.
- `recipient/localmlkem`: fully implemented local PQ recipient.
- `recipient/{gcpkms,awskms,azurekv,scwkm}`: explicit cloud stubs for v1.
- `keymgmt`: key lifecycle interfaces and domain types.
- `keymgmt/localmlkem`: local ML-KEM key manager with filesystem-backed metadata persistence.
- `resolver`: recipient resolver interfaces and backend registry.
- `resolver/localmlkem`: resolves local key references into runtime recipients.
- `mem`: best-effort memory hygiene helpers.

## Quick Start
Expand Down Expand Up @@ -79,6 +83,25 @@ plaintext, _ := field.DecryptValue(context.Background(), ciphertext,
)
```

### Key lifecycle and recipient resolution

```go
km, _ := keymgmtlocalmlkem.NewManager("/var/lib/enigma-keys")
desc, _ := km.CreateKey(context.Background(), keymgmt.CreateKeyRequest{
Name: "tenant-a-primary",
Purpose: keymgmt.PurposeRecipientDecrypt,
Algorithm: keymgmt.AlgorithmMLKEM768,
ProtectionLevel: keymgmt.ProtectionSoftware,
})

res, _ := resolverlocalmlkem.New("/var/lib/enigma-keys")
runtimeRecipient, _ := res.ResolveRecipient(context.Background(), desc.Reference)

_ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc",
document.WithRecipient(runtimeRecipient),
)
```

## Security Properties (Implemented)

- Confidentiality and authenticity of encrypted content when recipients and primitives are used correctly.
Expand All @@ -91,11 +114,19 @@ plaintext, _ := field.DecryptValue(context.Background(), ciphertext,

- Go memory is not fully controllable; key wiping is best-effort only.
- v1 cloud providers are stubs and return `ErrNotImplemented` for wrapping/unwrapping.
- Key lifecycle mapping (for example one key per tenant or organization) is application-owned.
- Recipient metadata (type/key references/capability labels) is inspectable by design and not encrypted.
- No signatures in v1 (footer/signature area is an extension point only).
- No deterministic/searchable field encryption in v1.
- No identity platform, policy engine, or remote API service.

## Lifecycle versus Runtime

- `KeyManager` provisions, inspects, rotates, and deletes keys.
- `Recipient` only wraps and unwraps DEKs at runtime.
- `RecipientResolver` resolves a stored `KeyReference` back to a runtime `Recipient`.
- Key rotation and document rewrap are distinct operations. Rotation creates successor keys; rewrap updates recipient entries in existing encrypted containers.

## Capability Model

- `local-pq`: local ML-KEM recipient.
Expand All @@ -112,6 +143,7 @@ go test ./...

See:
- [`docs/architecture.md`](docs/architecture.md)
- [`docs/key-management.md`](docs/key-management.md)
- [`docs/container-format.md`](docs/container-format.md)
- [`docs/threat-model.md`](docs/threat-model.md)
- [`SECURITY.md`](SECURITY.md)
Expand Down
32 changes: 27 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@

## Overview

Enigma is structured into four layers:
Enigma is structured into five layers:

1. Recipient / key wrapping layer
1. Key lifecycle and resolution layer
- `keymgmt`: key lifecycle interfaces and domain types.
- `keymgmt/localmlkem`: local ML-KEM key manager implementation.
- `resolver`: recipient resolution interfaces and registry.
- `resolver/localmlkem`: resolves stored local key references into runtime recipients.
- Separates key provisioning from runtime wrapping semantics.

2. Recipient / key wrapping layer
- Defines recipient interface.
- Wraps and unwraps a random DEK.
- Supports local PQ recipient (ML-KEM) and cloud-provider stubs with explicit capabilities.

2. Symmetric encryption layer
3. Symmetric encryption layer
- Uses one DEK per encrypted object.
- Derives separated subkeys with HKDF-SHA256.
- Encrypts content using AEAD suites:
- default: XChaCha20-Poly1305
- optional: AES-256-GCM
- Uses chunked authenticated framing for document/blob workloads.

3. Container format layer
4. Container format layer
- Implements strict binary envelope parser/serializer.
- Header split:
- immutable section (content-bound)
- recipient section (rewrap-mutable)
- Header authentication tag is derived from DEK material.

4. High-level API layer
5. High-level API layer
- `document` package:
- `EncryptFile`, `DecryptFile`
- `NewEncryptWriter`, `NewDecryptReader`
Expand All @@ -42,6 +49,21 @@ Enigma is structured into four layers:
- nonce salt
- reserved material

## Key Lifecycle Model

- `KeyManager` provisions and manages key lifecycle.
- `Recipient` only performs runtime `WrapKey`/`UnwrapKey`.
- `RecipientResolver` converts stored `KeyReference` records into runtime `recipient.Recipient` instances.
- `KeyReference` is stable, serializable metadata that never includes private key material.
- Application key ownership mapping (for example per tenant or per organization) is handled by the application, not by Enigma.

### Rotation versus Rewrap

- Rotation creates or selects successor keys at lifecycle level.
- Rewrap updates recipient entries in encrypted documents.
- Rotation does not automatically re-encrypt existing payloads.
- Rewrap does not create or rotate backend keys.

## Rewrap Model

Rewrap attempts to unwrap DEK with supplied recipient(s), then rewrites only:
Expand Down
86 changes: 86 additions & 0 deletions docs/key-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Key Management

## Purpose

Enigma separates key lifecycle management from runtime wrapping and unwrapping.

This split avoids mixing concerns:
- lifecycle APIs (`KeyManager`) provision and manage keys
- runtime recipient APIs (`recipient.Recipient`) wrap and unwrap DEKs
- resolver APIs (`RecipientResolver`) rebuild runtime recipients from stored key references

## Core Concepts

### KeyManager

`KeyManager` is responsible for key lifecycle operations:
- create key
- inspect key
- rotate key (native or successor workflow)
- delete key
- report capability set

### KeyReference

`KeyReference` is a stable, serializable key pointer suitable for storage in an application database.

A valid key reference must not include private key material.

### RecipientResolver

`RecipientResolver` turns a `KeyReference` back into a runtime `recipient.Recipient`.

Applications can persist `KeyReference` records and resolve recipients on demand for encryption/decryption operations.

## Local ML-KEM Backend

The local backend is implemented in:
- `keymgmt/localmlkem`
- `resolver/localmlkem`

### Storage model

- records are stored under `<root>/localmlkem/v1/<key-id>/<version>.json`
- files are written with mode `0600`
- directories are created with mode `0700`
- references include backend, id, version, and URI
- private key material is persisted in backend storage, not in `KeyReference`

### Filesystem trust assumptions

- local backend security depends on host filesystem access controls
- protect the configured root path with strict OS permissions
- if host compromise is in scope, local software key storage may be insufficient

## Rotation and Rewrap

Rotation and rewrap are intentionally distinct operations:

- `KeyManager.RotateKey` creates a successor key descriptor
- `document.Rewrap` updates recipient entries in encrypted containers

Rotation does not automatically rewrite historical ciphertext.
Applications should perform rewrap workflows explicitly when policy requires migration to successor keys.

## Application Ownership Mapping

Enigma does not map keys to tenants, organizations, or environments.

That mapping belongs to the application.

Typical pattern:
1. application creates a key with `KeyManager`
2. application stores `KeyReference` in its own data model
3. application resolves a runtime recipient with `RecipientResolver`
4. application encrypts/decrypts via `document` or `field` packages

## Capability Reporting

`CapabilitySet` provides explicit backend capabilities, including:
- creation/deletion support
- native rotation support versus successor workflow
- recipient resolution support
- PQ-native support versus classical wrapping support
- rewrap workflow compatibility

Capability reporting is descriptive and should be checked by the application before selecting a workflow.
9 changes: 6 additions & 3 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Layered architecture (`recipient`, `container`, `document`, `field`)
- Local ML-KEM recipient implementation
- Key lifecycle abstraction (`keymgmt`) and recipient resolution abstraction (`resolver`)
- Local ML-KEM key manager and resolver implementation
- Chunked document encryption and stream APIs
- Rewrap path without content re-encryption
- Field encryption compact format
Expand All @@ -12,8 +14,9 @@

## Planned Next

1. Cloud backend implementations
- Replace provider stubs with production integrations.
1. Provider lifecycle backends
- Add `KeyManager` and `RecipientResolver` implementations for cloud backends.
- Keep capability reporting explicit for native rotation versus successor workflows.
- Add live integration test matrix behind opt-in configuration.

2. Stronger policy controls
Expand All @@ -25,7 +28,7 @@

4. Advanced rewrap tooling
- CLI improvements for inspection/rewrap automation.
- Batch rewrap workflows.
- Batch workflows that combine successor-key rotation and explicit rewrap execution.

5. Hardening and observability
- Additional fuzzing corpora.
Expand Down
32 changes: 20 additions & 12 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ import (
)

var (
ErrInvalidArgument = errors.New("enigma: invalid argument")
ErrInvalidContainer = errors.New("enigma: invalid container")
ErrUnsupportedVersion = errors.New("enigma: unsupported version")
ErrUnsupportedAlgorithm = errors.New("enigma: unsupported algorithm")
ErrNoRecipients = errors.New("enigma: no recipients configured")
ErrWrapFailed = errors.New("enigma: key wrap failed")
ErrUnwrapFailed = errors.New("enigma: key unwrap failed")
ErrDecryptFailed = errors.New("enigma: decrypt failed")
ErrIntegrity = errors.New("enigma: integrity check failed")
ErrNotImplemented = errors.New("enigma: not implemented")
ErrCapabilityMismatch = errors.New("enigma: capability mismatch")
ErrRecipientNotFound = errors.New("enigma: recipient not found")
ErrInvalidArgument = errors.New("enigma: invalid argument")
ErrInvalidContainer = errors.New("enigma: invalid container")
ErrUnsupportedVersion = errors.New("enigma: unsupported version")
ErrUnsupportedAlgorithm = errors.New("enigma: unsupported algorithm")
ErrNoRecipients = errors.New("enigma: no recipients configured")
ErrWrapFailed = errors.New("enigma: key wrap failed")
ErrUnwrapFailed = errors.New("enigma: key unwrap failed")
ErrDecryptFailed = errors.New("enigma: decrypt failed")
ErrIntegrity = errors.New("enigma: integrity check failed")
ErrNotImplemented = errors.New("enigma: not implemented")
ErrCapabilityMismatch = errors.New("enigma: capability mismatch")
ErrRecipientNotFound = errors.New("enigma: recipient not found")
ErrUnsupportedCapability = errors.New("enigma: unsupported capability")
ErrInvalidKeyReference = errors.New("enigma: invalid key reference")
ErrKeyNotFound = errors.New("enigma: key not found")
ErrKeyAlgorithmMismatch = errors.New("enigma: key algorithm mismatch")
ErrResolveRecipientFailed = errors.New("enigma: recipient resolver failed")
ErrCreateKeyFailed = errors.New("enigma: create key failed")
ErrDeleteKeyFailed = errors.New("enigma: delete key failed")
ErrRotateKeyFailed = errors.New("enigma: rotate key failed")
)

// OpError stores operation context while preserving typed errors via errors.Is/errors.As.
Expand Down
113 changes: 113 additions & 0 deletions keymgmt/keymgmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package keymgmt

import "context"

type KeyManager interface {
CreateKey(ctx context.Context, req CreateKeyRequest) (*KeyDescriptor, error)
GetKey(ctx context.Context, ref KeyReference) (*KeyDescriptor, error)
RotateKey(ctx context.Context, ref KeyReference, req RotateKeyRequest) (*KeyDescriptor, error)
DeleteKey(ctx context.Context, ref KeyReference) error
Capabilities(ctx context.Context) CapabilitySet
}

type KeyClass string

const (
KeyClassAsymmetricKEM KeyClass = "asymmetric_kem"
KeyClassAsymmetricEncryption KeyClass = "asymmetric_encryption"
KeyClassSymmetricWrapping KeyClass = "symmetric_wrapping"
)

type KeyPurpose string

const (
PurposeKeyEncapsulation KeyPurpose = "key_encapsulation"
PurposeKeyWrapping KeyPurpose = "key_wrapping"
PurposeRecipientDecrypt KeyPurpose = "recipient_decrypt"
)

type KeyAlgorithm string

const (
AlgorithmMLKEM768 KeyAlgorithm = "ml-kem-768"
AlgorithmMLKEM1024 KeyAlgorithm = "ml-kem-1024"
AlgorithmRSAOAEP3072SHA256 KeyAlgorithm = "rsa-oaep-3072-sha256"
AlgorithmAES256GCM KeyAlgorithm = "aes-256-gcm"
)

type ProtectionLevel string

const (
ProtectionSoftware ProtectionLevel = "software"
ProtectionHSM ProtectionLevel = "hsm"
ProtectionKMS ProtectionLevel = "kms"
)

type SecurityLevel string

const (
SecurityLevelLocalPQ SecurityLevel = "local_pq"
SecurityLevelCloudPQNative SecurityLevel = "cloud_pq_native"
SecurityLevelCloudClassic SecurityLevel = "cloud_classical"
)

type CreateKeyRequest struct {
Name string `json:"name"`
Purpose KeyPurpose `json:"purpose"`
Algorithm KeyAlgorithm `json:"algorithm"`
ProtectionLevel ProtectionLevel `json:"protection_level"`
Exportable bool `json:"exportable"`
Metadata map[string]string `json:"metadata,omitempty"`
}

type RotateKeyRequest struct {
SuccessorName string `json:"successor_name"`
Metadata map[string]string `json:"metadata,omitempty"`
}

type KeyReference struct {
Backend string `json:"backend"`
URI string `json:"uri"`
ID string `json:"id"`
Version string `json:"version"`
}

type PublicKeyInfo struct {
Algorithm KeyAlgorithm `json:"algorithm"`
Data []byte `json:"data"`
}

type KeyDescriptor struct {
ID string `json:"id"`
Backend string `json:"backend"`
Class KeyClass `json:"class"`
Purpose KeyPurpose `json:"purpose"`
Algorithm KeyAlgorithm `json:"algorithm"`
SecurityLevel SecurityLevel `json:"security_level"`
Reference KeyReference `json:"reference"`
PublicInfo *PublicKeyInfo `json:"public_info,omitempty"`
Capabilities CapabilitySet `json:"capabilities"`
Metadata map[string]string `json:"metadata,omitempty"`
}

type CapabilitySet struct {
CanCreateKeys bool `json:"can_create_keys"`
CanDeleteKeys bool `json:"can_delete_keys"`
CanRotateProviderNative bool `json:"can_rotate_provider_native"`
CanExportPublicKey bool `json:"can_export_public_key"`
CanResolveRecipient bool `json:"can_resolve_recipient"`
SupportsPQNatively bool `json:"supports_pq_natively"`
SupportsClassicalWrapping bool `json:"supports_classical_wrapping"`
SupportsRewrapWorkflow bool `json:"supports_rewrap_workflow"`
}

func CloneMap(in map[string]string) map[string]string {
if len(in) == 0 {
return nil
}
out := make(map[string]string, len(in))
for k, v := range in {
out[k] = v
}
return out
}
Loading