openhsm

A USB hardware security module on an STM32U585 — bare-metal firmware with signed boot, an encrypted key store and a tamper-evident audit log, plus a PKCS#11 provider, CLI, SSH agent and OpenBao auto-unseal.

Cwiphsm · pkcs#11 · stm32 · secure boothardware & securitysource on github

Why

Auto-unseal OpenBao (or Vault) from a physical device through a standard PKCS#11 provider, without plaintext keys ever leaving the hardware. The same provider serves any PKCS#11 consumer — OpenSC, p11-kit, ssh -I. The OpenBao path works end to end on real hardware.

How

The board is an STM32U585 (Cortex-M33) with hardware AES, PKA and TRNG. No crystal — USB runs from HSI48 with clock recovery — and no debugger: everything is flashed over DFU. Firmware is about 53 KB of flash and 19 KB of RAM.

  • Transport. A USB vendor class on a bulk endpoint: 16-byte header (command, session id, counter, length, status) and up to 512 bytes of payload, reassembled from 64-byte packets.
  • Secure session. Ephemeral X25519 on both sides, HKDF-SHA256 into separate host→device and device→host keys, AES-256-GCM with the header as AAD and a strictly increasing per-direction counter for replay protection.
  • Key store. Objects are sealed with AES-256-GCM under a KEK derived from the device, with immutable metadata bound as AAD. 32 slots in one 8 KB flash page. AES-256, HMAC-SHA256, Ed25519, X25519 and ECDSA P-256. There is no plaintext export — keys leave only wrapped, and only if marked exportable.
  • Crypto split. AES-GCM on the AES peripheral, P-256 on the PKA, Ed25519/X25519 via Monocypher, SHA-256/HMAC/HKDF in software. The HASH peripheral was dropped: it mishandled lengths that aren’t a multiple of 4 and wedged on failure, which made the KEK non-reproducible. Known-answer tests use vectors generated with libsodium.
  • Secure boot. A 64 KB stage-1 bootloader verifies an Ed25519 signature over version, length and image against a baked-in vendor key, with an anti-rollback high-water mark. Updates go over USB into a staging bank and are copied over by a RAM-resident routine, so power loss mid-apply retries from staging.
  • Audit log. A two-page flash ring of 1024 16-byte entries. Each entry carries a sequence number and a truncated HMAC over the previous MAC, so the chain is tamper-evident; openhsm-cli audit-verify recomputes it on the host. Logging every operation costs roughly 25% throughput.
  • Host side. libopenhsm_pkcs11 with all 68 PKCS#11 entry points wired, a CLI, a USB↔TCP bridge daemon for remote use, and an SSH agent for Ed25519 (stock ssh -I only drives P-256).

Status

Work in progress, and honest about it. The handshake is unauthenticated ECDH (no device certificate yet), the KEK derives from a readable UID, readout protection and TrustZone are not enabled yet, and there is no RTC, watchdog or fuzzing so far. The roadmap orders the remaining hardening from safe to irreversible.

make toolchain && make firmware && make flash && make ping
$ git clone https://github.com/zombocoder/openhsm
← all projects