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 ServerConfig level.
  • Server certificate -- tls_cert (PEM) and tls_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 when tls_client_ca is 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:

  1. daemon.allow_insecure = true is set in the config, and
  2. daemon.bind parses as a loopback IP (127.0.0.1 or [::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:

  1. Install the default rustls CryptoProvider (ring).
  2. Initialise the tracing subscriber for structured logs.
  3. Canonicalise the config path (one positional argument; default craton_hsm.toml) and warn if it resolves through a symlink.
  4. Load FullConfig (daemon section) and HsmConfig (core section). A missing or unparseable config is fatal -- the daemon fails closed rather than running with defaults.
  5. Run FIPS POST self-tests via craton_hsm::crypto::self_test::run_post. Any failure aborts startup.
  6. Construct HsmCore in an Arc.
  7. 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).
  8. Bind the TCP listener, wrap with TLS if configured, and serve until Ctrl-C / SIGINT triggers 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_ca and optionally revoked through tls_client_crl.
  • Application -- the usual PKCS#11 login flow (Login RPC with CKU_USER or CKU_SO) identifies the token user. PIN-retry lockout is enforced inside HsmCore exactly 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 by tower::limit::ConcurrencyLimitLayer.
  • Login throttle -- after max_login_attempts failed logins, the daemon imposes a login_cooldown_secs cooldown. 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, systemd RestartSec and StartLimitBurst).
  • Payload caps -- max_random_length caps GenerateRandom request size; max_digest_length caps Digest input 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.