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
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Enigma is a pure Go library for high-level document and field encryption using m
- Rewrap support without content re-encryption when a valid recipient can unwrap the DEK.
- Separate compact field/value encryption API.
- Local post-quantum recipient implementation using `crypto/mlkem` (ML-KEM-768 default, ML-KEM-1024 optional).
- Scaleway Key Manager backend for key lifecycle, recipient resolution, and runtime DEK wrap/unwrap using the official Scaleway SDK.
- Scaleway Key Manager backend for key lifecycle, recipient resolution, and runtime DEK wrap/unwrap using the official Scaleway SDK, with native ML-KEM wrapping (ML-KEM-1024 default).
- GCP/AWS/Azure provider packages present as explicit capability-aware stubs (no fake crypto behavior).

## Installation
Expand All @@ -28,7 +28,7 @@ go get github.com/hyperscale-stack/enigma
- `container`: strict parser/serializer for the binary envelope format.
- `recipient`: recipient abstractions and capability model.
- `recipient/localmlkem`: fully implemented local PQ recipient.
- `recipient/scwkm`: Scaleway Key Manager runtime recipient (classical cloud wrapping).
- `recipient/scwkm`: Scaleway Key Manager runtime recipient (native ML-KEM wrapping, classical wrapping still supported).
- `recipient/{gcpkms,awskms,azurekv}`: explicit cloud stubs for v1.
- `keymgmt`: key lifecycle interfaces and domain types.
- `keymgmt/localmlkem`: local ML-KEM key manager with filesystem-backed metadata persistence.
Expand Down Expand Up @@ -106,7 +106,7 @@ _ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc",
)
```

### Scaleway KMS (classical cloud backend)
### Scaleway KMS (native post-quantum backend)

```go
km, _ := keymgmtscwkm.NewManager(keymgmtscwkm.Config{
Expand All @@ -117,16 +117,21 @@ km, _ := keymgmtscwkm.NewManager(keymgmtscwkm.Config{
desc, _ := km.CreateKey(context.Background(), keymgmt.CreateKeyRequest{
Name: "org-primary",
Purpose: keymgmt.PurposeKeyWrapping,
Algorithm: keymgmt.AlgorithmAES256GCM,
ProtectionLevel: keymgmt.ProtectionKMS,
// Algorithm omitted: defaults to keymgmt.AlgorithmMLKEM1024 (ML-KEM-1024).
})

res, _ := resolverscwkm.New(resolverscwkm.Config{
Region: "fr-par",
ProjectID: "<project-id>",
})
runtimeRecipient, _ := res.ResolveRecipient(context.Background(), desc.Reference)
_ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc", document.WithRecipient(runtimeRecipient))

// ML-KEM keys held by Scaleway are `cloud-pq-native`, not `local-pq`.
_ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc",
document.WithRecipient(runtimeRecipient),
document.WithDefaultProfile(enigma.ProfileCloudBalanced),
)
```

## Security Properties (Implemented)
Expand All @@ -140,7 +145,7 @@ _ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc", doc
## Important Limitations

- Go memory is not fully controllable; key wiping is best-effort only.
- Scaleway backend is classical cloud wrapping only and does not provide PQ-native guarantees.
- Scaleway PQ-native wrapping keeps the ML-KEM private key inside Key Manager; use `localmlkem` when the key must stay on the host.
- GCP/AWS/Azure backend packages are still 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.
Expand All @@ -158,8 +163,8 @@ _ = document.EncryptFile(context.Background(), "plain.txt", "plain.txt.enc", doc
## Capability Model

- `local-pq`: local ML-KEM recipient.
- `cloud-classical`: cloud-backed classical wrapping path.
- `cloud-pq-native`: cloud-backed native PQ path.
- `cloud-classical`: cloud-backed classical wrapping path (Scaleway AES/RSA keys).
- `cloud-pq-native`: cloud-backed native PQ path (Scaleway ML-KEM keys).

The active capability is explicit in recipient descriptors and metadata.

Expand Down
6 changes: 3 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Enigma is structured into five layers:
2. Recipient / key wrapping layer
- Defines recipient interface.
- Wraps and unwraps a random DEK.
- Supports local PQ recipient (ML-KEM), Scaleway KMS classical recipient, and cloud-provider stubs with explicit capabilities.
- Supports local PQ recipient (ML-KEM), Scaleway KMS recipient (native ML-KEM or classical wrapping), and cloud-provider stubs with explicit capabilities.

3. Symmetric encryption layer
- Uses one DEK per encrypted object.
Expand Down Expand Up @@ -62,11 +62,11 @@ Enigma is structured into five layers:
### Scaleway Backend Notes

- Backend ID: `scaleway_kms`.
- Security capability: `cloud-classical`.
- Security capability: `cloud-pq-native` for ML-KEM keys, `cloud-classical` for AES/RSA keys.
- Uses Scaleway Key Manager as root of trust for DEK wrapping and unwrapping.
- Enigma still performs local content encryption (`XChaCha20-Poly1305` or `AES-256-GCM`).
- Wrapped DEKs and encrypted payloads are stored and managed by the application.
- No PQ-native guarantee for this backend.
- ML-KEM keys are wrapped natively by Key Manager; the private key never leaves Scaleway.

### Rotation versus Rewrap

Expand Down
72 changes: 57 additions & 15 deletions docs/backends/scaleway-kms.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ This backend uses the official Scaleway Go SDK:

- `github.com/scaleway/scaleway-sdk-go`

ML-KEM support (`usage.key_encapsulation` plus the `WrapKey`/`UnwrapKey` endpoints) landed on
the SDK main branch after `v1.0.0-beta.37` was tagged, so `go.mod` currently pins a
pseudo-version of that branch. Switch back to a released tag once `v1.0.0-beta.38` ships.

## Security Model

- Scaleway Key Manager is used as a root of trust for envelope encryption key custody.
Expand All @@ -20,25 +24,53 @@ This backend uses the official Scaleway Go SDK:
- Wrapped DEKs are stored by the application in Enigma containers/value blobs.
- DEKs are not stored by Scaleway Key Manager for the application lifecycle.

This backend is classical cloud cryptography:
The delivered guarantee depends on the key algorithm, and is reported per key rather than
assumed for the whole backend:

- ML-KEM keys: `SecurityLevel` = `cloud_pq_native`, recipient capability `cloud-pq-native`.
- AES/RSA keys: `SecurityLevel` = `cloud_classical`, recipient capability `cloud-classical`.

- `SecurityLevel`: `cloud_classical`
- `SupportsPQNatively`: `false`
- No post-quantum guarantee is provided by this backend.
Post-quantum wrapping is performed by Scaleway Key Manager itself. Enigma never sees the
ML-KEM private key material.

## Supported Algorithms

Current lifecycle/runtime mapping:

- `aes-256-gcm` -> Scaleway key usage `symmetric_encryption/aes_256_gcm`
- `rsa-oaep-3072-sha256` -> Scaleway key usage `asymmetric_encryption/rsa_oaep_3072_sha256`
| Enigma algorithm | Scaleway key usage | Runtime endpoints | Security level |
|---|---|---|---|
| `ml-kem-1024` (**default**) | `key_encapsulation/ml_kem_1024` | `WrapKey` / `UnwrapKey` | `cloud_pq_native` |
| `ml-kem-768` | `key_encapsulation/ml_kem_768` | `WrapKey` / `UnwrapKey` | `cloud_pq_native` |
| `aes-256-gcm` | `symmetric_encryption/aes_256_gcm` | `Encrypt` / `Decrypt` | `cloud_classical` |
| `rsa-oaep-3072-sha256` | `asymmetric_encryption/rsa_oaep_3072_sha256` | `Encrypt` / `Decrypt` | `cloud_classical` |

`CreateKey` defaults to `ml-kem-1024` when `CreateKeyRequest.Algorithm` is empty
(`scwkm.DefaultAlgorithm`). Any other algorithm is rejected with `ErrUnsupportedAlgorithm`.

`PurposeKeyEncapsulation` is accepted only for ML-KEM algorithms; combining it with a
classical algorithm returns `ErrUnsupportedCapability`.

Scaleway caps the wrap endpoint at 2 KB of plaintext and the unwrap endpoint at 4 KB of
ciphertext. Enigma DEKs are 32 bytes, so these limits are never reached in practice.

Not supported in this backend:
Use the `localmlkem` backend when the PQ private key must stay local instead of in the KMS.

- `ml-kem-768`
- `ml-kem-1024`
## Wrap Algorithm Identifiers

Use `localmlkem` backend for local PQ workflows.
The identifier stored in each container recipient entry selects the unwrap path, which keeps
existing containers readable after this backend gained ML-KEM support:

- `scwkm+encrypt-v1` -> classical `Encrypt`/`Decrypt`
- `scwkm+mlkem-768-wrap-v1` -> native `WrapKey`/`UnwrapKey`
- `scwkm+mlkem-1024-wrap-v1` -> native `WrapKey`/`UnwrapKey`

## Profile Interaction

The `local-pq` profile (the default in `document` and `field`) requires `local-pq` recipients
and rejects `cloud-pq-native` ones: the profile asserts that the private key never leaves the
host, which a KMS-held ML-KEM key does not satisfy.

Use `WithDefaultProfile(enigma.ProfileCloudBalanced)` with Scaleway ML-KEM recipients.

## Configuration

Expand Down Expand Up @@ -68,7 +100,12 @@ Scaleway references are serialized as generic Enigma `KeyReference` values:
- `Backend`: `scaleway_kms`
- `ID`: Scaleway key ID
- `Version`: key rotation count string
- `URI`: `enigma-scwkm://key/<key-id>?region=<region>&project_id=<project-id>&version=<n>`
- `URI`: `enigma-scwkm://key/<key-id>?alg=<algorithm>&region=<region>&project_id=<project-id>&version=<n>`

The `alg` parameter lets a resolved recipient pick the wrapping path without an extra
Key Manager round trip. References produced before this parameter existed stay valid and
resolve to the classical path. Persist the `KeyReference` returned by the manager rather than
rebuilding it by hand, so the algorithm stays attached to the key.

`KeyReference` never stores credentials or private key material.

Expand All @@ -81,8 +118,8 @@ km, _ := keymgmtscwkm.NewManager(keymgmtscwkm.Config{Region: "fr-par", ProjectID
desc, _ := km.CreateKey(ctx, keymgmt.CreateKeyRequest{
Name: "org-a-primary",
Purpose: keymgmt.PurposeKeyWrapping,
Algorithm: keymgmt.AlgorithmAES256GCM,
ProtectionLevel: keymgmt.ProtectionKMS,
// Algorithm omitted: defaults to keymgmt.AlgorithmMLKEM1024.
})

// Store desc.Reference in your application database.
Expand All @@ -99,7 +136,10 @@ runtimeRecipient, _ := res.ResolveRecipient(ctx, storedRef)
### 3) Encrypt/decrypt with existing document/field APIs

```go
_ = document.EncryptFile(ctx, "plain.txt", "plain.txt.enc", document.WithRecipient(runtimeRecipient))
_ = document.EncryptFile(ctx, "plain.txt", "plain.txt.enc",
document.WithRecipient(runtimeRecipient),
document.WithDefaultProfile(enigma.ProfileCloudBalanced),
)
_ = document.DecryptFile(ctx, "plain.txt.enc", "plain.dec.txt", document.WithRecipient(runtimeRecipient))
```

Expand All @@ -119,12 +159,14 @@ Scaleway backend reports:
- `CanRotateProviderNative = true`
- `CanExportPublicKey = true` (backend capability)
- `CanResolveRecipient = true`
- `SupportsPQNatively = false`
- `SupportsPQNatively = true`
- `SupportsClassicalWrapping = true`
- `SupportsRewrapWorkflow = true`

## Current Limitations

- No PQ-native wrapping.
- PQ-native wrapping relies on Scaleway holding the ML-KEM private key; use `localmlkem` when
the key must stay on the host.
- Only explicitly mapped algorithms are accepted.
- ML-KEM support requires an SDK build newer than `v1.0.0-beta.37`.
- Live cloud integration tests are optional and not required for standard CI runs.
4 changes: 2 additions & 2 deletions docs/key-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ The Scaleway backend is implemented in:
- `recipient/scwkm`

Properties:
- cloud classical security level (`cloud_classical`)
- no native PQ guarantee
- native PQ security level (`cloud_pq_native`) for ML-KEM keys, `cloud_classical` for AES/RSA keys
- `ml-kem-1024` is the default algorithm when a create request omits one
- provider-native key rotation support via Key Manager
- runtime recipient resolution from stored `KeyReference`

Expand Down
2 changes: 2 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- `keymgmt/scwkm` lifecycle implementation
- `recipient/scwkm` runtime DEK wrap/unwrap
- `resolver/scwkm` key-reference resolution
- native ML-KEM wrapping (`ml-kem-1024` default, `ml-kem-768` supported)
- Chunked document encryption and stream APIs
- Rewrap path without content re-encryption
- Field encryption compact format
Expand All @@ -24,6 +25,7 @@
- Keep unsupported capabilities explicit; no fake cloud behavior.

2. Scaleway integration hardening
- Repin `scaleway-sdk-go` on a released tag once ML-KEM ships in `v1.0.0-beta.38`.
- Add opt-in live integration tests (credential-gated) for create/get/rotate/delete and wrap/unwrap flows.
- Add operational guidance for key policies and production rollout checks.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/hyperscale-stack/enigma
go 1.26.0

require (
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37.0.20260908130414-d3305e10fca3
github.com/stretchr/testify v1.12.1
golang.org/x/crypto v0.56.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37 h1:1Q6K8D0BagYYEnCTkT9fn3YHUFb06bS1OvIHWcc3JQM=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37/go.mod h1:Rtb4r3WZ5x4AqmL3t/wiF/DmQi+7GlU/nCRdqFbClV4=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37.0.20260908130414-d3305e10fca3 h1:SOYidC/j3uqsSzfTQ7Y86pXqbl2E3sjGrD8V9mQs1Sg=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.37.0.20260908130414-d3305e10fca3/go.mod h1:4Py4dEgJWqoPEn06mi/00bJn/XxY3B+GnA8MbPBV3p8=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
Expand Down
10 changes: 10 additions & 0 deletions internal/scwkmapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type Client interface {
DeleteKey(ctx context.Context, req *keymanager.DeleteKeyRequest) error
Encrypt(ctx context.Context, req *keymanager.EncryptRequest) (*keymanager.EncryptResponse, error)
Decrypt(ctx context.Context, req *keymanager.DecryptRequest) (*keymanager.DecryptResponse, error)
WrapKey(ctx context.Context, req *keymanager.WrapKeyRequest) (*keymanager.WrapKeyResponse, error)
UnwrapKey(ctx context.Context, req *keymanager.UnwrapKeyRequest) (*keymanager.UnwrapKeyResponse, error)
}

type SDKClient struct {
Expand Down Expand Up @@ -90,3 +92,11 @@ func (c *SDKClient) Encrypt(ctx context.Context, req *keymanager.EncryptRequest)
func (c *SDKClient) Decrypt(ctx context.Context, req *keymanager.DecryptRequest) (*keymanager.DecryptResponse, error) {
return c.api.Decrypt(req, scw.WithContext(ctx))
}

func (c *SDKClient) WrapKey(ctx context.Context, req *keymanager.WrapKeyRequest) (*keymanager.WrapKeyResponse, error) {
return c.api.WrapKey(req, scw.WithContext(ctx))
}

func (c *SDKClient) UnwrapKey(ctx context.Context, req *keymanager.UnwrapKeyRequest) (*keymanager.UnwrapKeyResponse, error) {
return c.api.UnwrapKey(req, scw.WithContext(ctx))
}
Loading