Signer Boundary
What the signer owns today, what changes when the production signer lands, and how operators recognize healthy versus failing signer behavior.
Signer Boundary
The signer is the only component allowed to perform private-key operations: certificate PDF signatures, blockchain transaction signatures, defensive certificate HMACs, and decryption of agent proof envelopes. Everything else in the platform consumes the signer as a service.
Current State: In-Process Test Signer
Local and CI stacks use the in-process test signer. It is not a custody boundary.
| Attribute | Value |
|---|---|
| Mode | SIGNER_MODE=test |
| Package | internal/adapters/signer/testsigner |
| Seed | TEST_SIGNER_SEED (shared by API and proof worker so HMAC verification is stable) |
| Capabilities | proof-envelope:v1 decryption, deterministic HMACs, RSA-backed PAdES signing, placeholder blockchain transaction signing |
| Trust | None — no remote custody, no durable key store, no approval workflow, no hardware backing |
Both SIGNER_MODE=test and AGENT_TRUST_MODE=dev are refused at startup when ENVIRONMENT, APP_ENV, or OBSERVABILITY_ENVIRONMENT is set to prod or production. Do not run production load against SIGNER_MODE=test. A dedicated signer
service is the production boundary.
Remote Signer (cmd/signer)
SIGNER_MODE=remote points the backend at the remote Signer, a separate Go
binary (backend/cmd/signer, image wipe-signer, chart component signer).
It is the only workload that holds private keys, and it receives none of the
backend environment: no database, queue or object-store credentials.
Routes
| Endpoint | Operation | Behaviour |
|---|---|---|
GET /healthz | none | Unauthenticated liveness, for the kubelet. |
GET /v1/keys/{key_id} | keys | Key id, version (first 16 hex of the SHA-256 of the public key), algorithm, public key (SPKI DER), certificate chain. |
POST /v1/sign/digest | sign | algo: "sha256": a 32-byte SHA-256 digest, signed as given (never hashed again). ECDSA returns a DER ECDSA-Sig-Value, RSA a PKCS#1 v1.5 signature. algo: "ed25519": the message itself, pure Ed25519 (Hedera HCS transactions and anchor messages). |
POST /v1/hmac, POST /v1/hmac/verify | hmac | HMAC-SHA256, encoded hmac:v1:<hex> like the test signer. |
POST /v1/decrypt | decrypt | Opens an exawipe-cms-1 AuthEnvelopedData addressed to the key’s certificate (envelope.KeyDecrypter). |
POST /v1/sign/pades, POST /v1/verify/pades | sign_pades, verify_pades | PAdES with the Signer’s PAdES key: dev (self-signed) or eIDAS (key certificate chain + mandatory TSA). |
POST /v1/sign/transaction | — | Not offered (501): HCS transactions are signed through sign/digest with algo: "ed25519". |
JSON byte fields are standard base64. A refused call answers 401 (unknown
caller), 403 (operation not granted, or unknown key), 400 (malformed
input), 422 (an envelope or PDF the key cannot process).
Keys
One Kubernetes Secret (signer.keysSecret), one entry per file; the key id is
the file name without its suffix:
| File | Holds |
|---|---|
<id>.key | PEM private key: PKCS#8, SEC 1 EC PRIVATE KEY or PKCS#1 RSA PRIVATE KEY (RSA at least 2048 bits). |
<id>.crt | Optional certificate chain of <id>.key, leaf first. Required for decrypt and for eIDAS PAdES. |
<id>.hmac | Raw HMAC-SHA256 secret, at least 32 bytes. |
The Signer refuses to start on a malformed file, an unknown suffix, or a certificate that is not for its key.
| Key id (backend default) | Type | Used by |
|---|---|---|
pades-signing-key | RSA or ECDSA (+ .crt in eIDAS mode) | Certificate PDFs |
hedera-operator-key | Ed25519 | HCS anchoring (BLOCKCHAIN_HEDERA_*_OPERATOR_KEY_ID) |
agent-decryption-key | RSA ≥ 2048 + .crt | Proof envelope decryption (PROOF_AGENT_DECRYPTION_KEY_ID) |
consumption-receipt-signing-key | ECDSA or RSA | Consumption receipts |
wipe-hmac | HMAC secret (.hmac) | Defensive certificate HMACs |
agents-ca | ECDSA P-256 | The agents CA: the ISO issuer signs ISO certificates through sign/digest |
Callers
signer.policy.clients names every caller and what it may do on which key;
nothing is granted by default. A caller is recognised by the Common Name of a
client certificate verified against signer.tls.clientCAKey, or by the
SHA-256 (hex) of its bearer token — the token itself is never stored. Give the
ISO issuer its own client, allowed sign and keys on agents-ca only.
Every call writes one signer.audit log line: caller, operation, key id, key
version, SHA-256 of the request body, status.
With networkPolicy.enabled, only the API, the ingest gateway and the workers
may open a connection to the Signer.
Operator Checks
| Symptom | First check |
|---|---|
WipeSignerFailures by operation | decrypt, hmac, hmac_verify, pades_sign, or receipt_sign — see Incident response. |
Proofs stuck in SIGNING | Signer reachability, SIGNER_MODE, mTLS material, key IDs, TSA URL. |
PAdES verification failures on /verify | TSA reachability and signer certificate chain (PAdES trust anchors). |
INVALID_HMAC on public verification | Signer key version drift between issue-time and verify-time. |
Production Readiness Inputs
The signer is externally blocked until the signer team provides the
following. Track these against adr/0001-remaining-backend-gaps.md item 2:
| Input | Owner |
|---|---|
| Remote signer protocol semantics | Signer team |
| mTLS material (client cert, CA) | Infra |
| Key IDs and rotation metadata | Signer team |
| Transaction signing behavior | Signer team |
| HMAC, decrypt, PAdES, TSA behavior | Signer team |
The remote Signer now exists (see above); what remains is the production material itself: the keys, the eIDAS certificate chain, the TSA and the mTLS certificates of each caller.
Backend Integration Notes
When the production signer lands, the backend integration is already scaffolded:
- Domain services stay on
SecretsProvider,KeySignerProvider, andSignatureProviderports. - The remote signer client (
adapters/signer/remote) ships besidetestsignerand also exposes acrypto.Signerbacked bysign/digest, which the Hedera HCS adapter uses in remote mode. - PAdES public certificate retrieval moves from local self-signed into signer metadata.
- Signer contract tests pin HMAC, digest signing, transaction signing, and proof-envelope decryption to fixed test vectors.
- Non-test modes fail at startup unless the remote signer URL, auth, and trust roots are configured.
Operators do not need to change anything in the application database when the signer mode flips; the change is configuration only.