Skip to main content

Key Management (KMS)

Choose where the private keys of signing key chains live and configure the provider in kms.json. By default every key is created by the db provider and stored encrypted in the database (see Encryption keys). Every field of kms.json is listed in the KMS config reference.

Choose a provider​

typePrivate key lives inCreateImportDeleteUse it for
dbThe EUDIPLO database, encryptedyesyesyesDevelopment, small installations
vaultHashiCorp Vault Transit engineyesyesyesSelf-hosted production
aws-kmsAWS KMS (ECC_NIST_P256, SIGN_VERIFY)yesnoyes (7-day deletion window)Production on AWS
pkcs11A hardware security module via PKCS#11yesnoyesHSMs (SoftHSM, YubiHSM, CloudHSM, …)
httpYour own remote signing serviceyesif canImportyesCustom key management
cscA Cloud Signature Consortium (CSC v2) serviceyesnonoQualified remote signing

All providers sign with ES256 (P-256). Encryption keys for credential and response encryption always use the db provider. Keys that cannot be imported must be created in the provider, so plan the provider before you create key chains.

Configure kms.json​

The global file is <CONFIG_FOLDER>/kms.json (/app/config/config/kms.json in the image, config/kms.json in CLI projects). It is read at startup:

kms.json
{
"defaultProvider": "vault",
"providers": [
{ "id": "db", "type": "db" },
{
"id": "vault",
"type": "vault",
"description": "Production Vault",
"vaultUrl": "${VAULT_ADDR}",
"vaultToken": "${VAULT_TOKEN}"
}
]
}

Without the file, only the db provider exists. defaultProvider (default db) must match a provider id; provider IDs must be unique. Write secrets as ${VAR} placeholders and set the variables in the backend's environment; an unset variable stops the startup. The schema is strict, so unknown fields are rejected.

Checkpoint: GET /api/key-chain/providers lists the providers and the default; GET /api/key-chain/providers/health reports ok for each, and eudiplo doctor includes the same check.

Provider notes​

Vault. Only vaultUrl and vaultToken are supported; there is no AppRole login. Keys are created in the Transit engine mounted at transit, which the backend enables if it is missing; the mount path is not configurable. Give the token permissions on transit/* (and on sys/mounts/transit for the automatic mount). Since 9.0, Vault holds signing keys only through kms.json; the old KM_TYPE, VAULT_NAMESPACE and VAULT_MOUNT_PATH variables have no effect. The encryption key for data at rest is a separate setting (Encryption keys).

AWS KMS. Omit accessKeyId and secretAccessKey to use the AWS SDK default credential chain (IAM role, IRSA). The backend calls CreateKey (with a tag, so kms:TagResource is needed as well), GetPublicKey, Sign, ScheduleKeyDeletion and, for the health check, ListKeys.

PKCS#11. The vendor's PKCS#11 library must be available in the backend container; slot is the slot index or the token label. readOnly: true opens a read-only session, which cannot create or delete keys.

HTTP. EUDIPLO calls your service for key generation, signing, deletion and health. Authenticate it with auth of type bearer, oauth2-client-credentials or mtls (or none on a trusted network). The endpoints your service must implement are specified in the reference.

Create a key in a provider​

Choose the provider per key chain with kmsProvider; without it the defaultProvider is used:

curl -X POST https://eudiplo.example.com/api/key-chain \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"usageType": "attestation",
"type": "standalone",
"kmsProvider": "vault",
"description": "Credential signing key"
}'

usageType is one of access, attestation, trustList, statusList and encrypt; type is standalone or internalChain. Certificates, rotation and key import are covered in Keys and certificates. A key chain stays with its provider; to move it, create a new key chain in the target provider and switch the configurations that reference it.

Per-tenant providers​

A tenant can add or override providers without touching the global file. Its kms.json is stored at <CONFIG_FOLDER>/<tenant-id>/kms.json and merged over the global one: providers with the same id replace the global entry, and the tenant's defaultProvider wins. Manage it through the API (roles tenant:admin or tenants:manage, because the configuration contains provider credentials):

RequestEffect
GET /api/key-chain/providers/configtenantConfig (the tenant file or null) and effectiveConfig (merged)
PUT /api/key-chain/providers/configValidate and write the tenant file, body as in kms.json
DELETE /api/key-chain/providers/configRemove the tenant file; the global configuration applies again

The API never returns credentials (vaultToken, secretAccessKey, pin, auth.token, auth.clientSecret, clientSecret, sad and authorizeAuthData[].value); it returns <redacted> instead. In tenantConfig, a credential stored as a ${ENV_VAR} placeholder is returned as the placeholder. Providers of the global file appear in effectiveConfig with their non-secret settings only.

A PUT replaces the whole tenant file. To keep a stored credential, send <redacted> back unchanged: it is replaced by the stored value of the provider with the same id and type. Any other value, including a placeholder, replaces the credential. <redacted> for a credential the tenant file does not store, for example of a global provider copied from effectiveConfig, is rejected with 400.

The API needs CONFIG_FOLDER to be writable. The tenant kms.json is also part of configuration bundles, with secrets exported as placeholders.