Skip to content

Encryption at rest, KMS and HSM

PKIFactor encrypts every sensitive value it stores: credentials of authorities, devices and directories, private keys it generated on the server side, API keys, secrets of protocol gateways. This page explains the key hierarchy and how to place its root in a key management service (Vault Transit) or a hardware security module (PKCS#11).

Key hierarchy

   KEK  (key-encryption key)    never leaves the KMS or the HSM
    |   wraps / unwraps
   DEKs (data-encryption keys)  stored wrapped in data/keys/dek_store.json
    |   encrypt / decrypt (Fernet, AES-128-CBC + HMAC-SHA256)
   data in the database
Mode Where the root lives Use
none The data key itself, in the db_encryption_key secret file Evaluation, laboratories
vault-transit A Transit key in HashiCorp Vault (zcert-kek by default) Production
pkcs11-hsm A non-extractable AES-256 key in an HSM slot (any PKCS#11 HSM; tested with Utimaco CryptoServer and SoftHSM) Production with hardware protection

AWS KMS, Azure Key Vault and Google Cloud KMS appear in the provider list as coming soon: they are not available in this version.

Several DEKs can coexist: after a rotation, the newest encrypts new data and the older ones still decrypt existing data.

Behaviour when the KMS is unavailable

  • At start-up: PKIFactor retries three times (30 seconds apart). If the KMS still does not answer and an encrypted cache of the DEKs exists, it starts in degraded mode: everything can be read, but writes that need new secrets (issuance, revocation) pause until the KMS returns. Without a cache it refuses to start (fail-closed): it never falls back silently to a key stored next to the data.
  • At run time: a KMS outage switches to degraded mode; PKIFactor leaves it by itself when the KMS answers again (or on Reload Keys).
  • The cache is encrypted with its own key (kek_cache_key), distinct from the KMS credentials.

/health/ready reports degraded while the KMS is unavailable.

Configure a provider

In the Administration workspace, open Infrastructure > Encryption (page Encryption Providers, reserved for administrators) and click Add Provider. Give the provider a unique Name and choose its Provider Type.

The page also shows:

  • Encryption Status: Mode (the provider actually in use, or Local (env var)), Active Keys, Health, Key Store, Last Rotation, Last Reload;
  • Encryption Keys: the DEKs, the newest marked Primary, the older ones Decrypt;
  • the list of providers with their Status (Active, Connected, Error, Unchecked) and a summary of their configuration (key, mount, slot).

Vault Transit

On the Vault side:

vault secrets enable transit
vault write -f transit/keys/zcert-kek type=aes256-gcm96
vault policy write pkifactor-kms - <<'EOF'
path "transit/encrypt/zcert-kek" { capabilities = ["update"] }
path "transit/decrypt/zcert-kek" { capabilities = ["update"] }
path "transit/rewrap/zcert-kek"  { capabilities = ["update"] }
path "transit/keys/zcert-kek"    { capabilities = ["read"] }
path "sys/health"                { capabilities = ["read"] }
EOF
vault auth enable approle
vault write auth/approle/role/pkifactor-kms token_policies=pkifactor-kms \
  token_ttl=1h token_max_ttl=4h secret_id_bound_cidrs=<PKIFactor address>/32 token_bound_cidrs=<PKIFactor address>/32
vault read auth/approle/role/pkifactor-kms/role-id
vault write -f auth/approle/role/pkifactor-kms/secret-id

In PKIFactor, provider type HashiCorp Vault Transit:

Field Content
Vault Address Full URL of the Vault server, with its port
Transit Mount Path Mount of the Transit engine (transit)
Encryption Key Name The Transit key used for wrapping (zcert-kek)
Key Store Path Location of the wrapped DEKs
AppRole Role ID / AppRole Secret ID The AppRole credentials (recommended)
Token (alternative) A Vault token, instead of the AppRole
CA Certificate (PEM) The CA that signed the Vault server certificate, when it is private. Empty: the system trust store (or VAULT_CA_CERT) is used
Skip TLS verification Laboratories only: disables the check of the Vault server certificate

When its Vault token expires or is rejected, PKIFactor logs in again with the AppRole by itself.

HSM (PKCS#11)

The vendor PKCS#11 library is not part of the images: it is mounted read-only into the backend and worker containers (on the appliance, /opt/pkifactor/hsm-libs/<vendor>; on Kubernetes, kms.pkcs11.librarySecret). On the appliance, put the library (and its configuration file, if the vendor needs one) under /opt/pkifactor/hsm-libs/<vendor>/, declare the mount in /opt/pkifactor/docker-compose.override.yml, and point KEK_PKCS11_LIB in .env at the path inside the container:

/opt/pkifactor/docker-compose.override.yml
services:
  backend:
    volumes:
      - ./hsm-libs/utimaco-r3:/opt/utimaco-r3:ro
  backend_worker:
    volumes:
      - ./hsm-libs/utimaco-r3:/opt/utimaco-r3:ro
/opt/pkifactor/.env
KEK_PKCS11_LIB=/opt/utimaco-r3/libcs_pkcs11_R3.so

The user PIN is never written in .env: it lives in /opt/pkifactor/secrets/kek_pkcs11_pin. This layout is the one used by the test appliance with a Utimaco HSM.

In PKIFactor, provider type PKCS#11 HSM. The Quick Setup buttons prefill the library path, configuration file and authentication key file for SoftHSM2, Utimaco R2, Utimaco R3 (PQC), Thales Luna and AWS CloudHSM; check the paths against your installation.

Field Content
PKCS#11 Library Path Full path of the vendor library inside the container
HSM Slot Slot number. Enter the library path, then click Detect Slots to list the slots of the HSM
HSM PIN The slot's user PIN, stored encrypted
KEK Label Label of the AES-256 wrapping key in the HSM
Provider Config File Vendor configuration file, when the vendor needs one
Auth Key File HSM authentication key file (Utimaco)

When no key carries the label, PKIFactor creates the AES-256 key-encryption key in that slot, as non-extractable. DEKs are wrapped with AES Key Wrap (RFC 3394 or RFC 5649, whichever the HSM offers).

Use a dedicated slot

Give PKIFactor its own slot and user PIN. Never point it at a slot used by an authority or another product.

Test, activate, migrate

Action Effect
Test Checks that the provider answers: Vault health endpoint, or a session opened on the HSM slot. The status becomes Connected or Error
Edit Changes the name or the configuration. A secret shown masked and left unchanged keeps its stored value
Activate Marks the provider as the one to use
Migrate Re-wraps every DEK with this provider (from local mode, or from another provider), then activates it and reloads the keys. The provider must pass its health check first. Existing data stays readable throughout: only the wrapping of the DEKs changes
Rotate Key Creates a new DEK, wrapped by the active provider, used for new data. Not available in local mode
Reload Keys Re-reads the DEK store and the provider, for example after a KMS outage
Delete Removes a provider that is not active

Every action is recorded in the audit trail. The page warns when the provider marked active differs from the one actually used by the running backend (Configuration drift): the running backend follows the KEK_* variables described below, so update them to match.

Rotation does not re-encrypt existing data: older DEKs stay available to decrypt it. To re-wrap the DEKs after rotating the key in Vault or the HSM, use the rewrap command below.

Where the credentials of the KMS are kept

The secret that unlocks the KMS (Vault secret ID, HSM PIN) is a file, never an environment variable: /opt/pkifactor/secrets/kek_vault_secret_id or kek_pkcs11_pin on the appliance, a dedicated Kubernetes Secret mounted as files on OpenShift. It is mounted only into the services that need it (backend, worker, ACME and EST gateways).

Variable Meaning
KEK_PROVIDER vault-transit or pkcs11-hsm
KEK_KEY_NAME Transit key name or HSM key label (zcert-kek)
KEK_VAULT_ADDR, KEK_VAULT_MOUNT, KEK_VAULT_SKIP_VERIFY Vault address, Transit mount, TLS verification (never skip in production)
KEK_VAULT_ROLE_ID_FILE, KEK_VAULT_SECRET_ID_FILE, KEK_VAULT_TOKEN_FILE Files holding the Vault credentials
KEK_PKCS11_LIB, KEK_PKCS11_PROVIDER_CONFIG, KEK_PKCS11_SLOT, KEK_PKCS11_PIN_FILE HSM library, vendor configuration, slot, PIN file
KEK_DEK_STORE_PATH Location of the wrapped DEKs (/app/data/keys/dek_store.json)
KEK_CACHE_KEY_FILE Key of the encrypted DEK cache
KMS_RETRY_ATTEMPTS, KMS_RETRY_DELAY Start-up retries (3, 30 seconds)
Via the API
# Routes without /api prefix: called under /api/v1/
curl -s -b jar https://clm.example.com/api/v1/admin/kms-providers
curl -s -b jar https://clm.example.com/api/v1/admin/kms-providers/status
curl -s -b jar -X POST https://clm.example.com/api/v1/admin/kms-providers/3/test
curl -s -b jar -X POST https://clm.example.com/api/v1/admin/kms-providers/3/migrate
curl -s -b jar https://clm.example.com/api/v1/admin/settings/kms/status
curl -s -b jar -X POST https://clm.example.com/api/v1/admin/settings/kms/rotate
curl -s -b jar -X POST https://clm.example.com/api/v1/admin/settings/kms/reload

Command line

From the backend container (appliance: docker exec -it pkifactor_backend ...):

python -m backend.utils.kms_admin status            # provider, KEK, DEKs
python -m backend.utils.kms_admin init              # first DEK, wrapped with the configured provider
python -m backend.utils.kms_admin migrate-from-env  # wrap the local key with the configured provider
python -m backend.utils.kms_admin rotate            # new DEK
python -m backend.utils.kms_admin rewrap            # re-wrap the DEKs with the latest KEK version

Backups

A backup contains the wrapped DEKs, not the KEK. Restoring it on another host needs access to the same Vault Transit key or HSM key, and the credentials to reach it. See Backup and restore.