Skip to content

Signing Service ​

This is the authoritative bounded-context reference for the Signing Service that ships under internal/signing and the cmd/plexsphere-signer binary. The reference is split into four focused pages plus this entry point.

The Signing Service is the sharpest privilege edge in a plexsphere deployment: the only process in the system that ever touches Ed25519 private key material. Every other subsystem reaches signatures through the two-RPC gRPC surface and holds only public halves. The "what the Signing Service is not" list (in Operations) pins the shape of that boundary verbatim against ../../../README.md#signing-service.

Pages ​

  • Deployment scoping and rotation — the (saas | selfhosted-single | selfhosted-multi) profile matrix, the three-state rotation lifecycle (active, rotating, retired), and the rotation overlap window invariant.
  • KeyProvider contract and adapters — the outbound port every back-end implements (software, PKCS #11 HSM, cloud KMS, test double) plus the production HSM wiring rules.
  • Security invariants — the no-private-half invariant in the core binary, the mTLS peer-identity gate, and the one-entry-per-RPC audit contract.
  • gRPC surface and operations — the two RPCs and their stable error strings, the NetworkPolicy topology, the operator debugging workflow, and the explicit "what the Signing Service is not" boundary.

See also ​

Ubiquitous language ​

The terms below travel together across the Go code, the audit log, the .proto contract, and operator-facing tooling. Names are preserved verbatim in error messages and structured log attributes.

TermDefinitionCode anchor
SigningKeyThe aggregate that owns a single signing-key row: (scope, key_id, state, valid_from, valid_until, public_bytes, provider_handle_id). Carries only the public half plus the opaque provider_handle_id; the private bytes never enter this process address space.internal/signing/types.go
ScopeThe compound identifier (ScopeKind, ID) where ScopeKind is platform or domain. The wire form is platform or domain:<uuid>; NewScope enforces ID == uuid.Nil iff Kind == platform.Scope, NewScope, ParseScope in internal/signing/types.go
KeyIDAn opaque URL-safe identifier of a single SigningKey row (^[A-Za-z0-9._~-]+$, ≤ 128 bytes). The zero value is always invalid.NewKeyID in internal/signing/types.go
StateThe lifecycle phase of a SigningKey: active, rotating, or retired. Transitions are funnelled exclusively through internal/signing/rotation.State constants in internal/signing/types.go; transition fns in internal/signing/rotation/state.go
RotationTransitionThe aggregate binding an outgoing OldKey and incoming NewKey to a Window. Cross-scope transitions are rejected at construction time.internal/signing/rotation/overlap.go
WindowThe (OpenedAt, ClosesAt) pair during which both halves of a rotation are advertised. Strict OpenedAt < ClosesAt.NewWindow in internal/signing/rotation/overlap.go
KeyProviderThe outbound port for the signing back-end (software, PKCS #11 HSM, cloud KMS, test double). The only code in the system that touches private key material.internal/signing/ports.go
SignRequest / SignResponseThe Sign RPC shape: canonical_bytes, scope, key_id in; raw 64-byte Ed25519 signature plus echoed key_id out.api/proto/signing/v1/signing.proto
PublicKeyRequest / PublicKeyResponseThe PublicKey RPC shape: scope, key_id in; 32-byte public_key, echoed key_id, and lifecycle state out.api/proto/signing/v1/signing.proto
canonical bytesThe exact pre-serialised byte sequence passed to Sign. The signer NEVER re-canonicalises or transforms the payload; the caller owns deterministic encoding.internal/signing/envelope/canonical.go
SPIFFE peer identityThe spiffe:// URI SAN on the caller's mTLS client certificate, extracted from peer.FromContext before any business logic runs. Drives admission through the --allow-spiffe-id allow-list.internal/signing/peeridentity/interceptor.go
audit grant / denyOne audit.Entry per admission decision: granted when the allow-list admits, insufficient_relation when the allow-list refuses (or when the domain refuses with ErrClientIdentityDenied), caveat_violation for lifecycle refusals (ErrKeyRetired, ErrRotationInProgress, ErrProviderUnavailable).internal/signing/audit_middleware.go, internal/signing/peeridentity/interceptor.go