Hardware Security Module 🆕¶
Fudo Enterprise can keep the key that encrypts its master key (the KEK) inside a hardware security module instead of on disk. The module is reached through a PKCS#11 library supplied in an uploaded bundle; enrollment generates the KEK on the token and switches the system to HSM master-key mode.
Data Structures¶
Attribute |
Type |
Required |
Description |
|---|---|---|---|
|
boolean |
Read-only. Whether HSM master-key mode is active. |
|
|
string |
PKCS#11 module path relative to the uploaded bundle. Must not start with a slash. |
|
|
string |
PKCS#11 token label. |
|
|
string |
Read-only. Label of the KEK generated during enrollment. |
|
|
object |
Module environment. A map sent on |
|
|
string |
Token PIN. Write-only and protected - never returned by |
|
|
boolean |
Dry run: validate the configuration without committing it. See the note below. |
Attribute |
Type |
Required |
Description |
|---|---|---|---|
|
boolean |
Read-only. Whether HSM master-key mode is active. |
|
|
boolean |
Read-only. The whole chain works: module loaded, token found, login accepted, exactly one KEK present. |
|
|
string |
Read-only. Diagnosis of the failing step. |
|
|
string |
Read-only. Token label reported by the HSM. |
|
|
string |
Read-only. Token manufacturer. |
|
|
string |
Read-only. Token model. |
|
|
string |
Read-only. Token serial number. |
|
|
string |
Read-only. Token firmware version. |
|
|
boolean |
Read-only. Login is blocked until the PIN is reset. |
|
|
boolean |
Read-only. PIN is expired; cryptographic operations fail until it is changed. |
|
|
string |
Read-only. KEK fingerprint, comparable across cluster nodes. |
Attribute |
Type |
Required |
Description |
|---|---|---|---|
|
string-array |
Read-only. PKCS#11 module ( |
|
|
string |
Read-only. Original upload filename of the active bundle. |
|
|
datetime |
Read-only. RFC3339 time the active bundle was uploaded. |
Retrieve Available Attributes¶
Request
Method |
|
Path |
|
GET /api/v2/objspec/hsm
Example Request
curl -s -k -X GET \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/objspec/hsm'
Get the HSM Configuration¶
Request
Method |
|
Path |
|
GET /api/v2/hsm
Example Request
curl -s -k -X GET \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/hsm'
Response
{
"enabled": true,
"module": "module.so",
"token_label": "soft-hsm",
"key_label": "fudo-kek-masterkey",
"env": {
"PKCS11_PROXY_SOCKET": "tcp://10.0.100.37:2345"
},
"result": "success"
}
When HSM mode is off, the configuration is reported empty:
{
"enabled": false,
"module": null,
"token_label": null,
"key_label": null,
"env": {},
"result": "success"
}
Get the HSM Status¶
Request
Method |
|
Path |
|
GET /api/v2/hsm/status
Runs the whole chain - load the module, find the token, log in, count the KEKs - and reports where it stops.
Example Request
curl -s -k -X GET \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/hsm/status'
Response
{
"enabled": true,
"healthy": true,
"error": null,
"token_label": "soft-hsm",
"token_manufacturer": "SoftHSM project",
"token_model": "SoftHSM v2",
"token_serial": "bf5f6cb03dd72192",
"firmware_version": "2.7",
"pin_locked": false,
"pin_expired": false,
"fingerprint": "61cf4f0c9721171647bc56af0f85dd4c851542339c12e3bb765408c70164f0b1",
"result": "success"
}
When the chain breaks, healthy is false, error carries the diagnosis and the token
details are null:
{
"enabled": true,
"healthy": false,
"error": "HSM unavailable: C_Initialize: Function::Initialize: PKCS11 error: Some problem has occurred with the token and/or slot.",
"token_label": null,
"token_manufacturer": null,
"token_model": null,
"token_serial": null,
"firmware_version": null,
"pin_locked": null,
"pin_expired": null,
"fingerprint": null,
"result": "success"
}
Note
Compare fingerprint across cluster nodes to confirm they all reached the same KEK.
Test or Commit an HSM Configuration¶
Request
Method |
|
Path |
|
Body |
|
PATCH /api/v2/hsm
This endpoint both validates and enrolls, and it answers with an HsmStatusModel rather than a
plain result:
test_onlyset totrueis a dry run. The configuration is exercised against the token and the outcome reported, but the live configuration is left untouched. Theenabledfield of the result describes the staged configuration, not the running one, so it readsfalse.test_onlyset tofalsecommits: it enrolls the configuration, generates a KEK on the token, activates any bundle staged withPOST /api/v2/hsm/bundleand switches Fudo Enterprise to HSM master-key mode.
Warning
Test the configuration with test_only set to true before committing it. A dry run is the
only way to find a wrong token label or an unreachable module without changing the running
system.
Example Request
Dry run:
curl -s -k -X PATCH \
-H 'Authorization: <token>' \
-H 'Content-Type: application/json' \
-d '{"module":"module.so","token_label":"soft-hsm","env":{"PKCS11_PROXY_SOCKET":"tcp://10.0.100.37:2345"},"pin":"","test_only":true}' \
'https://10.36.9.188/api/v2/hsm'
Response
{
"enabled": false,
"healthy": true,
"error": null,
"token_label": "soft-hsm",
"token_manufacturer": "SoftHSM project",
"token_model": "SoftHSM v2",
"token_serial": "bf5f6cb03dd72192",
"firmware_version": "2.7",
"pin_locked": false,
"pin_expired": false,
"fingerprint": "61cf4f0c9721171647bc56af0f85dd4c851542339c12e3bb765408c70164f0b1",
"result": "success"
}
A dry run that cannot reach the token reports the reason and changes nothing:
{
"enabled": false,
"healthy": false,
"error": "no token labelled 'does-not-exist'",
"result": "success"
}
Example Request
Commit the configuration:
curl -s -k -X PATCH \
-H 'Authorization: <token>' \
-H 'Content-Type: application/json' \
-d '{"module":"module.so","token_label":"soft-hsm","env":{"PKCS11_PROXY_SOCKET":"tcp://10.0.100.37:2345"},"pin":"<token PIN>","test_only":false}' \
'https://10.36.9.188/api/v2/hsm'
Response
{
"enabled": true,
"healthy": true,
"error": null,
"token_label": "soft-hsm",
"token_manufacturer": "SoftHSM project",
"token_model": "SoftHSM v2",
"token_serial": "bf5f6cb03dd72192",
"firmware_version": "2.7",
"pin_locked": false,
"pin_expired": false,
"fingerprint": null,
"result": "success"
}
Note
fingerprint is null in the enrollment response because the KEK is still being created.
Read GET /api/v2/hsm/status afterwards to obtain it.
Sending pin as an empty string reuses the PIN already in effect, which is what you want when
changing only the module path or the environment of a working configuration.
Get the Active Bundle¶
Request
Method |
|
Path |
|
GET /api/v2/hsm/bundle/active
Example Request
curl -s -k -X GET \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/hsm/bundle/active'
Response
{
"modules": [
"module.so"
],
"filename": "libpkcs11-proxy.so",
"uploaded_at": "2026-08-14T07:54:35.811133402+00:00",
"result": "success"
}
Upload a PKCS#11 Bundle¶
Request
Method |
|
Path |
|
Body |
Raw file bytes |
POST /api/v2/hsm/bundle
The bundle carries the vendor PKCS#11 library and whatever it needs alongside it. The file name is
supplied in the X-Fudo-Filename header and the body is the raw file.
Warning
The upload only stages the bundle - GET /api/v2/hsm/bundle/active keeps reporting the
previous one. The staged bundle becomes active when a configuration is committed with
PATCH /api/v2/hsm and test_only set to false.
Example Request
curl -s -k -X POST \
-H 'Authorization: <token>' \
-H 'X-Fudo-Filename: libpkcs11-proxy.so' \
-H 'Content-Type: application/octet-stream' \
--data-binary '@libpkcs11-proxy.so' \
'https://10.36.9.188/api/v2/hsm/bundle'
Response
The response describes the staged bundle and, unlike other endpoints, carries no result field.
{
"modules": [
"module.so"
],
"filename": "libpkcs11-proxy.so",
"uploaded_at": "2026-08-18T15:55:14.422743725+00:00"
}
Rotate the Key Encryption Key¶
Request
Method |
|
Path |
|
POST /api/v2/hsm/rotate-kek
Generates a new KEK on the token and re-wraps the master key with it. The answer is an
HsmStatusModel carrying the new fingerprint; key_label reported by GET /api/v2/hsm
changes as well.
Example Request
curl -s -k -X POST \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/hsm/rotate-kek'
Response
{
"enabled": true,
"healthy": true,
"error": null,
"token_label": "soft-hsm",
"token_manufacturer": "SoftHSM project",
"token_model": "SoftHSM v2",
"token_serial": "bf5f6cb03dd72192",
"firmware_version": "2.7",
"pin_locked": false,
"pin_expired": false,
"fingerprint": "e774d21cb5d1c478cf4d2cce5d0f1bea1fe1e5e3dca1b9afd1726e2fd82c8611",
"result": "success"
}
Note
In this example the fingerprint moved from
61cf4f0c9721171647bc56af0f85dd4c851542339c12e3bb765408c70164f0b1 to the value above, and
key_label moved from fudo-kek-masterkey to fudo-kek-d2b0e2e7c8b0d11a. On a cluster,
read the status on every node afterwards and confirm the fingerprints match again.
Disable HSM Master-Key Mode¶
Request
Method |
|
Path |
|
DELETE /api/v2/hsm
Switches Fudo Enterprise back to keeping the master key without the module and clears the stored
configuration - module, token_label, key_label and env are all reset.
Warning
The PIN is cleared together with the rest of the configuration. Turning HSM mode back on is a full enrollment: it needs the token PIN again and it generates a new KEK, so the fingerprint after re-enrollment differs from the one before.
Example Request
curl -s -k -X DELETE \
-H 'Authorization: <token>' \
'https://10.36.9.188/api/v2/hsm'
Response
{
"result": "success"
}