Craton HSM
gRPC Daemon
gRPC Daemon
cratond (crate: craton-hsm-daemon) exposes HsmCore over gRPC so that
applications on other hosts -- or in other containers within the same
pod -- can use a single shared HSM instance. The daemon process owns one
HsmCore and serialises all calls through it; clients authenticate with
mutual TLS.
Protocol Surface
The service is defined in craton-hsm-daemon/proto/craton_hsm.proto as
a single HsmService with RPCs grouped into four areas:
service HsmService {
// Session management
rpc OpenSession(OpenSessionRequest) returns (OpenSessionResponse);
rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse);
rpc Login(LoginRequest) returns (LoginResponse);
rpc Logout(LogoutRequest) returns (LogoutResponse);
// Token management
rpc GetTokenInfo(GetTokenInfoRequest) returns (GetTokenInfoResponse);
rpc InitToken(InitTokenRequest) returns (InitTokenResponse);
// Key and object operations
rpc GenerateKey(GenerateKeyRequest) returns (GenerateKeyResponse);
rpc GenerateKeyPair(GenerateKeyPairRequest) returns (GenerateKeyPairResponse);
rpc DestroyObject(DestroyObjectRequest) returns (DestroyObjectResponse);
rpc FindObjects(FindObjectsRequest) returns (FindObjectsResponse);
rpc GetAttributeValue(GetAttributeValueRequest) returns (GetAttributeValueResponse);
// Cryptographic operations
rpc Sign(SignRequest) returns (SignResponse);
rpc Verify(VerifyRequest) returns (VerifyResponse);
rpc Encrypt(EncryptRequest) returns (EncryptResponse);
rpc Decrypt(DecryptRequest) returns (DecryptResponse);
rpc Digest(DigestRequest) returns (DigestResponse);
rpc GenerateRandom(GenerateRandomRequest) returns (GenerateRandomResponse);
}
PINs are transmitted as bytes rather than string so that clients can
keep them in zeroisable buffers; the daemon zeroises the incoming PIN
field on its side after the login attempt completes.
GetTokenInfoRequest requires an open session_handle. Unauthenticated
slot enumeration is not supported -- a caller cannot learn anything
about a token without first opening a session against its slot.
RPC Scope vs. PKCS#11
The gRPC surface covers the operations a production client needs
(session, login, key generation, sign/verify, encrypt/decrypt, digest,
random, object lookup). Multi-part streaming operations
(C_SignUpdate, C_EncryptUpdate) are not exposed as streaming RPCs in
v1; clients submit the full payload in a single Sign or Encrypt
call. Wrap / unwrap and derive are not yet exposed over gRPC.
Mutual TLS Model
TLS is constructed by craton-hsm-daemon/src/tls.rs and enforced
directly against a rustls ServerConfig rather than through tonic's
convenience wrapper:
- TLS 1.3 minimum -- older protocol versions are rejected at the
rustls
ServerConfiglevel. - Server certificate --
tls_cert(PEM) andtls_key(PEM) are mandatory unless the daemon is running in explicit insecure mode. - Client certificate CA --
tls_client_ca(PEM) enables mutual TLS. When set, clients must present a certificate signed by the listed CA(s). Strongly recommended for production. - CRL checking --
tls_client_crl(PEM or DER) adds revocation checking for client certificates. Only effective whentls_client_cais also set.
Incoming TCP connections are wrapped through a TlsAcceptor built from
the validated ServerConfig. Handshake failures are logged at debug
level and the connection is dropped without falling back to plaintext.
Insecure Mode
Plaintext gRPC is permitted only when both conditions hold:
daemon.allow_insecure = trueis set in the config, anddaemon.bindparses as a loopback IP (127.0.0.1or[::1]).
Hostnames such as localhost are rejected -- only literal loopback
addresses count. If TLS is not configured and allow_insecure is
false, the daemon refuses to start.
Process Lifecycle
main.rs performs the following sequence:
- Install the default rustls
CryptoProvider(ring). - Initialise the
tracingsubscriber for structured logs. - Canonicalise the config path (one positional argument; default
craton_hsm.toml) and warn if it resolves through a symlink. - Load
FullConfig(daemon section) andHsmConfig(core section). A missing or unparseable config is fatal -- the daemon fails closed rather than running with defaults. - Run FIPS POST self-tests via
craton_hsm::crypto::self_test::run_post. Any failure aborts startup. - Construct
HsmCorein anArc. - Build the tonic service with request timeout,
per-connection concurrency limit (64), global
ConcurrencyLimitLayer, and gRPC message-size limits (4 MiB inbound, 16 MiB outbound). - Bind the TCP listener, wrap with TLS if configured, and serve
until
Ctrl-C/SIGINTtriggers graceful shutdown. In-flight requests are drained; new connections are refused.
Configuration
The daemon reads the [daemon] section of the same craton_hsm.toml
file that the core uses:
[daemon]
bind = "0.0.0.0:5696"
tls_cert = "/etc/craton-hsm/server.crt"
tls_key = "/etc/craton-hsm/server.key"
tls_client_ca = "/etc/craton-hsm/clients-ca.crt"
tls_client_crl = "/etc/craton-hsm/clients.crl"
allow_insecure = false
max_random_length = 1048576 # 1 MiB cap per GenerateRandom
max_digest_length = 16777216 # 16 MiB cap per Digest
max_login_attempts = 5
login_cooldown_secs = 60
max_connections = 256
request_timeout_secs = 30
The full list of fields, defaults, and validation rules is documented in the Configuration Reference.
Authentication
Two authentication layers compose:
- Transport -- mutual TLS identifies the client process. The
client certificate is validated against
tls_client_caand optionally revoked throughtls_client_crl. - Application -- the usual PKCS#11 login flow (
LoginRPC withCKU_USERorCKU_SO) identifies the token user. PIN-retry lockout is enforced insideHsmCoreexactly as for the C ABI path.
The core library does not link a TLS client identity to a token user;
mTLS authorises access to the daemon, and the token PIN authorises
access to the token. Mapping client certificates to PKCS#11 roles is
an enterprise-edition capability; see the craton-hsm-auth crate in
the enterprise distribution for RBAC, LDAP, OIDC, and certificate
mapping.
Rate Limiting and Quotas
The daemon applies several DoS bounds:
- gRPC message sizes -- 4 MiB decoding / 16 MiB encoding.
- Request timeout --
request_timeout_secs(default 30 s) applied at the tower layer. - Per-connection concurrency -- 64 in-flight requests per connection.
- Global concurrency --
max_connections(default 256) in-flight requests across all connections, enforced bytower::limit::ConcurrencyLimitLayer. - Login throttle -- after
max_login_attemptsfailed logins, the daemon imposes alogin_cooldown_secscooldown. This counter is in-memory only; an attacker who can crash and restart the daemon bypasses it. Persistent lockout is provided by the token-level PIN retry counter, which survives restarts when persistent storage is enabled. For RAM-only deployments, pair this with OS-level restart throttling (for example, systemdRestartSecandStartLimitBurst). - Payload caps --
max_random_lengthcapsGenerateRandomrequest size;max_digest_lengthcapsDigestinput size. These prevent unbounded allocation and CPU exhaustion.
Per-user / per-tenant quotas and key-count ceilings are enterprise features; the core daemon enforces only the global bounds above.
The Admin CLI as a Client
craton-hsm-admin is a local client: it loads the same craton_hsm.toml
config and operates directly against HsmCore in its own process. It
does not currently speak to cratond over gRPC. An operator running
craton-hsm-admin on the same host as cratond must coordinate with
the daemon: both cannot hold the persistent-store file lock
simultaneously.
See Admin CLI for the full command surface.