Why the signer is a separate process
The most valuable bytes in a certificate platform are the authority's private keys. The most exposed code is the API that takes requests from the network. They should never share a process.
The problem in one sentence
Whoever controls the issuing keys can mint trusted certificates for any name in your fleet, and there is no recovery from an undetected key compromise: every credential the platform ever issued becomes suspect. That is a close paraphrase of the opening of trstctl's design document for the signer,1 and it is the reason the signer exists.
The usual shape, and why it fails
Most certificate tooling keeps the keys in the same process that serves the API. It is convenient: one binary, one config, one deploy. It also means that one remote code execution in the API, one dependency with a bad day, one debug endpoint left on, is the keys. At one renewal a year you could tell yourself the API was rarely busy. At twelve renewals a year per certificate, the API is the busiest thing you run.
trstctl's shape
The signer is trstctl-signer, a separate binary with its own address space, never run inside the control plane.2 It has no HTTP server, no database and no third-party logging, so there is very little of it to attack.1 On a single node the control plane launches it as a child process and talks to it over a Unix domain socket, and the socket checks that the process on the other end runs as the control plane's own user before it answers.3 Across nodes the two talk over mutual TLS pinned to TLS 1.3.4
Inside the signer, keys are referenced by opaque handles. The control plane never sees a private key; it asks for signatures. Key material sits in locked memory that is excluded from core dumps and is zeroed when a key is destroyed.5 The private key bytes do not leave the process, even if the control plane is fully compromised. That is the guarantee, and it is narrow on purpose. It covers more than the CA keys: when trstctl talks to Let's Encrypt, the account signatures are made in the signer too.8
What that does not solve, and what does
A compromised control plane is, by construction, allowed to ask for signatures. The signer cannot tell a legitimate issuance from an attacker driving an already-trusted control plane. So the design says so, and puts the defenses where they belong:1
- Policy and attestation gate the request in the control plane. They decide whether a signature should happen at all, before anything reaches the signer.
- Keys carry their own constraints. A key is created with an allowed set of purposes, sealed together with the key so it survives a restart. The issuing-CA key is bound to CA signing; a caller holding its handle still cannot make it sign anything else.6
- Crown-jewel keys need a second party. A key can be created under dual control. The signer then refuses every signature unless the request carries an authorization token: a keyed hash over the exact request, which key, for what purpose, and the exact digest to be signed, minted by an approval authority whose secret the caller does not have. The token commits to one digest, so it cannot be replayed onto different bytes. A signer without the verifier material, or a control plane without an independent token source, fails closed.7
Raising the bar further, so that even an attacker holding both the socket and the approver's secret cannot sign, is the job of hardware security modules and offline ceremonies. The signer's boundary is designed to compose with them, not to replace them.1
Why this is the 47-day question
Short lifetimes force automation, and automation is a robot signing certificates all day. The only honest way to run that robot is to make sure it never holds the thing that matters. The control plane orchestrates discovery, policy, approvals, renewal and audit. The signer signs. Automation without losing custody.
The full design, including the threat table and the wire protocol, is public: the signing service design. Every claim above links to the file it comes from.
Sources
- trstctl, Signing service design (source): threat statement, single-binary and multi-node modes, no HTTP server or database, policy and attestation gating, HSMs and offline ceremonies.
- trstctl, cmd/trstctl-signer/main.go: the separate signer binary.
- trstctl, internal/signing/peercred_linux.go: the Unix-socket peer credential check.
- trstctl, internal/crypto/mtls/mtls.go: minimum and maximum TLS version both set to 1.3.
- trstctl, internal/crypto/locked.go: locked, dump-excluded key buffers, zeroized on destroy.
- trstctl, internal/signing/server.go: purpose and usage constraints enforced per key.
- trstctl, internal/crypto/signauth.go: the dual-control authorization token.
- trstctl documentation, Issuance and CAs: Let's Encrypt account signatures stay in the isolated signer.