Skip to content

Secrets and key management

This page lists the secrets PKIFactor depends on, how data is encrypted at rest, where private keys of certificates are kept, and what is needed to decrypt a backup. The configuration of Vault Transit and HSMs is in Encryption at rest, KMS and HSM.

Envelope encryption

Sensitive values in the database (credentials of authorities, devices and directories, API keys, gateway secrets, private keys generated by PKIFactor, PKCS#12 passwords, application backups) are encrypted with data keys (DEKs). The DEKs are themselves encrypted (wrapped) by a key-encryption key (KEK) held by a KMS or an HSM.

Layer What it is Where it lives
KEK Vault Transit key, or non-extractable AES-256 key in an HSM slot In the KMS or HSM only
DEKs Fernet keys (AES-128-CBC with HMAC-SHA256) Wrapped, in data/keys/dek_store.json; in clear only in the memory of the running services
Encrypted values Fernet tokens Database columns
  • At start-up the backend asks the KMS to unwrap every DEK and keeps them in memory. The newest DEK encrypts new values; the older ones still decrypt existing values.
  • The provider recorded in dek_store.json decides which KMS unwraps the keys; KEK_PROVIDER is only used before the store exists.
  • With an HSM, wrapping uses the AES key wrap mechanisms of PKCS#11 (with padding, falling back to the variants the HSM supports).
  • The ACME and EST gateways unwrap the DEKs themselves (read-only access to data/keys) to read their own secrets; they hold the KMS credentials for that reason.

Modes

Mode Root of the encryption Tested
none The data key is the db_encryption_key secret file itself Evaluation and laboratories
vault-transit Transit key in HashiCorp Vault Yes, including migration from local mode and a Vault outage
pkcs11-hsm AES-256 key in an HSM slot Yes, with a Utimaco CryptoServer (and SoftHSM)

Encrypted key cache and fail-closed start

The backend keeps a copy of the unwrapped DEKs in data/keys/dek_cache.json (mode 600), encrypted with its own key, kek_cache_key, never with the data key. The cache lets PKIFactor start in degraded mode when the KMS is down. Without the cache key there is no cache.

If the KMS does not answer after its retries and there is no usable cache, the backend refuses to start. Falling back to the local key exists only behind KMS_ALLOW_ENV_FALLBACK=true, for laboratories, and even then encrypted writes are blocked.

After a migration from local mode

Migrating from none to a KMS wraps the existing local key as the first DEK, so existing data stays readable. That key is still present in the db_encryption_key file, which the backend requires at every start. Data encrypted with the first DEK is therefore not protected by the KMS alone. After the migration, use Rotate Key so that new values are encrypted with a DEK that exists only in wrapped form. PKIFactor does not re-encrypt existing values with the new DEK: the earlier DEKs remain needed to read them.

Secret files

On the appliance, secrets are files in /opt/pkifactor/secrets/ (directory mode 750, owner root:pkifactor), mounted into containers under /run/secrets/. Each service mounts only the files it uses.

File Content Mounted into
db_encryption_key Local data key (mode none); also required at start-up in KMS modes Backend, worker, ACME, EST, CMP
jwt_secret_key Signing key of session tokens Backend, worker
jwt_secret_previous Previous signing key during a rotation (empty otherwise) Backend, worker
session_secret_key Key of the interface session cookie Frontend
kek_cache_key Key of the encrypted DEK cache Backend, worker, ACME, EST
kek_vault_role_id, kek_vault_secret_id Vault AppRole credentials (empty if unused) Backend, worker, ACME, EST
kek_vault_token Vault token, as an alternative to AppRole (empty if unused) Backend, worker
kek_pkcs11_pin User PIN of the HSM slot (empty if unused) Backend, worker, ACME, EST

The setup wizard generates the data key, the two token keys and the cache key, and creates the other files empty. Unused files must still exist: Docker refuses to start a service whose secret file is missing (pkifactor-ctl recreates missing files before each start).

How a service reads a secret

For a secret NAME, the service reads the file named by NAME_FILE; if that file is empty, it falls back to the environment variable NAME. This is how unused KMS files stay harmless.

Keep secret values out of .env

The gateways give an environment variable priority over the mounted file, and the backend reads JWT_SECRET (environment) before the jwt_secret_key file. A value left in .env can silently replace the file. .env should hold only the database passwords and non-secret settings.

The database passwords (DB_PASSWORD for the administrator account, DB_APP_PASSWORD for clm_app) are in /opt/pkifactor/.env (mode 640).

On Kubernetes and OpenShift the same secrets are Kubernetes Secrets mounted as files (OpenShift).

Rotation

Secret How Effect
Session token key Procedure below Sessions stay valid during the change
Data key (DEK) Rotate Key in Infrastructure > Encryption, or python -m backend.utils.kms_admin rotate New values use the new DEK; existing values keep theirs
Vault Transit KEK vault write -f transit/keys/zcert-kek/rotate, then python -m backend.utils.kms_admin rewrap Vault re-wraps the DEKs without returning them in clear
Vault AppRole secret ID, HSM PIN Issue the new value on the KMS side, write it to the file (and to the provider in Infrastructure > Encryption if it was configured there), then stop and start the platform

Rotate the session token key

Without interrupting sessions:

  1. Copy the content of secrets/jwt_secret_key into secrets/jwt_secret_previous.
  2. Write a new random value of at least 32 characters into secrets/jwt_secret_key:
    python3 -c "import secrets; print(secrets.token_urlsafe(64))"
    
  3. Stop and start the platform, so that the containers are recreated and read the new files: sudo pkifactor-ctl stop && sudo pkifactor-ctl start. New tokens are signed with the new key; tokens signed with the previous key are still accepted.
  4. After 30 minutes (the token lifetime), empty secrets/jwt_secret_previous (keep the file) and stop and start again.

In high availability, apply the same files on every node: the nodes share the key.

Private keys of certificates

Case What PKIFactor holds
The requester supplies a CSR No private key: it never leaves the requester
PKIFactor generates the key (request without CSR) The private key, encrypted with the DEKs, in the certificate record, together with the PKCS#12 password chosen at request time, also encrypted
Enrolment gateways The client's key stays on the client, except for EST server-side key generation

The PKCS#12 file is built in memory at the moment of download, never stored. Downloading it:

  • requires the right to act on the certificate (owner, team, or a role scoped to the organization);
  • is possible for a limited time after issuance: p12_ttl_hours, 48 hours by default; an empty value or 0 removes the limit;
  • is limited to 5 attempts per minute and recorded as CERT_PRIVATE_KEY_EXPORTED in the audit trail.

The encrypted private key stays in the record after the download window, because automatic deployment to devices uses it.

Backups and what decrypts them

Backup Encrypted by Needed to decrypt
Application backup (Encrypt backups on) The current DEK The DEKs (dek_store.json) and access to the same Vault Transit key or HSM key with valid credentials. In mode none, the db_encryption_key file
Appliance archive (pkifactor-ctl backup) gpg AES-256, passphrase in /etc/pkifactor/backup.pass (outside the archive) The passphrase: the archive contains the database dump, secrets/ and data/keys/

The appliance archive bypasses the KMS

The archive holds the encrypted DEK cache together with its key (kek_cache_key), and the local data key. Whoever holds the archive can read every encrypted value without access to Vault or the HSM. Encrypt it before it leaves the host and store it with the same care as the KMS credentials.

See Backup and restore.

The licence signing key

Licences are signed by ZetaCert with an ECDSA P-256 key that never leaves ZetaCert. The appliance holds only the corresponding public key, embedded in the product, and verifies licences locally without any network call. The signing tool is not part of the images. See License.