Craton HSM

Security Model

Threat Model

Craton HSM is a software HSM. Unlike hardware HSMs, it does not protect against physical attacks or OS-level compromise. Its security boundary is the process.

In scope (threats Craton HSM defends against)

ThreatDefense
Memory corruption (buffer overflow, use-after-free)Rust's ownership system prevents these at compile time
PIN brute-forcePBKDF2 (600K iterations) + configurable lockout after N failures
Timing side-channels on PIN comparisonsubtle::ConstantTimeEq for all sensitive comparisons
Key material leakage via loggingCustom Debug impls print [REDACTED] for all key types
Key material leakage in memory after useZeroizeOnDrop on RawKeyMaterial; mlock/VirtualLock on key pages
Fork-induced state corruptionPID recorded at init; child gets CKR_CRYPTOKI_NOT_INITIALIZED
Database corruption from concurrent processesExclusive file lock on persistent store
Unauthorized key extractionCKA_SENSITIVE=true and CKA_EXTRACTABLE=false enforced
Unauthorized operationsSession state machine enforces login before private object access
Audit log tamperingChained SHA-256 hashes; any modification breaks the chain
Panic-induced undefined behavior at FFI boundarycatch_unwind wraps every extern "C" export
Use of weak algorithmsallow_weak_rsa=false, allow_sha1_signing=false by default
Corrupted binary / supply-chainFIPS POST: 17 self-tests (integrity + 16 KATs) + continuous RNG health check run before any crypto operation
AES-GCM nonce reuse (per key)Per-key encryption counters with 2^31 limit; all-zero IV rejection at C_EncryptInit
Config path traversalUNC path, .. traversal, and absolute path rejection in config validation
Key misuse after expirySP 800-57 key lifecycle: date-based deactivation blocks signing/encryption

Out of scope (threats requiring hardware HSM or OS hardening)

ThreatWhy out of scope
Cold boot attacksKeys live in process memory; physical access can dump RAM
DMA attacksNo IOMMU enforcement at software level
OS kernel compromiseIf the kernel is compromised, all process memory is accessible
Power analysis / EM side-channelsSoftware cannot prevent hardware-level leakage
Speculative execution attacks (Spectre)Mitigations depend on CPU microcode and OS patches

Side-Channel Resistance

Craton HSM implements software-level side-channel countermeasures for timing attacks. Physical side-channels (power, electromagnetic) are out of scope for a software HSM.

Constant-time operations:

OperationImplementationSide-Channel Defense
PIN comparisonPBKDF2-HMAC-SHA256 + subtle::ConstantTimeEqTiming-safe: compare time independent of correct/incorrect PIN bytes
HMAC verificationsubtle::ConstantTimeEq on HMAC tagsPrevents byte-by-byte timing leakage of MAC values
RSA signing (RustCrypto)rsa crate with blinding by defaultMontgomery multiplication with randomized blinding factor
RSA signing (aws-lc-rs)AWS-LC (BoringSSL fork)Constant-time bignum arithmetic, same as Google/AWS production
ECDSA signing (RustCrypto)p256/p384 cratesField arithmetic in constant time (no branching on secret scalars)
ECDSA signing (aws-lc-rs)AWS-LC P-256/P-384Assembly-optimized constant-time point multiplication
AES operationsRustCrypto aes crate with AES-NIHardware AES-NI intrinsics are inherently constant-time (no table lookups)
SHA-256/384/512RustCrypto sha2 crateNo secret-dependent branching; hardware SHA extensions when available

Backend-specific notes:

  • RustCrypto backend (default): Pure Rust implementations. RSA uses randomized blinding. ECC uses projective coordinates with complete addition formulas. AES uses AES-NI when available, falling back to a bitsliced implementation that avoids timing-leaky S-box table lookups.

  • aws-lc-rs backend (FIPS): Assembly-optimized constant-time implementations from AWS-LC/BoringSSL. Battle-tested in AWS production. RSA uses constant-time modular exponentiation. ECC uses optimized P-256 and P-384 assembly with constant-time scalar multiplication. This is the same cryptographic core used by AWS KMS.

What we do NOT claim:

  • Protection against cache-timing attacks on systems without AES-NI (extremely rare on modern hardware)
  • Protection against speculative execution attacks (Spectre/Meltdown variants)
  • Protection against physical side-channels (power analysis, EM emanations)
  • Formally verified constant-time guarantees (would require tools like ct-verif or Jasmin)

Key Protection

Memory safety (Rust guarantees)

Rust eliminates at compile time the vulnerability classes responsible for the majority of memory-safety CVEs in C/C++ HSM implementations:

  • No buffer overflows: array bounds are checked at runtime
  • No use-after-free: the ownership system prevents accessing freed memory
  • No double-free: memory is freed exactly once when the owner is dropped
  • No null pointer dereference: Option<T> forces explicit handling
  • No data races: the borrow checker prevents concurrent mutable access

The only unsafe code is in the PKCS#11 C ABI layer (src/pkcs11_abi/functions.rs) and the memory locking module (src/crypto/mlock.rs). All blocks are annotated with // SAFETY: comments explaining their correctness.

Fork safety: On Unix, Craton HSM detects fork(2) by comparing the current PID against the PID stored at C_Initialize time. A forked child process receives CKR_CRYPTOKI_NOT_INITIALIZED on any PKCS#11 call and must call C_Initialize to create fresh state. See docs/fork-safety.md for full details.

Multi-process access: Concurrent multi-process access to the same persistent database is prevented by an exclusive file lock acquired at initialization. For multi-process access to the same token, use the gRPC daemon (craton-hsm-daemon), which serializes all operations.

Key material lifecycle

Generate / Import
       |
       v
  RawKeyMaterial::new(bytes)
  |  └── mlock(ptr, len)    <-- locks pages into physical RAM
  |  └── Debug: [REDACTED]  <-- never logs key bytes
  |
  |  Used via &[u8] borrow (never cloned unnecessarily)
  |
  v
  Crypto operation (sign, encrypt, etc.)
  |
  v
  Drop (manual impl):
  |  1. self.0.zeroize()     <-- clears all bytes to 0x00
  |  2. munlock(ptr, len)    <-- unlocks (now-zeroed) pages

Platform support:

  • Unix: libc::mlock / libc::munlock
  • Windows: VirtualLock / VirtualUnlock via windows-sys
  • Failure is non-fatal (logged, continues) — may lack privileges for large buffers

Access control model

PKCS#11 objects have three levels of protection:

  1. CKA_PRIVATE: If true, object is invisible to C_FindObjects unless the session is logged in
  2. CKA_SENSITIVE: If true, CKA_VALUE (key bytes) cannot be read via C_GetAttributeValue
  3. CKA_EXTRACTABLE: If false, key material cannot be exported

Default for generated keys: PRIVATE=true, SENSITIVE=true, EXTRACTABLE=false (maximum protection).

PIN security

PropertyImplementation
Hash algorithmPBKDF2-HMAC-SHA256
Iterations600,000 (configurable)
Salt32 bytes from OsRng (per-PIN, stored with hash)
Comparisonsubtle::ConstantTimeEq
LockoutConfigurable threshold (default: 10 failures)
PIN storageHash stored in Zeroizing<Vec<u8>> (zeroed on drop)
PIN in transitNever logged; CLI uses rpassword (no terminal echo)

Audit Integrity

The audit log is append-only with chained SHA-256 hashes:

Entry[0].previous_hash = [0; 32]  (genesis)
Entry[1].previous_hash = SHA-256(serialize(Entry[0]))
Entry[2].previous_hash = SHA-256(serialize(Entry[1]))
...

Properties:

  • Tamper-evident: modifying, deleting, or reordering any entry breaks the hash chain
  • Synchronous: event is recorded before the PKCS#11 function returns (not fire-and-forget)
  • Non-repudiable: each entry includes session handle, operation type, timestamp, and result

Audited operations: Initialize, Finalize, Login, Logout, GenerateKey, GenerateKeyPair, Sign, Verify, Encrypt, Decrypt, CreateObject, DestroyObject, GenerateRandom.

Algorithm Selection

Approved algorithms

CategoryAlgorithmsStandard
Symmetric encryptionAES-256-GCM, AES-256-CBC, AES-256-CTRSP 800-38A/D
Key wrappingAES Key WrapSP 800-38F (RFC 3394)
RSA signingPKCS#1 v1.5, PSS (SHA-256/384/512)FIPS 186-5
RSA encryptionOAEP (SHA-256)PKCS#1 v2.2
Elliptic curveECDSA P-256, P-384FIPS 186-5
Edwards curveEd25519RFC 8032
Key agreementECDH P-256, P-384SP 800-56A
HashSHA-256, SHA-384, SHA-512FIPS 180-4
Hash (SHA-3)SHA3-256, SHA3-384, SHA3-512FIPS 202
MACHMAC-SHA256/384/512FIPS 198-1
KDFPBKDF2-HMAC-SHA256SP 800-132
PQC signingML-DSA-44/65/87FIPS 204
PQC KEMML-KEM-512/768/1024FIPS 203
PQC signingSLH-DSA (SHA2-128s, SHA2-256s)FIPS 205
HybridML-DSA-65 + ECDSA-P256Composite

Restricted by default

SettingDefaultEffect
allow_weak_rsafalseBlocks RSA key generation < 2048 bits
allow_sha1_signingfalseBlocks SHA-1 as a hash in signatures

FIPS 140-3 Readiness

Power-On Self-Tests (POST)

Run during C_Initialize. If any test fails, the module sets POST_FAILED=true and all subsequent calls return CKR_GENERAL_ERROR. On re-initialization (after C_Finalize), POST_FAILED is reset and POST re-runs. See docs/audit-scope.md for the full POST table (17 tests: integrity check + 16 KATs).

SP 800-90A DRBG

All cryptographic random number generation — including key generation for RSA, EC, and Ed25519 — is routed through an HMAC_DRBG (HMAC-SHA256) per SP 800-90A via a DrbgRng wrapper implementing rand::CryptoRng. The DRBG is seeded from the OS CSPRNG with prediction resistance (fresh entropy on every call). A continuous health test (SP 800-90B) detects stuck output.

SP 800-57 Key Lifecycle

Keys support date-based lifecycle states per SP 800-57: pre-activation (before CKA_START_DATE), active, deactivated (after CKA_END_DATE), compromised, and destroyed. Deactivated keys can still verify/decrypt but cannot sign/encrypt.

FIPS crypto backend

Craton HSM supports two crypto backends, selectable via algorithms.crypto_backend in config:

BackendFeature FlagFIPS Status
RustCrypto (default)rustcrypto-backendNot FIPS-certified
AWS-LC (aws-lc-rs)awslc-backendFIPS 140-3 validated module

With --features awslc-backend and crypto_backend = "awslc", all classical crypto operations use the FIPS-validated AWS-LC library.

Remaining gaps for formal FIPS certification

See docs/fips-gap-analysis.md for the full analysis. The major gaps (DRBG, key lifecycle, POST KATs) have been resolved. Remaining items are documentation and formal certification process.

Security Invariants

These 10 properties are verified at every development phase and must never be violated:

  1. No panic crosses FFI boundary — catch_unwind on all extern "C" functions
  2. No key bytes in logs — custom Debug impls with [REDACTED]
  3. No key bytes returned for CKA_SENSITIVE=true, CKA_EXTRACTABLE=false objects
  4. Constant-time PIN/HMAC comparison — subtle::ConstantTimeEq everywhere
  5. All key material zeroized on drop — ZeroizeOnDrop derive on all key types
  6. Session state machine is authoritative — check state, not just login flag
  7. Audit log written before operation returns — not fire-and-forget async
  8. DRBG for all key material — SP 800-90A HMAC_DRBG (via DrbgRng adapter) for RSA, EC, Ed25519, AES key generation; never OsRng directly or rand::random() for keys
  9. No unsafe in crypto paths — all unsafe blocks confined to ABI layer and mlock.rs, none in crypto engine
  10. Generic errors to callers — never leak internal state via error messages