Skip to main content
Version: v0.25.0 (Latest)

KMS Container

sphereon/enterprise-kms is the cryptographic authority for the deployment. Every key the issuer signs credentials with, every signing key on an OID4VP request object, every key alias an AS uses to sign access tokens, and every audit-checkpoint signing key lives on this container. The other data-plane containers do not generate or hold key material; they reference keys by alias and call the KMS REST to operate on them.

That centralisation is deliberate. It lets a deployment use a single provider backend (an HSM, AWS KMS, Azure Key Vault) across all of its issuer, verifier, AS, and DID activity, with one place to manage key lifecycles and one audit trail for crypto operations. It also keeps the issuer, verifier, and AS images free of provider SDKs they would otherwise need to ship.

KMS container internal architecture

Why the KMS Is Internal-Only

The KMS sees raw key material on its way in to the provider backend and sees plaintext payloads on their way in to be signed. There is no scenario in which a public client should call it directly: wallets do not call KMS; relying parties do not call KMS; only the EDK's own issuer, verifier, AS, and DID containers do.

The published Helm chart binds the KMS service to an internal LB or a service-mesh-only endpoint. The Compose file binds it to an internal Docker network without an exposed host port. The admin REST sits behind the same internal binding, with a bearer-JWT auth requirement: operators reach it through a port-forward, a bastion, or whatever internal access pattern the customer's cluster provides.

If a deployment ever needs the KMS reachable from outside, the right answer is almost always to put a peer-authenticated EDK service in front of it, not to expose the KMS itself.

Providers

A provider is the actual backend that holds the key material and performs the operations. The EDK ships first-party providers for:

  • Software keystore. Keys generated and held in process. Useful for development and for non-critical signing duties.
  • AWS KMS. Keys live in AWS KMS; the EDK provider issues Sign, Verify, and GetPublicKey calls. Provider configuration carries the AWS region, credential strategy, and the key id mapping.
  • Azure Key Vault. Keys live in Azure Key Vault under the configured vault URL. Authentication via the Azure credential chain (managed identity, service principal, environment).
  • Digidentity CSC. Keys held in a Digidentity Cloud Signature Consortium account, suitable for eIDAS qualified signing.
  • REST. A KMS provider that itself fronts another KMS over the EDK KMS REST surface. Useful when a tenant needs to route to a remote KMS instance behind the EDK KMS façade.

Providers register through the admin REST as typed configuration, never as raw JSONB. The provider id is a stable identifier (software-default, aws-eu-west-1, azure-vault-prod) that tenants reference when they bind a key alias.

A KMS instance can have any number of providers registered simultaneously. A tenant chooses which provider to use per signing duty by binding a kms_provider integration row on the tenant that uses the key (the issuer, verifier, or AS), pointing at the provider id on this KMS.

Key Aliases

A key alias is a logical name a data plane uses to refer to a key. The alias structure is (tenant, service, purpose):

  • tenant: the tenant slug the key belongs to.
  • service: which data plane uses it: issuer, verifier, as, did, audit.
  • purpose: what the key signs: credential-signing, request-object-signing, access-token-signing, audit-checkpoint, did-update.

The first time a data plane needs a key for a given alias, it either resolves to a system-provisioned default (the deployment template generates one per tenant on first activity) or to an operator-supplied alias the tenant admin has configured through the signing-key admin REST. Either way, the data plane never sees the raw key, it only sees the alias and the provider routing.

Key rotation is per-alias and per-tenant. The KMS holds the key history; the data plane queries the current public key when it needs to publish JWKS or to verify a counterparty signature.

What the REST Surface Provides

The KMS exposes four HTTP adapter families:

  • KeysHttpAdapter: key generation, key import, key listing, key deletion, key metadata read. Tenant context is JWT-bound.
  • ProvidersHttpAdapter: provider registration, provider configuration, provider listing. Platform-admin scoped.
  • ResolversHttpAdapter: public-key resolution by alias or key id. This is what the issuer, verifier, and AS call when they need to publish JWKS or verify a counterparty signature.
  • SignaturesHttpAdapter: sign, verify, encrypt, decrypt. The hot path. Issuer credential signing, AS access-token signing, OID4VP request-object signing, and audit-checkpoint signing all land here.

Multi-tenant isolation is enforced on every call: a tenant can only operate on its own keys, and the cross-tenant isolation is covered by the MultiTenantKmsIsolationTest integration suite that runs in CI against a real KMS instance.

Persistence

The KMS stores only key-reference bookkeeping in Postgres: the key_reference table maps (tenant, alias) to (provider_id, kid, metadata). The actual key material lives in the provider backend. That separation is important: even if the Postgres is exfiltrated, no key material is in it.

The persistence module is lib-crypto-key-persistence-postgresql. It also has a MySQL counterpart (lib-crypto-key-persistence-mysql) for deployments that standardise on MySQL.

Building and Running

The Dockerfile is a two-stage build because the KMS image is the one EDK image that builds the fat JAR inside the container build (the other four use a pre-built fat JAR copied in from the Gradle build). The base layer is eclipse-temurin:21-jdk; the runtime layer is eclipse-temurin:21-jre with a non-root appuser (uid 10001).

The container exposes port 8080. The published Helm chart binds it to an internal-only Service and an internal-only Ingress; the published Compose file binds it to the enterprise_network bridge without a host port mapping.

The minimal application.yaml shipped in the image is intentionally permissive for local testing:

server:
rest:
port: 8080
auth:
enabled: false
anonymous:
allowed: true

A production deployment overrides this through the standard EDK config layering (environment variables, mounted YAML overlays, cloud config providers) to require bearer-JWT auth on every call and to disable anonymous access. Per-tenant configuration, including provider routing and key alias overrides, lives in the shared Postgres under tenant_config_property.

Operational Notes

  • Capacity. The KMS is rarely the bottleneck for low-to-medium throughput. When it is, the bottleneck is usually the provider backend (AWS KMS rate limits, HSM throughput) rather than the container.
  • Failure modes. If the KMS is unreachable, signing-dependent paths fail immediately on the calling data plane. There is no fallback to a local key, by design. The deployment template's readiness probes consider the KMS dependency healthy before marking issuer/verifier/AS ready.
  • Provider failover. A tenant can configure multiple kms_provider integration rows and switch between them at the application level. The KMS itself does not currently sequence cross-provider failover automatically.
  • Auditing. Every signing operation emits an audit event with the tenant, alias, provider id, kid, and timestamp. The audit signing key alias is a KMS key in its own right ((tenant, audit, audit-checkpoint)).