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.jsondecides which KMS unwraps the keys;KEK_PROVIDERis 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:
- Copy the content of
secrets/jwt_secret_keyintosecrets/jwt_secret_previous. - Write a new random value of at least 32 characters into
secrets/jwt_secret_key:python3 -c "import secrets; print(secrets.token_urlsafe(64))" - 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. - 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_EXPORTEDin 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.