HSM Setup and Configuration Runbook
Audience: operators and security engineers wiring a Hardware Security Module (HSM) into a Stella Ops deployment.
This runbook provides step-by-step procedures for configuring HSM integration with Stella Ops, covering SoftHSM2 (development), YubiHSM 2, and any other PKCS#11-compliant token. It also documents the supported algorithms, plugin configuration surface, verification steps, and key rotation against an HSM-resident key.
Overview
Stella Ops integrates with HSMs through the PKCS#11 cryptography plugin (StellaOps.Cryptography.Plugin.Hsm, plugin id com.stellaops.crypto.hsm). The plugin loads a vendor PKCS#11 shared library and performs sign / verify / encrypt / decrypt operations against keys that already live in the token. Any PKCS#11-compliant HSM is supported, including:
- YubiHSM 2
- Thales Luna Network HSM
- SoftHSM2 (development/testing — see SoftHSM2 Setup)
On-prem / sovereign posture. The HSM plugin is PKCS#11-only and makes no network calls to cloud-managed key services. Cloud-managed HSM/KMS backends (AWS CloudHSM/KMS, Azure Key Vault, Google Cloud KMS) are not part of the plugin and are not a supported default. (A legacy Doctor connectivity check,
check.crypto.hsm/HsmConnectivityCheck, still contains cloud-type branches, but these are diagnostic-only stubs, not a signing path.) For centralized secret/KEK material, HashiCorp Vault is the flagship backend.
Supported algorithms
The plugin advertises the following algorithm identifiers (operations route only to these; the HSM- prefix is required):
| Operation | Algorithm IDs |
|---|---|
| Sign / Verify (RSA) | HSM-RSA-SHA256, HSM-RSA-SHA384, HSM-RSA-SHA512, HSM-RSA-PSS-SHA256 |
| Sign / Verify (ECDSA) | HSM-ECDSA-P256, HSM-ECDSA-P384 |
| Encrypt / Decrypt (AES-GCM) | HSM-AES-128-GCM, HSM-AES-256-GCM |
Hashing (HSM-*-SHA*) is computed locally in-process, not on the token.
Prerequisites
Hardware Requirements
| Component | Requirement |
|---|---|
| HSM Device | PKCS#11 compatible |
| Network | HSM accessible from Stella Ops services |
| Backup | Secondary HSM for key backup |
Software Requirements
# PKCS#11 library for your HSM
# Example for SoftHSM2 (development)
apt-get install softhsm2 opensc
# Verify installation
softhsm2-util --version
pkcs11-tool --version
SoftHSM2 Setup (Development)
See softhsm2-test-environment.md for a focused test environment setup.
Step 1: Initialize SoftHSM
# Create token directory
mkdir -p /var/lib/softhsm/tokens
chmod 700 /var/lib/softhsm/tokens
# Initialize token
softhsm2-util --init-token \
--slot 0 \
--label "StellaOps-Dev" \
--so-pin 12345678 \
--pin 87654321
# Verify token
softhsm2-util --show-slots
Step 2: Generate Signing Key
# Generate ECDSA P-256 key
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 87654321 \
--keypairgen \
--key-type EC:prime256v1 \
--id 01 \
--label "stellaops-signing-2026"
# List keys
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 87654321 \
--list-objects
Step 3: Export Public Key
# Export public key for distribution
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 87654321 \
--read-object \
--type pubkey \
--id 01 \
--output-file stellaops-signing-2026.pub.der
# Convert to PEM
openssl ec -pubin -inform DER \
-in stellaops-signing-2026.pub.der \
-outform PEM \
-out stellaops-signing-2026.pub.pem
YubiHSM 2 Setup
Step 1: Install YubiHSM SDK
# Download YubiHSM SDK
wget https://developers.yubico.com/YubiHSM2/Releases/yubihsm2-sdk-2023.01-ubuntu2204-amd64.tar.gz
tar xzf yubihsm2-sdk-*.tar.gz
cd yubihsm2-sdk
sudo ./install.sh
# Start connector
sudo systemctl enable yubihsm-connector
sudo systemctl start yubihsm-connector
Step 2: Initialize YubiHSM
# Connect to YubiHSM shell
yubihsm-shell
# Authenticate with default auth key
connect
session open 1 password
# Create authentication key for Stella Ops
generate authkey 0 100 "StellaOps-Auth" 1 generate-asymmetric-key:sign-ecdsa:delete-asymmetric-key
# Generate signing key
generate asymmetric 0 200 "StellaOps-Signing" 1 sign-ecdsa ecp256
# Export public key
get public key 0 200 stellaops-yubihsm.pub
session close 0
quit
Step 3: Configure PKCS#11
# Create PKCS#11 configuration
cat > /etc/yubihsm_pkcs11.conf <<EOF
connector = http://127.0.0.1:12345
EOF
# Test PKCS#11 access
pkcs11-tool --module /usr/lib/libyubihsm_pkcs11.so \
--list-slots
Stella Ops Configuration
The HSM plugin binds its configuration block directly to HsmOptions (context.Configuration.Bind<HsmOptions>()). The keys below are the actual properties of HsmOptions / the plugin configSchema — there is no signing.provider discriminator and no keyId / keyLabel / maxSessions / sessionIdleTimeoutSeconds / maxRetries / retryDelayMs settings. Keys are referenced by label (CKA_LABEL) or hex id (CKA_ID) through the keyIds allow-list; the plugin does not split a single key into separate id/label fields.
PKCS#11 (production) configuration
# Bound to HsmOptions for the com.stellaops.crypto.hsm plugin.
libraryPath: "/usr/lib/softhsm/libsofthsm2.so" # or /usr/lib/libyubihsm_pkcs11.so
slotId: 0
tokenLabel: "StellaOps-Dev" # verified against the slot's token label
pin: "${HSM_PIN}" # inject from a secret source
keyIds: "stellaops-signing-2026" # comma/semicolon/pipe-delimited allow-list
# Optional operation-scoped allow-lists (any of these also populate the allow-list):
# signingKeyIds / verificationKeyIds / encryptionKeyIds / decryptionKeyIds
# signingKeyId / verificationKeyId / encryptionKeyId / decryptionKeyId (single-value aliases)
connectionTimeoutSeconds: 30 # default 30
readOnlySession: true # default true; no key generation/modification through the plugin
Outside explicit simulation mode the plugin fails closed at startup unless libraryPath, slotId, tokenLabel, pin, and at least one configured key id are all present. A token-label mismatch on the selected slot also fails the connect.
Key lifecycle is out of band.
readOnlySessiondefaults totrueand the plugin offers no keypair-generation API — keys are created in the token with vendor tooling (e.g.pkcs11-tool,softhsm2-util,yubihsm-shell) and the plugin only references them. Session pool sizing (MinSessionPoolSize,MaxSessionPoolSize,FailureThreshold) and failover slots (FailoverSlotIds) are internalPkcs11HsmClientOptionsdefaults and are not exposed throughHsmOptions.
Simulation mode (local harness only)
A built-in in-process simulator is available for tests and local development. It is gated: allowSimulation: true is honoured only when the runtime environment is Development or Testing (resolved from runtimeEnvironment, then DOTNET_ENVIRONMENT, then ASPNETCORE_ENVIRONMENT, defaulting to Production). In Production the same flag throws at startup. The simulator generates ephemeral in-memory RSA/AES keys and must never be used for release evidence.
# Test/dev harness only — fails closed in Production.
allowSimulation: true
runtimeEnvironment: "Testing"
keyIds: "stellaops-hsm-sim-signing,stellaops-hsm-sim-encryption"
Environment Variables
# Set HSM PIN securely (referenced as ${HSM_PIN} above)
export HSM_PIN="87654321"
Host Secret Store
Store HSM PIN material in the approved host secret store (HashiCorp Vault is the flagship backend) or a Compose environment file. The PIN is held in-process as a UTF-8 byte buffer and zeroized on disconnect (CryptographicOperations.ZeroMemory). Kubernetes Secrets are not supported deployment artifacts for Stella Ops, and the HSM plugin does not read PINs from cloud-managed secret services.
Verification
Step 1: Connectivity Check
# Run the HSM/PKCS#11 connectivity doctor check.
stella doctor --check check.crypto.hsm
The check.crypto.hsm check (HsmConnectivityCheck) only runs when HSM is enabled in config (Cryptography:Hsm:Enabled or Cryptography:EnableHsm set to true). For PKCS#11 it loads the library, enumerates slots with a token present, confirms the configured slot exists, and — if a PIN is configured — performs a login/logout. On success it reports Pass("HSM is accessible") with evidence such as Provider=PKCS#11, Library, SlotCount, TokenLabel, TokenModel, and Authenticated. It does not perform a sign/verify round-trip or assert a specific key is present; missing config or connectivity yields a Warn, not a hard fail.
Note: two distinct Doctor checks both register the id
check.crypto.hsm. The one wired into the Cryptography plugin isHsmConnectivityCheck(StellaOps.Doctor.Plugins.Cryptography, described above) whose remediation is plain manual-step text. A second implementation,HsmPkcs11AvailabilityCheck(StellaOps.Doctor.Plugin.Crypto), uses simulated slot/token probes and emits CLI hints such asstella crypto config set --hsm-module .... There is nocrypto configsubcommand today (see the CLI surface below); treat those hints as placeholders and configure viaHsmOptionsinstead.
Step 2: Sign / verify round-trip
There is no stella sign test --message ... command. Use the real crypto CLI verbs, which route to the configured provider, to exercise sign then verify:
# Sign an artifact, then verify the detached signature.
stella crypto sign --input ./artifact.bin --key-id stellaops-signing-2026 --output ./artifact.bin.sig
stella crypto verify --input ./artifact.bin --signature ./artifact.bin.sig --key-id stellaops-signing-2026
Step 3: Library / plugin verification (test suite)
There is no stella test integration --filter "HSM*" command. The HSM plugin’s behavioral coverage lives in the cryptography test project; run it directly:
# PKCS#11 fail-closed + simulation-mode harness tests.
dotnet test "src/Cryptography/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj" \
--filter "FullyQualifiedName~Hsm"
Key Rotation
Step 1: Generate New Key
# Generate new key in HSM
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin ${HSM_PIN} \
--keypairgen \
--key-type EC:prime256v1 \
--id 02 \
--label "stellaops-signing-2027"
Step 2: Allow the new key
NOTE: there is no
stella key add/stella key revokeHSM key-lifecycle CLI. (stella crypto keys generate/list/import/exportexist but are largely demonstration stubs and do not manage HSM-resident keys.) Key trust for the plugin is governed entirely by thekeyIdsallow-list inHsmOptions.
To transition, add the new key’s id/label to the allow-list so the plugin will accept operations against it:
# Allow both keys during the transition window (comma/semicolon/pipe-delimited).
keyIds: "stellaops-signing-2027,stellaops-signing-2026"
Step 3: Transition Period
Once both keys are in the allow-list, callers select the active key per request via --key-id (CLI) or CryptoSignOptions.KeyId. Cut producers over to stellaops-signing-2027 while consumers can still verify signatures produced with stellaops-signing-2026.
Step 4: Retire the Old Key
# After the transition window, drop the old key from the allow-list.
keyIds: "stellaops-signing-2027"
Then remove the retired key object from the token with vendor tooling (e.g. pkcs11-tool --delete-object / softhsm2-util). The plugin will reject any request whose --key-id is not in the allow-list with HSM key id '<id>' is not declared in configured key identifiers.
Troubleshooting
Common Issues
| Issue | Symptom | Resolution |
|---|---|---|
| Library not found | Slot {n} not found or has no token / PKCS#11 library not found | Verify libraryPath in config; confirm the .so/.dll exists |
| Token label mismatch | HSM token label mismatch for slot {n}. Expected '...', found '...' | Set tokenLabel to match the slot’s actual token label |
| Key not found | Private key not found: <id> (sign) / Key not found for verification: <id> (verify) / Secret key not found: <id> (encrypt/decrypt) | Verify the key’s CKA_LABEL or CKA_ID with pkcs11-tool --list-objects |
| Key not allowed | HSM key id '<id>' is not declared in configured key identifiers for <mode> mode. | Add the id/label to keyIds (or an operation-scoped allow-list) |
| Wrong key usage | Key attribute CKA_SIGN is False, expected True for signing | Use a key whose CKA_SIGN/CKA_VERIFY/CKA_ENCRYPT/CKA_DECRYPT matches the operation |
| PIN incorrect | CKR_PIN_INCORRECT | Check the HSM_PIN/pin value injected at startup |
| No healthy slots | No healthy HSM slots available | Inspect HSM/token health; a slot is marked unhealthy after the failure threshold |
Debug Logging
# Enable HSM plugin debug logging (note the .Plugin. segment in the namespace).
logging:
levels:
StellaOps.Cryptography.Plugin.Hsm: Debug
Session Recovery
The plugin manages an internal session pool and marks a slot unhealthy after consecutive failures, falling back to any configured failover slot. If all slots are unhealthy, restart the owning service so the plugin reconnects:
# Restart through Docker Compose or the host service manager.
docker compose restart stellaops-signer
Security Best Practices
PIN Management
- Never hardcode PINs in configuration files
- Inject PINs from the host secret store (HashiCorp Vault is the flagship backend) or a Compose environment file — not from cloud-managed secret services
- Rotate PINs periodically
Key Backup
- Configure HSM key backup/replication
- Test key recovery procedures regularly
- Document recovery process
Access Control
- Limit HSM access to required services only
- Use separate authentication keys per service
- Audit HSM access logs
Network Security
- Use TLS for network HSM connections
- Firewall HSM to authorized hosts only
- Monitor for unauthorized access attempts
