Stella Ops Signer

Cryptographic signing service for the Stella Ops release control plane: DSSE envelope signing/verification, dual-control key ceremonies, and trust-anchor key rotation, with fail-closed regional crypto profiles (FIPS/eIDAS/GOST/SM/PQ).

Audience: operators wiring the trust domain, and developers integrating signing into evidence-production workflows.

Consolidated under Attestor (Sprint 204, 2026-03-04). The Signer source now lives at src/Attestor/StellaOps.Signer/, and the authoritative design lives in the Attestor architecture dossier. Archived standalone docs are in docs-archive/modules/signer/. Runtime identity, the HTTP surface, and scopes are unchanged and are documented below.

Runtime Identity

HTTP API surface

All endpoints are minimal-API routes registered in StellaOps.Signer.WebService. Authorization policies are declared in Program.cs via AddStellaOpsScopePolicy and map to the canonical scopes in StellaOps.Auth.Abstractions/StellaOpsScopes.cs. Mutating endpoints emit audit events under AuditModules.Attestor.

Signing & verification (SignerEndpoints.cs)

Method & routePolicy → scopeNotes
POST /api/v1/signer/sign/dsseSigner.Signsigner:signBody SignDsseRequestDto; response SignDsseResponseDto (bundle, policy, auditId). The bundle carries providerName, algorithmId, and optional cryptoProfile beside the DSSE envelope so verification can resolve the exact configured trust path. Audited (SignDsse).
POST /api/v1/signer/verify/dsseSigner.Verifysigner:readA full Signer bundle is verified over DSSE PAE through ICryptoProviderRegistry using its provider/algorithm/key metadata. {dsse} or a bare envelope remains supported only for the explicit Development/Testing HMAC signer, which also signs/verifies PAE. Response VerifyDsseResponseDto (verified, keyId, reason). Audited (VerifyDsse).
GET /api/v1/signer/verify/referrers?digest=<digest>Signer.Verifysigner:readResponse VerifyReferrersResponseDto (trusted, trustedSigner).

The sign/dsse request DTO fields are subject[] (name, digest), predicateType, predicate (raw JSON), scannerImageDigest, poe (format = jwt|mtls, value), and optional options (signingMode = kms|keyless, expirySeconds, returnBundle, cadenced, cadesProfile = baselineB|baselineT|baselineLT|baselineLTA). JWT PoE additionally requires a DPoP request header; mTLS PoE requires a client certificate.

/verify/dsse is Signer-key verification, not arbitrary third-party Sigstore trust evaluation. Production callers should submit the complete bundle returned by /sign/dsse; the verifier decodes standard base64 or base64url, reconstructs DSSEv1 PAE from payloadType and payload bytes, and asks the named registered provider to verify the matching key id. Attestor remains responsible for broader certificate-chain, identity-policy, and transparency-log verification.

Dual-control ceremonies (CeremonyEndpoints.cs)

Route group /api/v1/ceremonies (group-level policy ceremony:readsigner:read).

Method & routePolicy → scopeNotes
POST /api/v1/ceremonies/ceremony:createsigner:signBody CreateCeremonyRequestDto; 201 Created with CeremonyResponseDto. Audited.
GET /api/v1/ceremonies/(inherits ceremony:readsigner:read)Filters: state, operationType, initiatedBy, tenantId, limit, offset. Returns CeremonyListResponseDto.
GET /api/v1/ceremonies/{ceremonyId:guid}(inherits ceremony:read)CeremonyResponseDto or 404.
POST /api/v1/ceremonies/{ceremonyId:guid}/approveceremony:approvesigner:signBody ApproveCeremonyRequestDto (reason, base64 signature, signingKeyId). Audited.
POST /api/v1/ceremonies/{ceremonyId:guid}/executeceremony:executesigner:adminAudited.
DELETE /api/v1/ceremonies/{ceremonyId:guid}?reason=ceremony:cancelsigner:admin204 No Content. Audited.

CeremonyOperationType: KeyGeneration, KeyRotation, KeyRevocation, KeyExport, KeyImport, KeyRecovery. CeremonyState: Pending, PartiallyApproved, Approved, Executed, Expired, Cancelled.

Policy naming note: CeremonyEndpoints.cs references the legacy raw policy names (ceremony:read/ceremony:create/ceremony:approve/ceremony:execute/ceremony:cancel), which Program.cs registers as backward-compatible aliases alongside the SignerPolicies.* constants (Signer.CeremonyRead, etc.). Both alias sets resolve to the same signer:* scopes.

Key rotation & trust anchors (KeyRotationEndpoints.cs)

Route group /api/v1/anchors (group-level policy KeyManagementsigner:rotate).

Method & routeNotes
POST /api/v1/anchors/{anchorId:guid}/keysAdd a signing key; 201 Created with AddKeyResponseDto. Audited (AddKey).
POST /api/v1/anchors/{anchorId:guid}/keys/{keyId}/revokeRevoke a key; RevokeKeyResponseDto. Audited (RevokeKey).
GET /api/v1/anchors/{anchorId:guid}/keys/{keyId}/validity?signedAt=Temporal key-validity check; KeyValidityResponseDto.
GET /api/v1/anchors/{anchorId:guid}/keys/historyKeyHistoryResponseDto.
GET /api/v1/anchors/{anchorId:guid}/keys/warningsRotationWarningsResponseDto.

Operational endpoints (Program.cs)

Scopes

The Signer-specific canonical scopes (from StellaOpsScopes.cs) are:

Observability

The Signer service participates in the shared StellaOps host tracing/auditing pipeline (AddAuditEmission, Router/host instrumentation). A planned set of Signer-specific span attributes (signer.key_id, signer.algorithm, signer.signature_id, signer.subject_count, signer.predicate_type, signer.signing_mode, signer.signing_ms) is documented as the target semantic convention in StellaOps.Signer.Tests/Observability/SignerOTelTraceTests.cs.

Not yet implemented. No production code in StellaOps.Signer.WebService / StellaOps.Signer.Infrastructure emits these custom signer.* span tags today — the OTel test only asserts the documented attribute list is non-empty and tolerates their absence (“may be internal”). Treat the attribute names above as a forward convention, not a live signal, until span emission is wired.

Runtime Binding Contract

Live Signer hosts are fail-closed against local-only runtime services. Outside the test host harness, startup requires:

The optional local CAdES-B companion provider is enabled only by explicit Signer:Dsse:CadesBaselineBPfxPath configuration and is suitable for sealed offline harnesses that need verifiable CMS bytes. It does not upgrade Signer to qualified eIDAS/QSCD evidence and does not satisfy T/LT/LTA evidence profiles.

Signer does not bake regional crypto provider packs into the service image. Base compose mounts only the shared Router/Messaging bundle roots needed for service registration. Crypto provider packs remain host-owned for default and offline.verification; FIPS/eIDAS/GOST/SM/HSM/simulator/cloud entries require explicit profile overlays and signed provider material before they become runtime providers.

Primary DSSE signatures are algorithm-pinned by the Signer active crypto profile before provider lookup. Built-in pins are fips -> ES256, eidas -> ES256, gost -> GOST12-256, sm -> SM2, and pq -> DILITHIUM3; deployments may supply explicit ProfileAlgorithmPins for operator-controlled provider packs. Profiles that require SM or PQ still obey their existing soft-provider gates and otherwise fail closed.

The WebService project no longer references EF InMemory or test bearer handlers. Test-only auth, EF InMemory key-management storage, and local ceremony/PoE/quota/audit doubles live in StellaOps.Signer.Tests and are injected by SignerApplicationFactory. Development hosts must provide PostgreSQL key-management storage; the local HMAC signer remains a Development/Testing-only deterministic signing harness.

Why Signer lives under Attestor

Signer, Attestor, and Provenance form the trust domain — the set of services responsible for cryptographic evidence production, transparency logging, and verification. Consolidating source ownership under src/Attestor/ makes trust-boundary responsibilities explicit while preserving runtime isolation between Signer state and Attestor evidence state.

See the Trust Domain Model ADR for the no-merge rationale.