A Rust PKCS#11 CLI tool for interfacing with SoftHSM2 in a Docker container. Provides repeatable HSM-style workflows for token management, key generation, and cryptographic operations using PKCS#11.
Vision: A Swiss Army knife for HSM operations - from troubleshooting to benchmarking to automation.
✅ Implemented:
- Token initialization and management
- User PIN setup
- RSA keypair generation (2048/4096 bits)
- ECDSA keypair generation (P-256/P-384 curves)
- Sign/verify operations with SHA-256+RSA (CKM_SHA256_RSA_PKCS)
- Sign/verify operations with ECDSA (CKM_ECDSA with manual SHA-256 hashing)
- RSA encryption/decryption (CKM_RSA_PKCS)
- Public key export in PEM format (RSA & ECDSA)
- Key deletion from HSM
- Symmetric key generation (AES-128/192/256)
- Symmetric encryption/decryption (AES-GCM with IV management)
- Hash operations (SHA-256, SHA-512, SHA-224, SHA-1) - no login required
- HMAC operations (SHA-1/224/256/384/512) - message authentication with hash functions
- CMAC operations (AES-based) - message authentication with block cipher
- Random number generation - secure random bytes from HSM's RNG (C_GenerateRandom)
- Performance benchmarking - comprehensive suite testing all operations with detailed metrics
- Mechanism discovery - list all PKCS#11 mechanisms supported by HSM
- Detailed mechanism capabilities - show encrypt/decrypt/sign/verify/wrap/unwrap capabilities
- Object listing and inspection
- Detailed object attributes - show key type, flags (tok/prv/r/w/loc), capabilities, security attributes
- Key attribute inspection - detailed CKA_* attribute display
- Key fingerprints - SHA-256 hash of public keys for verification
- Module and slot information
- PIN input from stdin (secure, no shell history)
- Comprehensive logging with PKCS#11 function names
- Key wrap/unwrap operations (AES Key Wrap RFC 3394)
- CSR generation (X.509 Certificate Signing Requests for RSA & ECDSA keys)
- JSON output (
--jsonflag) - Machine-parseable output for automation, CI/CD, and fleet auditing - Observability & Analysis:
- observe-core - Event logging with redaction-first design
- observe-cryptoki - Transparent PKCS#11 operation tracing
- analyze - Comprehensive log analysis with percentile stats, per-function metrics, and error summaries
- Troubleshooting commands:
- explain-error - Decode PKCS#11 error codes with context-aware guidance (35+ errors)
- find-key - Fuzzy key search with Levenshtein distance matching
- diff-keys - Side-by-side key comparison with attribute severity indicators
- End-to-end test suite (43 tests including troubleshooting validation)
rust-hsm/
├── crates/
│ ├── rust-hsm-core/ # Reusable PKCS#11 library
│ │ ├── src/
│ │ │ ├── lib.rs # Public API exports
│ │ │ └── pkcs11/ # Core PKCS#11 modules
│ │ └── Cargo.toml
│ ├── rust-hsm-cli/ # CLI application
│ │ ├── src/
│ │ │ ├── main.rs # Command-line interface
│ │ │ ├── cli.rs # Argument parsing
│ │ │ └── config.rs # Configuration handling
│ │ └── Cargo.toml
│ ├── observe-core/ # Observability event logging
│ │ ├── src/
│ │ │ ├── lib.rs # Event schema, redaction, sinks
│ │ │ └── ... # FileSink implementation
│ │ └── Cargo.toml
│ ├── observe-cryptoki/ # PKCS#11 operation tracing
│ │ ├── src/
│ │ │ ├── lib.rs # ObservedPkcs11, ObservedSession
│ │ │ └── ... # Transparent wrappers
│ │ └── Cargo.toml
│ └── rust-hsm-analyze/ # Log analysis engine
│ ├── src/
│ │ ├── lib.rs # Public exports
│ │ ├── parser.rs # JSON Lines parser
│ │ └── analyzer.rs # Statistics engine
│ └── Cargo.toml
└── docker/ # Docker container setup
Single Docker container with both SoftHSM2 and the Rust CLI:
+-------------------+
| rust-hsm-app |
|-------------------|
| - rust-hsm-cli |
| - rust-hsm-core |
| - SoftHSM2 |
| - Token storage |
+-------------------+
│
└─ Volume: tokens:/tokens (persistent)
The rust-hsm-core library can be embedded in other Rust applications:
# Cargo.toml
[dependencies]
rust-hsm-core = { git = "https://github.com/testingapisname/rust-hsm" }use rust_hsm_core::keys;
fn main() -> anyhow::Result<()> {
// Generate an RSA keypair
keys::gen_keypair(
"/usr/lib/softhsm/libsofthsm2.so",
"MY_TOKEN",
"user-pin-123456",
"signing-key",
"rsa",
Some(2048),
false,
)?;
// Sign data
keys::sign(
"/usr/lib/softhsm/libsofthsm2.so",
"MY_TOKEN",
"user-pin-123456",
"signing-key",
"data.txt",
"signature.bin",
)?;
Ok(())
}To avoid repeating --label and other parameters on every command, create a configuration file:
# .rust-hsm.toml or ~/.config/rust-hsm/config.toml
default_token_label = "DEV_TOKEN"
pkcs11_module = "/usr/lib/softhsm/libsofthsm2.so"Supported locations (checked in order):
.rust-hsm.toml(current directory)rust-hsm.toml(current directory)~/.config/rust-hsm/config.toml~/.rust-hsm.toml/app/.rust-hsm.toml(container)- Custom path via
--config /path/to/config.toml
With configuration, commands become shorter:
# Before: must specify --label every time
docker exec rust-hsm-app rust-hsm-cli gen-keypair --label DEV_TOKEN --user-pin 123456 --key-label my-key
# After: --label uses config default
docker exec rust-hsm-app rust-hsm-cli gen-keypair --user-pin 123456 --key-label my-keySee config.example.toml for all options.
# Build and start the container
docker compose up -d --build
# Initialize a token
docker exec rust-hsm-app rust-hsm-cli init-token --label DEV_TOKEN --so-pin 1234
# Set user PIN
docker exec rust-hsm-app rust-hsm-cli init-pin --label DEV_TOKEN --so-pin 1234 --user-pin 123456
# Generate RSA keypair
docker exec rust-hsm-app rust-hsm-cli gen-keypair \
--label DEV_TOKEN --user-pin 123456 \
--key-label signing-key --key-type rsa --bits 2048
# Create test data and sign it
docker exec rust-hsm-app bash -c "echo 'Hello World' > /app/data.txt"
docker exec rust-hsm-app rust-hsm-cli sign \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--input /app/data.txt --output /app/data.sig
# Verify signature
docker exec rust-hsm-app rust-hsm-cli verify \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--input /app/data.txt --signature /app/data.sig
# Encrypt data with RSA public key
docker exec rust-hsm-app bash -c "echo 'Secret data' > /app/secret.txt"
docker exec rust-hsm-app rust-hsm-cli encrypt \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--input /app/secret.txt --output /app/secret.enc
# Decrypt with RSA private key
docker exec rust-hsm-app rust-hsm-cli decrypt \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--input /app/secret.enc --output /app/secret-decrypted.txt
# Export public key (for sharing or external verification)
docker exec rust-hsm-app rust-hsm-cli export-pubkey \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--output /app/signing-key.pub.pem
# Generate AES-256 key for symmetric encryption
docker exec rust-hsm-app rust-hsm-cli gen-symmetric-key \
--label DEV_TOKEN --user-pin 123456 --key-label aes-key --bits 256
# Encrypt larger data with AES-GCM (no RSA size limits)
docker exec rust-hsm-app bash -c "echo 'Large data that would not fit in RSA' > /app/large-data.txt"
docker exec rust-hsm-app rust-hsm-cli encrypt-symmetric \
--label DEV_TOKEN --user-pin 123456 --key-label aes-key \
--input /app/large-data.txt --output /app/large-data.enc
# Decrypt with AES-GCM
docker exec rust-hsm-app rust-hsm-cli decrypt-symmetric \
--label DEV_TOKEN --user-pin 123456 --key-label aes-key \
--input /app/large-data.enc --output /app/large-data-decrypted.txt
# Delete a key when no longer needed
docker exec rust-hsm-app rust-hsm-cli delete-key \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key
# Wrap a key for secure backup or migration (AES Key Wrap - RFC 3394)
# First, generate a Key Encryption Key (KEK)
docker exec rust-hsm-app rust-hsm-cli gen-symmetric-key \
--label DEV_TOKEN --user-pin 123456 --key-label kek --bits 256
# Generate an extractable key to wrap
docker exec rust-hsm-app rust-hsm-cli gen-symmetric-key \
--label DEV_TOKEN --user-pin 123456 --key-label secret-key --bits 256 --extractable
# Wrap the key (output is encrypted key material - 40 bytes for AES-256)
docker exec rust-hsm-app rust-hsm-cli wrap-key \
--label DEV_TOKEN --user-pin 123456 --key-label secret-key \
--wrapping-key-label kek --output /app/wrapped-key.bin
# Unwrap the key to restore it (e.g., after disaster recovery)
docker exec rust-hsm-app rust-hsm-cli unwrap-key \
--label DEV_TOKEN --user-pin 123456 --key-label restored-key \
--wrapping-key-label kek --input /app/wrapped-key.bin --key-type aes
# Generate Certificate Signing Request (CSR) for submitting to a CA
docker exec rust-hsm-app rust-hsm-cli gen-csr \
--label DEV_TOKEN --user-pin 123456 --key-label signing-key \
--subject "CN=test.example.com,O=TestOrg,C=US" --output /app/test.csr
# Verify CSR with OpenSSL
docker exec rust-hsm-app openssl req -in /app/test.csr -noout -textFor better security (avoiding PINs in shell history or process lists), use --pin-stdin:
# Initialize token with PIN from stdin
echo "my-secure-so-pin" | docker exec -i rust-hsm-app \
rust-hsm-cli init-token --label DEV_TOKEN --pin-stdin
# Set user PIN with both PINs from stdin (one per line)
printf "my-secure-so-pin\nmy-secure-user-pin" | docker exec -i rust-hsm-app \
rust-hsm-cli init-pin --label DEV_TOKEN --so-pin-stdin --user-pin-stdin
# Generate keypair with PIN from stdin
echo "my-secure-user-pin" | docker exec -i rust-hsm-app \
rust-hsm-cli gen-keypair --label DEV_TOKEN --pin-stdin \
--key-label signing-key --key-type rsa --bits 2048
# Sign with PIN from stdin
echo "my-secure-user-pin" | docker exec -i rust-hsm-app \
rust-hsm-cli sign --label DEV_TOKEN --pin-stdin --key-label signing-key \
--input /app/data.txt --output /app/data.sigTransform rust-hsm from a human-friendly CLI into automation-first infrastructure tooling with the --json flag.
list-slots --json- Slot and token informationlist-objects --json [--detailed]- Object inventory with attributeslist-mechanisms --json [--detailed]- Mechanism capabilitiesinspect-key --json- Detailed key attributes and fingerprints
# Verify key exists and get fingerprint
docker exec rust-hsm-app rust-hsm-cli inspect-key \
--label PROD_TOKEN --user-pin $PIN --key-label signing-key --json \
| jq -r '.objects[0].fingerprint'
# Output: 7b:ca:ae:4e:4c:be:24:5e:ae:4c:9a:a1:78:ba:12:0a:a8:24:57:d6# List all keys across multiple tokens
for token in TOKEN1 TOKEN2 TOKEN3; do
echo "=== $token ==="
docker exec rust-hsm-app rust-hsm-cli list-objects \
--label $token --user-pin $PIN --json \
| jq -r '.objects[].label'
done# Get all RSA keys with key sizes
$objects = docker exec rust-hsm-app rust-hsm-cli list-objects `
--label DEV_TOKEN --user-pin 123456 --json --detailed | ConvertFrom-Json
$objects.objects | Where-Object { $_.key_type -like "*val: 0*" } | ForEach-Object {
[PSCustomObject]@{
Label = $_.label
KeySize = $_.key_size_bits
Handle = $_.handle
}
}#!/bin/bash
# Check if key is older than 90 days, rotate if needed
OLD_KEY_FP=$(docker exec rust-hsm-app rust-hsm-cli inspect-key \
--label PROD_TOKEN --user-pin $PIN --key-label prod-key --json \
| jq -r '.objects[0].fingerprint')
# Generate new key
docker exec rust-hsm-app rust-hsm-cli gen-keypair \
--label PROD_TOKEN --user-pin $PIN --key-label prod-key-new --key-type rsa
# Verify new key
NEW_KEY_FP=$(docker exec rust-hsm-app rust-hsm-cli inspect-key \
--label PROD_TOKEN --user-pin $PIN --key-label prod-key-new --json \
| jq -r '.objects[0].fingerprint')
echo "Old: $OLD_KEY_FP"
echo "New: $NEW_KEY_FP"# Count objects per token for capacity monitoring
OBJECT_COUNT=$(docker exec rust-hsm-app rust-hsm-cli list-objects \
--label PROD_TOKEN --user-pin $PIN --json \
| jq '.object_count')
if [ $OBJECT_COUNT -gt 100 ]; then
echo "WARNING: Token has $OBJECT_COUNT objects"
# Send alert to monitoring system
fi# Generate report of all non-extractable keys
docker exec rust-hsm-app rust-hsm-cli list-objects \
--label PROD_TOKEN --user-pin $PIN --json --detailed \
| jq '.objects[] | select(.flags.extractable == false) | {label, key_type, key_size_bits}'# Display PKCS#11 module information
rust-hsm-cli info
# List all slots and tokens
rust-hsm-cli list-slots
# List objects on a token (simple output)
rust-hsm-cli list-objects --label <TOKEN> --user-pin <PIN>
# List objects with detailed attributes (like p11ls)
rust-hsm-cli list-objects --label <TOKEN> --user-pin <PIN> --detailed
# Shows: object type, flags (tok/prv/pub/r/w/loc), capabilities (sig/vfy/enc/dec/wra/unw),
# security attributes (sen/ase/nxt/XTR), and key sizes# Initialize a new token
rust-hsm-cli init-token --label <TOKEN> --so-pin <SO_PIN>
# Set user PIN (must be done after init-token)
rust-hsm-cli init-pin --label <TOKEN> --so-pin <SO_PIN> --user-pin <USER_PIN># Generate RSA keypair
rust-hsm-cli gen-keypair \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--key-type rsa --bits <2048|4096>
# Generate ECDSA keypair (P-256)
rust-hsm-cli gen-keypair \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--key-type p256
# Generate ECDSA keypair (P-384)
rust-hsm-cli gen-keypair \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--key-type p384
# Sign data (automatically detects RSA vs ECDSA)
rust-hsm-cli sign \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> --output <SIGNATURE_FILE>
# Verify signature (automatically detects RSA vs ECDSA)
rust-hsm-cli verify \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> --signature <SIGNATURE_FILE>
# Export public key in PEM format (for sharing or external verification)
rust-hsm-cli export-pubkey \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--output <PEM_FILE>
# Encrypt data with RSA public key (max 245 bytes for 2048-bit key)
rust-hsm-cli encrypt \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> --output <ENCRYPTED_FILE>
# Decrypt data with RSA private key
rust-hsm-cli decrypt \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <ENCRYPTED_FILE> --output <DECRYPTED_FILE>
# Delete a keypair from the token
rust-hsm-cli delete-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME>
# Generate symmetric key (AES-128/192/256)
rust-hsm-cli gen-symmetric-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--bits <128|192|256>
# Generate extractable symmetric key (for key wrapping)
rust-hsm-cli gen-symmetric-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--bits <128|192|256> \
--extractable
# Encrypt data with AES-GCM (no size limits, includes IV and auth tag)
rust-hsm-cli encrypt-symmetric \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> --output <ENCRYPTED_FILE>
# Decrypt data with AES-GCM
rust-hsm-cli decrypt-symmetric \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <ENCRYPTED_FILE> --output <DECRYPTED_FILE>
# Wrap a key for secure export (AES Key Wrap - RFC 3394)
rust-hsm-cli wrap-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_TO_WRAP> \
--wrapping-key-label <KEK_LABEL> \
--output <WRAPPED_KEY_FILE>
# Unwrap an encrypted key for secure import
rust-hsm-cli unwrap-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <NEW_KEY_NAME> \
--wrapping-key-label <KEK_LABEL> \
--input <WRAPPED_KEY_FILE> \
--key-type aes
# Generate Certificate Signing Request (CSR) from keypair
rust-hsm-cli gen-csr \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--subject "CN=example.com,O=MyOrg,C=US" \
--output <CSR_FILE># Hash data with SHA-256 (default, no login required)
rust-hsm-cli hash \
--input <FILE> \
--output <HASH_FILE>
# Hash data with SHA-512
rust-hsm-cli hash \
--algorithm sha512 \
--input <FILE> \
--output <HASH_FILE>
# Supported algorithms: sha256 (default), sha512, sha224, sha1# Generate HMAC key (256-bit generic secret)
rust-hsm-cli gen-hmac-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--bits 256
# Generate HMAC-SHA256
rust-hsm-cli hmac-sign \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--algorithm sha256 \
--input <FILE> \
--output <HMAC_FILE>
# Verify HMAC
rust-hsm-cli hmac-verify \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--algorithm sha256 \
--input <FILE> \
--hmac <HMAC_FILE>
# Supported algorithms: sha1, sha224, sha256, sha384, sha512# Generate CMAC key (AES-128/192/256)
rust-hsm-cli gen-cmac-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--bits 256
# Generate CMAC (16-byte MAC)
rust-hsm-cli cmac-sign \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> \
--output <CMAC_FILE>
# Generate truncated CMAC (e.g., 8 bytes)
rust-hsm-cli cmac-sign \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> \
--output <CMAC_FILE> \
--mac-len 8
# Verify CMAC
rust-hsm-cli cmac-verify \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--input <FILE> \
--cmac <CMAC_FILE>HMAC vs CMAC:
- HMAC: Hash-based (SHA family), outputs 32/64 bytes, widely supported
- CMAC: Block cipher-based (AES), outputs 16 bytes, faster for short messages
- Both provide message authentication and integrity verification
# Display detailed key attributes (CKA_* values)
rust-hsm-cli inspect-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME>
# Shows: CKA_CLASS, CKA_KEY_TYPE, CKA_SENSITIVE, CKA_EXTRACTABLE,
# CKA_SIGN, CKA_VERIFY, CKA_ENCRYPT, CKA_DECRYPT, etc.
# Public keys also display SHA-256 fingerprint:
# FINGERPRINT (SHA-256): ec:bb:93:16:a4:7c:... (colon-separated hex)
# Get key inspection in JSON format (includes fingerprint)
rust-hsm-cli inspect-key \
--label <TOKEN> --user-pin <PIN> \
--key-label <KEY_NAME> \
--json# Generate 32 random bytes (hex output to stdout)
rust-hsm-cli gen-random --bytes 32
# Generate random bytes to binary file
rust-hsm-cli gen-random --bytes 64 --output /app/random.bin
# Generate random bytes to hex file
rust-hsm-cli gen-random --bytes 32 --output /app/random.hex --hex
# Use HSM RNG for secure key generation, IVs, nonces, etc.
# No authentication required - RNG is accessible without loginRun comprehensive performance benchmarks across all cryptographic operations:
# Run full benchmark suite with 100 iterations per test
echo '123456' | rust-hsm-cli benchmark --label TEST --iterations 100 --pin-stdin
# Benchmark a specific application key (auto-detects key type)
echo '123456' | rust-hsm-cli benchmark --label TEST --key-label my-app-key --iterations 100 --pin-stdin
# Run with custom iteration count
echo '123456' | rust-hsm-cli benchmark --label TEST --iterations 1000 --pin-stdin
# Quick benchmark (fewer iterations)
echo '123456' | rust-hsm-cli benchmark --label TEST --iterations 10 --pin-stdinBenchmark Modes:
- Full Suite: Tests all operations with temporary keys (default)
- Specific Key: Tests operations available for a specific key using
--key-label
Full Suite Tests:
- Signing: RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384
- Verification: RSA-2048, ECDSA-P256
- Encryption: RSA-2048, AES-256-GCM (1KB data)
- Hashing: SHA-256, SHA-384, SHA-512 (1KB data)
- MACs: HMAC-SHA256, AES-CMAC
- Random Generation: 32 bytes
Specific Key Tests (auto-detected):
- RSA keys: Sign, Verify, Encrypt operations
- ECDSA keys: Sign, Verify operations
- AES keys: Encrypt (if CKA_ENCRYPT), CMAC (if CKA_SIGN)
- HMAC keys: HMAC-SHA256 operation
Output includes:
- Operations per second (ops/sec)
- Average latency (milliseconds)
- Latency percentiles: P50, P95, P99
Example benchmark results (SoftHSM2 on typical hardware):
Operation Ops/sec Avg (ms) P50 (ms) P95 (ms) P99 (ms)
RSA-2048 Sign 1304.2 0.77 0.74 0.94 1.19
RSA-4096 Sign 235.5 4.25 4.27 4.79 5.09
ECDSA-P-256 Sign 12433.0 0.08 0.06 0.14 0.99
ECDSA-P-384 Sign 1297.2 0.77 0.72 0.96 1.15
AES-256-GCM Encrypt (1KB) 28240.4 0.04 0.02 0.05 0.29
SHA-256 Hash (1KB) 384457.2 0.00 0.00 0.00 0.06
Note: Benchmarks automatically create temporary test keys. Keys persist on token for subsequent runs to improve performance.
# List all PKCS#11 mechanisms supported by the HSM
rust-hsm-cli list-mechanisms
# List mechanisms with detailed capabilities (like p11slotinfo)
rust-hsm-cli list-mechanisms --detailed
# Shows flags: enc=encrypt, dec=decrypt, sig=sign, vfy=verify, hsh=digest,
# gkp=generate_key_pair, wra=wrap, unw=unwrap, der=derive
# List mechanisms for a specific slot
rust-hsm-cli list-mechanisms --slot 404614813
# List mechanisms for specific slot with details
rust-hsm-cli list-mechanisms --slot 404614813 --detailedrust-hsm/
README.md
.github/
copilot-instructions.md # AI agent guidance
docker/
Dockerfile # Single container with SoftHSM2 + Rust CLI
softhsm2.conf # SoftHSM configuration
entrypoint.sh # Container startup script
compose.yaml # Docker Compose configuration
crates/
rust-hsm-cli/ # Modular CLI with organized command handlers
Cargo.toml
src/
main.rs # Clean 35-line entry point
cli.rs # Command definitions
config.rs # Configuration handling
commands/ # Modular command handlers
mod.rs # Main dispatcher
common.rs # Shared utilities (PIN, config)
info.rs # Information commands
token.rs # Token management
keys.rs # Key management
crypto.rs # Sign/verify/encrypt/decrypt
symmetric.rs # Symmetric operations
key_wrap.rs # Key wrapping
mac.rs # HMAC/CMAC operations
util.rs # Benchmark/audit/troubleshooting
analyze.rs # Log analysis
rust-hsm-core/ # Core PKCS#11 operations
src/
pkcs11/ # PKCS#11 functionality
observe-core/ # Observability event schema
observe-cryptoki/ # Cryptoki observability wrapper
rust-hsm-analyze/ # Log analysis and statistics
- PKCS#11 Library:
cryptokiRust bindings (v0.10) - CLI Framework:
clapwith subcommands - Logging:
tracing+tracing-subscriber - SoftHSM Version: 2.6.1
- Rust Version: 1.83
All operations follow: init → open session → login → operation → logout → finalize
RSA:
- Key Generation: 2048/4096-bit keys with public exponent 65537
- Signing: SHA-256 with RSA PKCS#1 v1.5 (
CKM_SHA256_RSA_PKCS) - Encryption: PKCS#1 v1.5 padding (
CKM_RSA_PKCS) - max 245 bytes for 2048-bit keys - Hashing: Built-in to mechanism
ECDSA:
- Key Generation: P-256 (secp256r1) and P-384 (secp384r1) curves
- Signing: ECDSA (
CKM_ECDSA) - Hashing: Manual SHA-256 before signing (PKCS#11 ECDSA mechanism expects pre-hashed data)
AES:
- Key Generation: 128/192/256-bit keys
- Encryption: AES-GCM (
CKM_AES_GCM) with 96-bit random IV and 128-bit authentication tag - IV Management: Random IV generated per encryption, prepended to ciphertext for storage
Key Storage: Token-resident private/secret keys (sensitive, non-extractable)
- Base Image: Debian Bookworm Slim
- Build: Multi-stage (Rust builder + slim runtime)
- Token Storage: Persistent Docker volume at
/tokens
The CLI uses structured logging with multiple levels. Set the RUST_LOG environment variable to control verbosity:
# Info level (default) - shows major operations
docker exec rust-hsm-app rust-hsm-cli info
# Debug level - detailed PKCS#11 operations
RUST_LOG=debug docker exec rust-hsm-app rust-hsm-cli gen-keypair \
--label DEV_TOKEN --user-pin 123456 --key-label test-key --key-type rsa
# Trace level - includes raw data and attribute dumps
RUST_LOG=trace docker exec rust-hsm-app rust-hsm-cli sign \
--label DEV_TOKEN --user-pin 123456 --key-label test-key \
--input /app/data.txt --output /app/data.sigLog Levels:
info- Operation milestones (token found, key generated, signature created)debug- PKCS#11 library calls (initialization, sessions, login, mechanism selection)trace- Raw data dumps (attributes, hashes, signatures, slot IDs)
The CLI includes comprehensive observability features for monitoring, debugging, and analyzing HSM operations:
Enable Observability in configuration file:
# .rust-hsm.toml
default_token_label = "DEV_TOKEN"
pkcs11_module = "/usr/lib/softhsm/libsofthsm2.so"
observe_enabled = true
observe_log_file = "/app/rust-hsm-observe.json"What Gets Logged:
- Every PKCS#11 function call (C_Initialize, C_Sign, etc.)
- Timestamp with nanosecond precision (RFC3339)
- Return codes (numeric + symbolic name like CKR_OK)
- Duration in milliseconds
- Slot/session handles, mechanisms
- What is NOT logged: PINs, key material, plaintext/ciphertext, attribute values
Analyze Logs:
# View operation statistics
docker exec rust-hsm-app rust-hsm-cli analyze \
--log-file /app/rust-hsm-observe.json
# Example output:
# === PKCS#11 Session Analysis ===
# Total Operations: 35
# Success Rate: 100.00%
# Error Count: 0
#
# --- Overall Timing ---
# Total Duration: 43.91ms
# Average: 1.25ms
# P50: 0.77ms P95: 1.88ms P99: 13.46ms
#
# --- Per-Function Statistics ---
# C_Initialize: 5 calls, avg 4.11ms
# C_Sign: 5 calls, avg 0.84ms
# JSON output for automation
docker exec rust-hsm-app rust-hsm-cli analyze \
--log-file /app/rust-hsm-observe.json --format jsonUse Cases:
- Performance benchmarking: Track P95/P99 latencies over time
- Debugging: Understand operation sequences and failures
- Security audit: Monitor access patterns and errors
- Capacity planning: Measure operations per second under load
- Provider comparison: Benchmark SoftHSM2 vs Kryoptic
See docs/commands/observability.md for complete documentation.
PKCS11_MODULE- Path to PKCS#11 library (default:/usr/lib/softhsm/libsofthsm2.so)SOFTHSM2_CONF- SoftHSM configuration file (default:/etc/softhsm2.conf)TOKEN_LABEL- Default token labelUSER_PIN- Default user PINSO_PIN- Default SO PIN
# Build container
docker compose build
# Rebuild after code changes
docker compose up -d --build# Run automated test suite (automatically cleans up test tokens)
docker exec rust-hsm-app /app/test.sh
# Clean up accumulated test tokens manually
docker exec -e AUTO_CONFIRM=yes rust-hsm-app /app/cleanup-test-tokens.sh
# Or run cleanup interactively
docker exec -it rust-hsm-app /app/cleanup-test-tokens.sh
# Or run individual commands
docker exec rust-hsm-app rust-hsm-cli info
docker exec rust-hsm-app rust-hsm-cli list-slots
# Reset token storage (deletes all tokens and slots)
docker compose down
docker volume rm rust-hsm_tokens
docker compose up -dThe test suite (test.sh) validates all 43 operations including:
- Token management (init, PIN setup, deletion)
- RSA & ECDSA keypair generation (RSA-2048/4096, P-256, P-384)
- Sign/verify operations with multiple algorithms
- Symmetric encryption (AES-128/192/256 with GCM)
- Key wrap/unwrap (AES Key Wrap RFC 3394)
- Hash operations (SHA-256/512)
- HMAC/CMAC operations
- Public key export (PEM format)
- CSR generation
- Random number generation
- Troubleshooting commands (explain-error, find-key, diff-keys)
- Automatic cleanup: Test tokens are removed after each run to prevent slot accumulation
# View container logs
docker compose logs -f
# Enter container shell
docker exec -it rust-hsm-app bash
# Check SoftHSM configuration
docker exec rust-hsm-app cat /etc/softhsm2.conf
# List token files
docker exec rust-hsm-app ls -la /tokens- Development and testing
- Learning PKCS#11 workflows
- Integration testing with HSM-like interfaces
Not suitable for production cryptographic operations.
- Never hardcode PINs in code or commit them to version control
- Use environment variables or secure secret management for PINs
- PINs are never logged by the CLI (only non-sensitive data)
- Private keys are marked as sensitive and non-extractable
- Consider using
--pin-stdinoption (planned feature) for automation