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:

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):

OperationAlgorithm 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

ComponentRequirement
HSM DevicePKCS#11 compatible
NetworkHSM accessible from Stella Ops services
BackupSecondary 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. readOnlySession defaults to true and 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 internal Pkcs11HsmClientOptions defaults and are not exposed through HsmOptions.

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 is HsmConnectivityCheck (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 as stella crypto config set --hsm-module .... There is no crypto config subcommand today (see the CLI surface below); treat those hints as placeholders and configure via HsmOptions instead.

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 revoke HSM key-lifecycle CLI. (stella crypto keys generate/list/import/export exist but are largely demonstration stubs and do not manage HSM-resident keys.) Key trust for the plugin is governed entirely by the keyIds allow-list in HsmOptions.

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

IssueSymptomResolution
Library not foundSlot {n} not found or has no token / PKCS#11 library not foundVerify libraryPath in config; confirm the .so/.dll exists
Token label mismatchHSM token label mismatch for slot {n}. Expected '...', found '...'Set tokenLabel to match the slot’s actual token label
Key not foundPrivate 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 allowedHSM 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 usageKey attribute CKA_SIGN is False, expected True for signingUse a key whose CKA_SIGN/CKA_VERIFY/CKA_ENCRYPT/CKA_DECRYPT matches the operation
PIN incorrectCKR_PIN_INCORRECTCheck the HSM_PIN/pin value injected at startup
No healthy slotsNo healthy HSM slots availableInspect 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

  1. 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
  2. Key Backup

    • Configure HSM key backup/replication
    • Test key recovery procedures regularly
    • Document recovery process
  3. Access Control

    • Limit HSM access to required services only
    • Use separate authentication keys per service
    • Audit HSM access logs
  4. Network Security

    • Use TLS for network HSM connections
    • Firewall HSM to authorized hosts only
    • Monitor for unauthorized access attempts