restish-pkcs11 is the concrete TLS-signer plugin for PKCS#11 devices such as
YubiKey-backed PIV tokens. It lets Restish perform mTLS client authentication
while keeping the private key inside the PKCS#11 provider.
This plugin is the main real-world validation of the generic TLS-signer design.
- allow Restish to use non-exportable private keys for mTLS
- support common PKCS#11 deployment patterns without bloating Restish core
- make token and certificate selection explicit and non-ambiguous
- keep unattended and interactive PIN workflows possible
- map the generic TLS-signer protocol onto
crypto11cleanly
- auto-selecting a "best guess" token when several could match
- exposing arbitrary PKCS#11 configuration complexity directly through the host
- weakening signer failure into an implicit fallback to file-based or anonymous TLS
This plugin is a concrete implementation of design 021.
The host responsibilities are:
- select the plugin from profile config
- launch and manage signer lifetime
- ask for a certificate and later signatures
The plugin responsibilities are:
- resolve PKCS#11 module and token config
- establish a
crypto11.Context - select exactly one signer-backed certificate
- perform sign operations when requested
The plugin advertises a simple manifest:
name: pkcs11hooks: ["tls-signer"]
It does not add commands, formatters, or loaders.
On startup the plugin expects the standard TLS-signer init message and
interprets the params object as PKCS#11-specific configuration.
The startup flow is:
- parse and validate configuration
- resolve the module path
- resolve token selection
- resolve PIN policy
- create the
crypto11.Context - find matching paired certificates
- require exactly one usable match
- return the leaf certificate DER bytes in the
readymessage - enter the long-lived signing loop
If any of those stages fails, startup fails immediately and clearly.
The plugin accepts a few aliases so profile config can stay ergonomic:
moduleorpathfor the PKCS#11 shared librarytoken_labelorlabeltoken_serialorserialslotpinpin_envlogin_not_supported
The configuration surface is intentionally narrower than raw PKCS#11 because the goal is a stable signer plugin, not a complete PKCS#11 management tool.
At most one token selector may be provided:
- token label
- token serial
- slot number
If no selector is provided, the plugin enumerates present tokens and auto-selects the slot only when exactly one token is available. Otherwise, the plugin refuses ambiguous selection so it does not accidentally choose the wrong certificate when multiple tokens or slots are available.
If more than one selector is supplied, startup should fail.
Module path resolution is ordered:
- explicit
moduleorpath PKCS11_MODULE_PATH- a small OS-specific list of common OpenSC library paths
If none of those resolve, startup fails with a clear error instead of guessing more broadly.
This preserves operator control and keeps debugging manageable across different platforms and distributions.
PIN lookup is ordered:
- explicit
pin - environment variable named by
pin_env PKCS11_PIN- an interactive prompt on
/dev/ttyorCONIN$
If login_not_supported is true, the plugin skips the PIN requirement.
This model keeps unattended execution possible through config or environment variables while still allowing an interactive fallback for local use.
The design intentionally allows explicit pin even though storing PINs in
config is not ideal, because some unattended environments need that option.
Operator guidance should still prefer env vars or terminal prompting where
possible.
After parsing config, the plugin creates a crypto11.Context and calls
FindAllPairedCertificates().
It currently requires exactly one matching certificate:
- zero matches is an error
- more than one match is an error
That is a conservative design choice. It avoids inventing extra selection heuristics inside the plugin and keeps failure modes obvious.
Once it has the matching certificate, it verifies that the private key exposes
crypto11.Signer, returns the leaf certificate DER bytes in the ready
message, and then enters the long-lived signing loop.
For each sign request from Restish, the plugin reads:
digesthash- optional
padding - optional
salt_length
It maps those onto crypto.SignerOpts, including RSA-PSS when
padding == "pss", then calls the PKCS#11-backed signer.
Replies are intentionally small:
{"signature": ...}on success{"error": "..."}on failure
The plugin should not retain per-sign mutable session state beyond what the underlying PKCS#11 context requires.
Startup errors should identify the failing stage when practical:
- module path resolution
- token selection
- PIN lookup
- token login
- certificate matching
- signer capability validation
Sign-time errors should also be explicit, especially for cases such as:
- token removed
- wrong PIN / login failure
- unsupported signer options
- device/session busy conditions
The host may surface plugin stderr as part of these failures, so the plugin should emit diagnostics that are useful but do not leak secrets.
The plugin should treat stdin EOF or host process exit as the normal shutdown signal.
Shutdown behavior should include:
- closing the
crypto11.Context - releasing token sessions cleanly
- exiting promptly so the host does not need to kill the signer in the common case
Graceful shutdown matters because leaked signer processes can leave hardware token sessions busy for later requests.
The key security property of the plugin is:
- Restish receives the certificate but not the private key
- all private-key operations remain inside the PKCS#11 provider
This is stronger operationally than file-based key export and is the reason the plugin exists at all.
Convenient, but too risky.
Would simplify startup but would make local interactive use clumsy and encourage storing PINs where they do not need to live.
Would enlarge the main binary, add CGO pressure, and blur the core/plugin boundary.
- Design 005 defines how TLS-signer selection fits into request TLS behavior.
- Design 021 defines the generic TLS-signer lifecycle and host contract.
- Design 030 defines the expectations around sensitive diagnostics and cleanup.