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¶

HsmModel¶

Attribute

Type

Required

Description

enabled

boolean

Read-only. Whether HSM master-key mode is active.

module

string

PKCS#11 module path relative to the uploaded bundle. Must not start with a slash.

token_label

string

PKCS#11 token label.

key_label

string

Read-only. Label of the KEK generated during enrollment.

env

object

Module environment. A map sent on PATCH replaces the whole map.

pin

string

Token PIN. Write-only and protected - never returned by GET. An empty string inherits the currently active PIN.

test_only

boolean

Dry run: validate the configuration without committing it. See the note below.

HsmStatusModel¶

Attribute

Type

Required

Description

enabled

boolean

Read-only. Whether HSM master-key mode is active.

healthy

boolean

Read-only. The whole chain works: module loaded, token found, login accepted, exactly one KEK present.

error

string

Read-only. Diagnosis of the failing step. null when healthy.

token_label

string

Read-only. Token label reported by the HSM.

token_manufacturer

string

Read-only. Token manufacturer.

token_model

string

Read-only. Token model.

token_serial

string

Read-only. Token serial number.

firmware_version

string

Read-only. Token firmware version.

pin_locked

boolean

Read-only. Login is blocked until the PIN is reset.

pin_expired

boolean

Read-only. PIN is expired; cryptographic operations fail until it is changed.

fingerprint

string

Read-only. KEK fingerprint, comparable across cluster nodes.

HsmBundleModel¶

Attribute

Type

Required

Description

modules

string-array

Read-only. PKCS#11 module (.so) paths in the active bundle.

filename

string

Read-only. Original upload filename of the active bundle.

uploaded_at

datetime

Read-only. RFC3339 time the active bundle was uploaded.

Retrieve Available Attributes¶

Request

Method

GET

Path

/api/v2/objspec/hsm, /api/v2/objspec/hsm_status, /api/v2/objspec/hsm_bundle

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

GET

Path

/api/v2/hsm

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

GET

Path

/api/v2/hsm/status

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

PATCH

Path

/api/v2/hsm

Body

HsmModel

PATCH /api/v2/hsm

This endpoint both validates and enrolls, and it answers with an HsmStatusModel rather than a plain result:

  • test_only set to true is a dry run. The configuration is exercised against the token and the outcome reported, but the live configuration is left untouched. The enabled field of the result describes the staged configuration, not the running one, so it reads false.

  • test_only set to false commits: it enrolls the configuration, generates a KEK on the token, activates any bundle staged with POST /api/v2/hsm/bundle and 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

GET

Path

/api/v2/hsm/bundle/active

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

POST

Path

/api/v2/hsm/bundle

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

POST

Path

/api/v2/hsm/rotate-kek

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

DELETE

Path

/api/v2/hsm

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"
}