The HSM Work project supports three approaches to key management: direct PKCS#11 operations via pkcs11-tool, the hls-hsm C++ class API, and the pkcs11-daemon vault backed by SQLite. Choose the approach that fits your use case.
PKCS#11 key management
The PKCS#11 interface is exposed through SoftHSM2 and accessed with pkcs11-tool. Before generating keys you must initialize a token.
Initialize a token
softhsm2-util --init-token --free --label FirmwareHSM
softhsm2-util --show-slots
--free selects the first available slot. After initialization, note the slot ID printed by --show-slots — you will use it in all subsequent commands as <SLOT_ID>.
Generate key pairs
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so \
--slot <SLOT_ID> --login --pin 1234 \
--keypairgen --key-type rsa:2048 --id 10 --label FirmwareKey
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so \
--slot <SLOT_ID> --login --pin 1234 \
--keypairgen --key-type EC:prime256v1 --id 10 --label FirmwareKey
List objects
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so \
--slot <SLOT_ID> --login --pin 1234 --list-objects --verbose
Key IDs and labels
--id is a numeric byte identifier (e.g., 10 in hex is 0x0a) used to reference a key object programmatically. --label is a human-readable name associated with the object. Both values must match when signing or reading an object:
--id 10 identifies the key with ID 0x0a
--label FirmwareKey is descriptive and used for display
When exporting or signing, reference the key by --id. Labels are not guaranteed to be unique across tokens.
Delete a token
To remove a token entirely and re-initialize:
softhsm2-util --delete-token --token FirmwareHSM
softhsm2-util --init-token --free --label FirmwareHSM
Deleting a token destroys all keys stored in it. This operation is irreversible.
hls-hsm C++ API
The hls-hsm library (hls-hsm/include/hsm.h) provides a C++ interface for key generation and retrieval.
Supported key types
enum class KeyType { RSA, AES };
Initialize
Generate keys
// RSA 2048-bit key — returns key ID string
std::string rsa_id = hsm.generate_rsa_key(2048);
// AES 256-bit key — returns key ID string
std::string aes_id = hsm.generate_aes_key(256);
Retrieve a key
Key k = hsm.get_key(rsa_id);
// k.id — string identifier
// k.type — KeyType::RSA or KeyType::AES
// k.data — raw key bytes
Key naming conventions
Key IDs are derived from the algorithm and bit size:
| Key type | ID format | Example |
|---|
| RSA | RSA_<bits> | RSA_2048 |
| AES | AES_<bits> | AES_256 |
Random bytes
std::vector<uint8_t> rand = hsm.get_random_bytes(32);
Daemon vault
The pkcs11-daemon manages keys in a local SQLite database (vault.db) via the Vault class.
Schema
CREATE TABLE IF NOT EXISTS keys(
label TEXT PRIMARY KEY,
type TEXT,
data TEXT
)
Initialization and default keys
At startup the daemon generates and stores two default keys:
Vault vault("vault.db");
vault.init();
// RSA key generated with OpenSSL
std::string rsa = Crypto::generate_rsa(); // RSA 2048, PEM
vault.store_key("default-rsa", "rsa", rsa);
// Post-quantum key (stub — see post-quantum guide)
std::string pq = Crypto::pqc_generate();
vault.store_key("default-pq", "dilithium", pq);
Storing and retrieving keys
// Store a key
vault.store_key("my-key", "rsa", pem_string);
// Retrieve a key by label
std::string data = vault.get_key("my-key");
The label column is the primary key, so storing a key with an existing label overwrites it.
Crypto::pqc_generate() currently returns an empty string. The default-pq entry is created in the vault but holds no key material until the liboqs Dilithium integration is complete. See the post-quantum cryptography guide.