Skip to content

Commit e4c5fdf

Browse files
authored
Merge pull request #1 from hyperscale-stack/feature/key-manager
feat: add key manager
2 parents d7cdb1b + 46dcb81 commit e4c5fdf

13 files changed

Lines changed: 1017 additions & 20 deletions

File tree

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ go get github.com/hyperscale-stack/enigma
2828
- `recipient`: recipient abstractions and capability model.
2929
- `recipient/localmlkem`: fully implemented local PQ recipient.
3030
- `recipient/{gcpkms,awskms,azurekv,scwkm}`: explicit cloud stubs for v1.
31+
- `keymgmt`: key lifecycle interfaces and domain types.
32+
- `keymgmt/localmlkem`: local ML-KEM key manager with filesystem-backed metadata persistence.
33+
- `resolver`: recipient resolver interfaces and backend registry.
34+
- `resolver/localmlkem`: resolves local key references into runtime recipients.
3135
- `mem`: best-effort memory hygiene helpers.
3236

3337
## Quick Start
@@ -79,6 +83,25 @@ plaintext, _ := field.DecryptValue(context.Background(), ciphertext,
7983
)
8084
```
8185

86+
### Key lifecycle and recipient resolution
87+
88+
```go
89+
km, _ := keymgmtlocalmlkem.NewManager("/var/lib/enigma-keys")
90+
desc, _ := km.CreateKey(context.Background(), keymgmt.CreateKeyRequest{
91+
Name: "tenant-a-primary",
92+
Purpose: keymgmt.PurposeRecipientDecrypt,
93+
Algorithm: keymgmt.AlgorithmMLKEM768,
94+
ProtectionLevel: keymgmt.ProtectionSoftware,
95+
})
96+
97+
res, _ := resolverlocalmlkem.New("/var/lib/enigma-keys")
98+
runtimeRecipient, _ := res.ResolveRecipient(context.Background(), desc.Reference)
99+
100+
_ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc",
101+
document.WithRecipient(runtimeRecipient),
102+
)
103+
```
104+
82105
## Security Properties (Implemented)
83106

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

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

123+
## Lifecycle versus Runtime
124+
125+
- `KeyManager` provisions, inspects, rotates, and deletes keys.
126+
- `Recipient` only wraps and unwraps DEKs at runtime.
127+
- `RecipientResolver` resolves a stored `KeyReference` back to a runtime `Recipient`.
128+
- Key rotation and document rewrap are distinct operations. Rotation creates successor keys; rewrap updates recipient entries in existing encrypted containers.
129+
99130
## Capability Model
100131

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

113144
See:
114145
- [`docs/architecture.md`](docs/architecture.md)
146+
- [`docs/key-management.md`](docs/key-management.md)
115147
- [`docs/container-format.md`](docs/container-format.md)
116148
- [`docs/threat-model.md`](docs/threat-model.md)
117149
- [`SECURITY.md`](SECURITY.md)

docs/architecture.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,36 @@
22

33
## Overview
44

5-
Enigma is structured into four layers:
5+
Enigma is structured into five layers:
66

7-
1. Recipient / key wrapping layer
7+
1. Key lifecycle and resolution layer
8+
- `keymgmt`: key lifecycle interfaces and domain types.
9+
- `keymgmt/localmlkem`: local ML-KEM key manager implementation.
10+
- `resolver`: recipient resolution interfaces and registry.
11+
- `resolver/localmlkem`: resolves stored local key references into runtime recipients.
12+
- Separates key provisioning from runtime wrapping semantics.
13+
14+
2. Recipient / key wrapping layer
815
- Defines recipient interface.
916
- Wraps and unwraps a random DEK.
1017
- Supports local PQ recipient (ML-KEM) and cloud-provider stubs with explicit capabilities.
1118

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

20-
3. Container format layer
27+
4. Container format layer
2128
- Implements strict binary envelope parser/serializer.
2229
- Header split:
2330
- immutable section (content-bound)
2431
- recipient section (rewrap-mutable)
2532
- Header authentication tag is derived from DEK material.
2633

27-
4. High-level API layer
34+
5. High-level API layer
2835
- `document` package:
2936
- `EncryptFile`, `DecryptFile`
3037
- `NewEncryptWriter`, `NewDecryptReader`
@@ -42,6 +49,21 @@ Enigma is structured into four layers:
4249
- nonce salt
4350
- reserved material
4451

52+
## Key Lifecycle Model
53+
54+
- `KeyManager` provisions and manages key lifecycle.
55+
- `Recipient` only performs runtime `WrapKey`/`UnwrapKey`.
56+
- `RecipientResolver` converts stored `KeyReference` records into runtime `recipient.Recipient` instances.
57+
- `KeyReference` is stable, serializable metadata that never includes private key material.
58+
- Application key ownership mapping (for example per tenant or per organization) is handled by the application, not by Enigma.
59+
60+
### Rotation versus Rewrap
61+
62+
- Rotation creates or selects successor keys at lifecycle level.
63+
- Rewrap updates recipient entries in encrypted documents.
64+
- Rotation does not automatically re-encrypt existing payloads.
65+
- Rewrap does not create or rotate backend keys.
66+
4567
## Rewrap Model
4668

4769
Rewrap attempts to unwrap DEK with supplied recipient(s), then rewrites only:

docs/key-management.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Key Management
2+
3+
## Purpose
4+
5+
Enigma separates key lifecycle management from runtime wrapping and unwrapping.
6+
7+
This split avoids mixing concerns:
8+
- lifecycle APIs (`KeyManager`) provision and manage keys
9+
- runtime recipient APIs (`recipient.Recipient`) wrap and unwrap DEKs
10+
- resolver APIs (`RecipientResolver`) rebuild runtime recipients from stored key references
11+
12+
## Core Concepts
13+
14+
### KeyManager
15+
16+
`KeyManager` is responsible for key lifecycle operations:
17+
- create key
18+
- inspect key
19+
- rotate key (native or successor workflow)
20+
- delete key
21+
- report capability set
22+
23+
### KeyReference
24+
25+
`KeyReference` is a stable, serializable key pointer suitable for storage in an application database.
26+
27+
A valid key reference must not include private key material.
28+
29+
### RecipientResolver
30+
31+
`RecipientResolver` turns a `KeyReference` back into a runtime `recipient.Recipient`.
32+
33+
Applications can persist `KeyReference` records and resolve recipients on demand for encryption/decryption operations.
34+
35+
## Local ML-KEM Backend
36+
37+
The local backend is implemented in:
38+
- `keymgmt/localmlkem`
39+
- `resolver/localmlkem`
40+
41+
### Storage model
42+
43+
- records are stored under `<root>/localmlkem/v1/<key-id>/<version>.json`
44+
- files are written with mode `0600`
45+
- directories are created with mode `0700`
46+
- references include backend, id, version, and URI
47+
- private key material is persisted in backend storage, not in `KeyReference`
48+
49+
### Filesystem trust assumptions
50+
51+
- local backend security depends on host filesystem access controls
52+
- protect the configured root path with strict OS permissions
53+
- if host compromise is in scope, local software key storage may be insufficient
54+
55+
## Rotation and Rewrap
56+
57+
Rotation and rewrap are intentionally distinct operations:
58+
59+
- `KeyManager.RotateKey` creates a successor key descriptor
60+
- `document.Rewrap` updates recipient entries in encrypted containers
61+
62+
Rotation does not automatically rewrite historical ciphertext.
63+
Applications should perform rewrap workflows explicitly when policy requires migration to successor keys.
64+
65+
## Application Ownership Mapping
66+
67+
Enigma does not map keys to tenants, organizations, or environments.
68+
69+
That mapping belongs to the application.
70+
71+
Typical pattern:
72+
1. application creates a key with `KeyManager`
73+
2. application stores `KeyReference` in its own data model
74+
3. application resolves a runtime recipient with `RecipientResolver`
75+
4. application encrypts/decrypts via `document` or `field` packages
76+
77+
## Capability Reporting
78+
79+
`CapabilitySet` provides explicit backend capabilities, including:
80+
- creation/deletion support
81+
- native rotation support versus successor workflow
82+
- recipient resolution support
83+
- PQ-native support versus classical wrapping support
84+
- rewrap workflow compatibility
85+
86+
Capability reporting is descriptive and should be checked by the application before selecting a workflow.

docs/roadmap.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

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

1315
## Planned Next
1416

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

1922
2. Stronger policy controls
@@ -25,7 +28,7 @@
2528

2629
4. Advanced rewrap tooling
2730
- CLI improvements for inspection/rewrap automation.
28-
- Batch rewrap workflows.
31+
- Batch workflows that combine successor-key rotation and explicit rewrap execution.
2932

3033
5. Hardening and observability
3134
- Additional fuzzing corpora.

errors.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,26 @@ import (
66
)
77

88
var (
9-
ErrInvalidArgument = errors.New("enigma: invalid argument")
10-
ErrInvalidContainer = errors.New("enigma: invalid container")
11-
ErrUnsupportedVersion = errors.New("enigma: unsupported version")
12-
ErrUnsupportedAlgorithm = errors.New("enigma: unsupported algorithm")
13-
ErrNoRecipients = errors.New("enigma: no recipients configured")
14-
ErrWrapFailed = errors.New("enigma: key wrap failed")
15-
ErrUnwrapFailed = errors.New("enigma: key unwrap failed")
16-
ErrDecryptFailed = errors.New("enigma: decrypt failed")
17-
ErrIntegrity = errors.New("enigma: integrity check failed")
18-
ErrNotImplemented = errors.New("enigma: not implemented")
19-
ErrCapabilityMismatch = errors.New("enigma: capability mismatch")
20-
ErrRecipientNotFound = errors.New("enigma: recipient not found")
9+
ErrInvalidArgument = errors.New("enigma: invalid argument")
10+
ErrInvalidContainer = errors.New("enigma: invalid container")
11+
ErrUnsupportedVersion = errors.New("enigma: unsupported version")
12+
ErrUnsupportedAlgorithm = errors.New("enigma: unsupported algorithm")
13+
ErrNoRecipients = errors.New("enigma: no recipients configured")
14+
ErrWrapFailed = errors.New("enigma: key wrap failed")
15+
ErrUnwrapFailed = errors.New("enigma: key unwrap failed")
16+
ErrDecryptFailed = errors.New("enigma: decrypt failed")
17+
ErrIntegrity = errors.New("enigma: integrity check failed")
18+
ErrNotImplemented = errors.New("enigma: not implemented")
19+
ErrCapabilityMismatch = errors.New("enigma: capability mismatch")
20+
ErrRecipientNotFound = errors.New("enigma: recipient not found")
21+
ErrUnsupportedCapability = errors.New("enigma: unsupported capability")
22+
ErrInvalidKeyReference = errors.New("enigma: invalid key reference")
23+
ErrKeyNotFound = errors.New("enigma: key not found")
24+
ErrKeyAlgorithmMismatch = errors.New("enigma: key algorithm mismatch")
25+
ErrResolveRecipientFailed = errors.New("enigma: recipient resolver failed")
26+
ErrCreateKeyFailed = errors.New("enigma: create key failed")
27+
ErrDeleteKeyFailed = errors.New("enigma: delete key failed")
28+
ErrRotateKeyFailed = errors.New("enigma: rotate key failed")
2129
)
2230

2331
// OpError stores operation context while preserving typed errors via errors.Is/errors.As.

keymgmt/keymgmt.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package keymgmt
2+
3+
import "context"
4+
5+
type KeyManager interface {
6+
CreateKey(ctx context.Context, req CreateKeyRequest) (*KeyDescriptor, error)
7+
GetKey(ctx context.Context, ref KeyReference) (*KeyDescriptor, error)
8+
RotateKey(ctx context.Context, ref KeyReference, req RotateKeyRequest) (*KeyDescriptor, error)
9+
DeleteKey(ctx context.Context, ref KeyReference) error
10+
Capabilities(ctx context.Context) CapabilitySet
11+
}
12+
13+
type KeyClass string
14+
15+
const (
16+
KeyClassAsymmetricKEM KeyClass = "asymmetric_kem"
17+
KeyClassAsymmetricEncryption KeyClass = "asymmetric_encryption"
18+
KeyClassSymmetricWrapping KeyClass = "symmetric_wrapping"
19+
)
20+
21+
type KeyPurpose string
22+
23+
const (
24+
PurposeKeyEncapsulation KeyPurpose = "key_encapsulation"
25+
PurposeKeyWrapping KeyPurpose = "key_wrapping"
26+
PurposeRecipientDecrypt KeyPurpose = "recipient_decrypt"
27+
)
28+
29+
type KeyAlgorithm string
30+
31+
const (
32+
AlgorithmMLKEM768 KeyAlgorithm = "ml-kem-768"
33+
AlgorithmMLKEM1024 KeyAlgorithm = "ml-kem-1024"
34+
AlgorithmRSAOAEP3072SHA256 KeyAlgorithm = "rsa-oaep-3072-sha256"
35+
AlgorithmAES256GCM KeyAlgorithm = "aes-256-gcm"
36+
)
37+
38+
type ProtectionLevel string
39+
40+
const (
41+
ProtectionSoftware ProtectionLevel = "software"
42+
ProtectionHSM ProtectionLevel = "hsm"
43+
ProtectionKMS ProtectionLevel = "kms"
44+
)
45+
46+
type SecurityLevel string
47+
48+
const (
49+
SecurityLevelLocalPQ SecurityLevel = "local_pq"
50+
SecurityLevelCloudPQNative SecurityLevel = "cloud_pq_native"
51+
SecurityLevelCloudClassic SecurityLevel = "cloud_classical"
52+
)
53+
54+
type CreateKeyRequest struct {
55+
Name string `json:"name"`
56+
Purpose KeyPurpose `json:"purpose"`
57+
Algorithm KeyAlgorithm `json:"algorithm"`
58+
ProtectionLevel ProtectionLevel `json:"protection_level"`
59+
Exportable bool `json:"exportable"`
60+
Metadata map[string]string `json:"metadata,omitempty"`
61+
}
62+
63+
type RotateKeyRequest struct {
64+
SuccessorName string `json:"successor_name"`
65+
Metadata map[string]string `json:"metadata,omitempty"`
66+
}
67+
68+
type KeyReference struct {
69+
Backend string `json:"backend"`
70+
URI string `json:"uri"`
71+
ID string `json:"id"`
72+
Version string `json:"version"`
73+
}
74+
75+
type PublicKeyInfo struct {
76+
Algorithm KeyAlgorithm `json:"algorithm"`
77+
Data []byte `json:"data"`
78+
}
79+
80+
type KeyDescriptor struct {
81+
ID string `json:"id"`
82+
Backend string `json:"backend"`
83+
Class KeyClass `json:"class"`
84+
Purpose KeyPurpose `json:"purpose"`
85+
Algorithm KeyAlgorithm `json:"algorithm"`
86+
SecurityLevel SecurityLevel `json:"security_level"`
87+
Reference KeyReference `json:"reference"`
88+
PublicInfo *PublicKeyInfo `json:"public_info,omitempty"`
89+
Capabilities CapabilitySet `json:"capabilities"`
90+
Metadata map[string]string `json:"metadata,omitempty"`
91+
}
92+
93+
type CapabilitySet struct {
94+
CanCreateKeys bool `json:"can_create_keys"`
95+
CanDeleteKeys bool `json:"can_delete_keys"`
96+
CanRotateProviderNative bool `json:"can_rotate_provider_native"`
97+
CanExportPublicKey bool `json:"can_export_public_key"`
98+
CanResolveRecipient bool `json:"can_resolve_recipient"`
99+
SupportsPQNatively bool `json:"supports_pq_natively"`
100+
SupportsClassicalWrapping bool `json:"supports_classical_wrapping"`
101+
SupportsRewrapWorkflow bool `json:"supports_rewrap_workflow"`
102+
}
103+
104+
func CloneMap(in map[string]string) map[string]string {
105+
if len(in) == 0 {
106+
return nil
107+
}
108+
out := make(map[string]string, len(in))
109+
for k, v := range in {
110+
out[k] = v
111+
}
112+
return out
113+
}

0 commit comments

Comments
 (0)