Private keys are often the most important security boundary in a system, but many development and integration workflows still treat them as ordinary files. That creates a gap between how PKI is demonstrated and how production systems are expected to behave.
Real deployments usually want private keys to stay inside a protected boundary: an HSM, a smart card, a PKCS#11 token, or a TPM 2.0 device. The application gets signing, encryption, certificate, attestation, or unlock behavior, but it does not get to copy the private key material out into a local file.
keyman is a Go and TypeScript sandbox for that model. It provides a
management console, HTTP API, CLI, and Go library for creating and exercising
protected keys through PKCS#11/HSM and TPM 2.0 backends.
The default demo image runs everything in one container: the Go API, the
BlueprintJS management console, SoftHSM for PKCS#11, and swtpm for TPM 2.0.
That makes the project easy to run locally while still using the same kinds of
interfaces that real hardware-backed deployments use.
It is designed for teams that need to understand, prototype, or validate secure key workflows for SSH login keys, TLS certificates, disk unlock secrets, VPN identity, code signing, certificate requests, and certificate inventory without starting from vendor-specific HSM tooling.
Build and run the single-container demo:
make demoOpen:
http://127.0.0.1:8080
The command builds keyman:latest, starts the container, and keeps it running
in the foreground. Press Ctrl-C to stop it.
The Makefile uses Docker when docker is available and falls back to Podman
otherwise.
Force Docker:
CONTAINER_RUNTIME=docker make demoForce Podman:
CONTAINER_RUNTIME=podman make demoUse a different local port:
PORT=5080 make demoThen open:
http://127.0.0.1:5080
The demo image initializes a local SoftHSM token named KEYMAN.
HSM PIN: 1234
The TPM 2.0 side uses the software TPM started inside the same container and is available without an HSM PIN.
Run the regular Go test suite:
make testBuild the web console and server binary:
make buildRun the SoftHSM-backed integration suite:
make test-integrationRun the CLI lifecycle demo against an initialized PKCS#11 token:
keyman -module /usr/lib/softhsm/libsofthsm2.so -token KEYMAN -pin 1234 lifecycleRun the HTTP server against an initialized token:
keymand -module /usr/lib/softhsm/libsofthsm2.so -token KEYMANThe server opens the token after the browser submits the PIN through the management console.
keyman handles common protected-key workflows:
- Creates non-extractable AES-256-GCM, RSA-4096, and ECDSA P-256 keys.
- Stores key metadata alongside PKCS#11 objects.
- Rotates AES keys while preserving older decrypt versions.
- Encrypts and decrypts with versioned AES-GCM keys.
- Signs and verifies payloads with HSM-backed RSA and ECDSA keys.
- Exposes HSM-backed keys through Go's
crypto.Signerinterface. - Creates self-signed X.509 certificates using HSM-resident private keys.
- Creates CSRs from stored HSM keys.
- Imports PEM, DER, and certificate chains after checking for a matching key.
- Lists stored keys and certificates with backend, algorithm, key size, subject, issuer, validity, and matching key information.
- Prevents key deletion while certificates for that key are still present.
- Reads TPM 2.0 status, random bytes, and PCR values.
- Runs TPM signing, sealing, unsealing, PCR-bound secret, and attestation quote
exercises through
tpm2-tools. - Serves a BlueprintJS management console for guided key and certificate workflows.
The browser never talks directly to PKCS#11 or TPM devices. It calls the Go API; the Go process owns the backend session and device interaction.
PKCS#11 is the common application interface for HSMs, smart cards, USB tokens, and software tokens such as SoftHSM. Applications ask the token to generate, use, and manage objects. Sensitive keys can be marked non-extractable so the private key never leaves the token boundary.
TPM 2.0 is different. It is tied to a machine or platform, not to a portable token. A TPM can create machine-bound keys, release sealed data only under a policy, report measured boot state through PCRs, and produce attestation evidence about the platform state.
keyman puts both models in one place because real secure infrastructure often
needs both:
- HSM or PKCS#11 for portable, centralized, or token-backed private-key operations.
- TPM 2.0 for machine-bound identity, sealed secrets, boot-state policy, and attestation.
- X.509 and CSR workflows to connect those protected keys to PKI systems.
Public background:
Browser management console
|
| HTTPS or local HTTP JSON API
v
keymand Go server
|
+------------------------------+
| |
| PKCS#11 module | TPM2 TCTI
v v
HSM / smart card / SoftHSM TPM 2.0 / swtpm
For the default local demo:
keyman container
Go API/server
TypeScript static console
SoftHSM PKCS#11 module
SoftHSM token state
swtpm daemon
TPM2 state
For hardware-backed use, the same application shape can point at a vendor
PKCS#11 library, a mounted HSM device, a smart-card stack, /dev/tpmrm0, or an
external TPM TCTI endpoint.
The management console is intentionally organized around user workflows rather than low-level library options.
Overview: explains the purpose of the app at a high level.Create: guides the user through choosing what kind of key to create and whether it should live in the HSM token or TPM 2.0 device.My Keys: shows protected keys across backends.My Certs: creates CSRs, imports certificates, and lists stored certificate inventory.Proof: exposes the technical evidence behind the demo, including HSM and TPM operations.
The HSM token is unlocked from one place in the top bar. The PIN is kept only in server process memory for the active session.
Create a protected signing key for SSH authentication. In an HSM flow the key is portable with the token. In a TPM flow the key is bound to the local machine.
Create a private key, generate a CSR from that stored key, import an issued certificate or chain, and keep the certificate associated with the matching private key.
Use HSM-backed wrapping or TPM sealing behavior to model disk unlock secrets without storing the clear secret as an ordinary file.
Create a protected identity key that can be paired with a certificate for VPN or machine-authentication workflows.
Create a non-extractable signing key and demonstrate signing without exposing the private key material to the application.
The PKCS#11 backend currently supports:
- Module loading and token discovery.
- Session login by token label.
- AES-256-GCM secret-key generation.
- RSA-4096 and ECDSA P-256 private-key generation.
- Sensitive, non-extractable object templates.
- Key metadata object storage.
- AES-GCM encrypt/decrypt with key version metadata.
- Key rotation with old decrypt versions retained.
- RSA and ECDSA signing.
- Signature verification using public-key material.
crypto.Signeradapter for HSM-backed TLS use.- Self-signed X.509 certificate creation.
- CSR creation from stored keys.
- PEM, DER, and certificate-chain import.
- PKCS#11 certificate object inventory.
- Certificate deletion.
- Key deletion protection when matching certificates still exist.
The integration tests run these behaviors against SoftHSM.
The TPM 2.0 backend is implemented through tpm2-tools so the commands remain
transparent and inspectable. The default container starts swtpm in TPM2 mode
and configures the TCTI automatically.
The TPM demo currently supports:
- TPM readiness checks.
- TPM-generated random bytes.
- SHA-256 PCR reads.
- Owner-hierarchy ECC primary creation.
- TPM-resident ECC signing.
- Keyedhash sealed-data object creation.
- Secret unsealing.
- PCR-bound secret exercise.
- Attestation quote exercise.
The TPM side is meant to show the difference between portable token-backed keys and machine-bound trust: a TPM can bind behavior to the platform state rather than just protecting a private key object.
The HTTP server accepts flags and environment variables.
KEYMAN_ADDR HTTP listen address, default :8080
KEYMAN_STATIC_DIR compiled management console directory
PKCS11_MODULE_PATH PKCS#11 provider library path
PKCS11_TOKEN_LABEL token label, default KEYMAN
KEYMAN_APPLICATION_LABEL PKCS#11 application label, default keyman
PKCS11_PIN optional startup PIN
PKCS11_PIN_FILE optional file containing startup PIN
TPM2TOOLS_TCTI TPM2 TCTI, default swtpm:host=127.0.0.1,port=2321
KEYMAN_TPM2_WORK_DIR transient TPM work directory
Equivalent server flags:
keymand \
-addr :8080 \
-static web/dist \
-module /usr/lib/softhsm/libsofthsm2.so \
-token KEYMAN \
-app-label keyman \
-tpm2-tcti swtpm:host=127.0.0.1,port=2321For normal console use, the PIN is submitted through the browser after the page
loads. PKCS11_PIN and PKCS11_PIN_FILE are available for automation.
The server exposes JSON API routes for the console and for integration testing:
GET /api/health
POST /api/session
DELETE /api/session
GET /api/keys
POST /api/keys
POST /api/keys/{id}/rotate
DELETE /api/keys/{id}
GET /api/certificates
POST /api/certificates/csr
POST /api/certificates/import
POST /api/certificates/self-signed
DELETE /api/certificates/{label}
POST /api/lifecycle/run
GET /api/tpm2/health
GET /api/tpm2/pcrs
POST /api/tpm2/random
POST /api/tpm2/demo
The API is local-demo oriented today. Production use would need authentication, authorization, audit logging, policy controls, TLS configuration, and operational hardening around the same backend primitives.
Generate a private key in an HSM token, create a CSR, import the issued
certificate, and use the crypto.Signer adapter so TLS handshakes sign through
the token instead of a private-key file.
Create a token-backed or TPM-backed SSH-style signing key and demonstrate the difference between a portable credential and a machine-bound credential.
Use the TPM seal/unseal and PCR-bound secret flow to model how a disk unlock secret can be released only when the measured platform state matches policy.
Use the management console to create keys, request certificates, import issued certificates or chains, and keep certificate inventory tied to the matching stored key.
Use the HSM signing path to model release signing where the build or release tool can request a signature but cannot export the signing key.
For a PKCS#11 integration, the deployment needs:
- A PKCS#11 provider library path.
- A token label or other slot-selection policy.
- A user PIN or session-unlock flow.
- Object naming and lifecycle policy.
- Certificate and key-usage policy.
For a TPM 2.0 integration, the deployment needs:
- A TCTI target such as
device:/dev/tpmrm0,swtpm:..., or another supported TPM access method. - A decision about owner hierarchy, endorsement hierarchy, and persistent object use.
- PCR and policy choices for sealed-data workflows.
- Attestation evidence handling if remote trust decisions are needed.
The application code should not need to know how to extract private keys, because protected private keys should not be extractable.
keyman is not a full secrets manager, CA, OpenBao replacement, Vault
replacement, Cloud KMS replacement, or HSM vendor SDK.
That is intentional.
It is a focused protected-key lab and integration layer. It shows how applications can create and use keys through standard HSM and TPM interfaces while keeping private key material inside the backend boundary.
keyring.go: backend-neutral key management interfaces and data types.pkcs11/manager.go: PKCS#11 module, token, and session lifecycle.pkcs11/keys.go: key generation, lookup, metadata, rotation, and deletion.pkcs11/aes.go: AES-GCM encryption with key version metadata.pkcs11/sign.go: RSA/ECDSA signing andcrypto.Signersupport.pkcs11/certificate.go: certificate creation, CSR creation, and import.pkcs11/certificates.go: certificate inventory.pkcs11/tls.go: managed TLS certificate loading.tpm2/: TPM2 command adapter.cmd/keyman/main.go: CLI lifecycle demo.cmd/keymand/main.go: HTTP API and static console server.web/src/App.tsx: TypeScript management console.web/src/styles/console.css: BlueprintJS console styling.scripts/run-softhsm-tests.sh: SoftHSM integration-test runner.build/Dockerfile: single-container demo image.
Run the Go unit tests:
make testRun the web build:
make web-buildBuild the server and console:
make buildRun the SoftHSM integration test suite:
make test-integrationThe integration suite exercises:
- SoftHSM token initialization.
- AES-256-GCM key creation.
- Encryption with key version 1.
- AES key rotation.
- Decryption of version 1 ciphertext after rotation.
- Encryption with the latest key version.
- RSA signing and verification.
- ECDSA signing and verification.
- Self-signed X.509 certificate creation.
- PKCS#11 certificate object storage.
- TLS certificate loading with an HSM-backed
crypto.Signer.
Build the default image:
make container-buildBuild and run the default demo:
make demoRun it with Docker:
docker run --rm -p 8080:8080 keyman:latestRun it with Podman:
podman run --rm -p 8080:8080 keyman:latestPersist token and TPM state with mounted volumes:
docker run --rm -p 8080:8080 \
-v "$PWD/.runtime/softhsm:/var/lib/softhsm:rw" \
-v "$PWD/.runtime/keyman:/var/lib/keyman:rw" \
keyman:latestThe image uses local build caches for package managers where supported by the
container runtime. Docker builds use Docker's default local-cache behavior.
Podman builds default to --pull=missing so already-present base images are
reused instead of pulled on every build.
The Dockerfile uses BuildKit cache mounts. Modern Docker enables BuildKit by
default; if an older Docker setup disables it, run DOCKER_BUILDKIT=1 make demo.
The Makefile defaults are:
CONTAINER_RUNTIME=docker when available, otherwise podman
CONTAINER_BUILD_FLAGS=empty for Docker, --pull=missing for Podman
HOST=127.0.0.1
PORT=8080
CONTAINER_NAME=keyman-demo
For a real HSM or smart-card stack, mount the needed device paths or sockets and point the server at the vendor PKCS#11 provider:
docker run --rm -p 8080:8080 \
-e PKCS11_MODULE_PATH=/usr/lib/vendor/pkcs11.so \
-e PKCS11_TOKEN_LABEL=MYTOKEN \
keyman:latestFor a host TPM resource manager:
docker run --rm -p 8080:8080 \
--device /dev/tpmrm0 \
-e TPM2TOOLS_TCTI=device:/dev/tpmrm0 \
keyman:latestExact device flags depend on the host OS, container runtime, HSM vendor, and TPM access model.
keyman is expected to run on standard Go-supported Unix-like systems with CGO
support for PKCS#11. The local demo path is intended for:
- macOS with Docker or Podman.
- Linux with Docker or Podman.
- Linux hosts with SoftHSM, OpenSC, vendor PKCS#11 libraries, or TPM2 tooling.
The web console is built with TypeScript, Vite, React, and BlueprintJS.
This repository is intended for transparent examples, repeatable local tests, and backend integration work.
Do not commit real HSM PINs, production token labels, private keys, sensitive certificate subjects, deployment-specific policy, or real certificate-chain material.
For production use, add authentication, authorization, audit logging, rate limits, secret handling policy, TLS, operational monitoring, backup and recovery procedures, and vendor-specific HSM controls.
Apache License 2.0. See LICENSE.
keyman makes protected-key workflows concrete.
It gives developers and integrators one place to create HSM-backed keys, create CSRs, import certificates, exercise TPM sealed secrets and attestation, and see how applications can use private keys without turning those private keys into ordinary files.
