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 indocs-archive/modules/signer/. Runtime identity, the HTTP surface, and scopes are unchanged and are documented below.
Runtime Identity
- Docker image:
stellaops/signer:dev(verified indevops/compose/docker-compose.stella-services.yml; regional variantssigner:eu/signer:russia/signer:chinaexist for compliance compose files). - API base paths:
/api/v1/signer/,/api/v1/ceremonies/, and/api/v1/anchors/(all mapped inStellaOps.Signer.WebService/Program.cs). - Direct payload signing: internal callers can use
SigningRequest.DirectPayload(SigningRequest.CreateDirectPayload) for registry-backed direct JSON artifacts such asproduct-csaf-advisory-v1.SignerPipelinevalidates the EU signing payload registry entry, schema pin, canonical JSON hash, entitlement, release integrity, quota, provider resolution, and audit before emitting the DSSE envelope. - CRA dossier wrapper: the DSSE signing endpoint accepts opt-in
options.cadenced=trueor areturnBundletoken containingcadenced. The response returns an outer DSSE payload typeapplication/vnd.stellaops.cadenced-dsse+json(schemastellaops.cadenced-dsse-envelope.v1, profilecra-dossier-envelope) plusbundle.cadencedmetadata.BaselineBcan emit a detached local CMS/CAdES-B-compatible companion whenSigner:Dsse:CadesBaselineBPfxPathis configured with a PKCS#12/PFX certificate containing private-key material; this is offline harness coverage only, not production QES/QTSP evidence.BaselineT,BaselineLT, andBaselineLTAremain fail-closed withcadenced.cades.provider_pack_requireduntil the production CAdES provider pack is available. (Grounded inCadencedEnvelopeBuilder.cs.) - Database schema:
signer(auto-migrated on startup viaAddStartupMigrations("signer", "Signer.KeyManagement", …)). Tables:key_history,key_audit_log,trust_anchors,ceremonies,ceremony_approvals,ceremony_audit_log(migrations under__Libraries/StellaOps.Signer.KeyManagement/Migrations/). - Authentication: Authority resource-server auth (
AddStellaOpsResourceServerAuthentication) in live hosts; test bearer auth is injected only by the test project harness (Testingenvironment).
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 & route | Policy → scope | Notes |
|---|---|---|
POST /api/v1/signer/sign/dsse | Signer.Sign → signer:sign | Body 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/dsse | Signer.Verify → signer:read | A 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.Verify → signer:read | Response 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:read → signer:read).
| Method & route | Policy → scope | Notes |
|---|---|---|
POST /api/v1/ceremonies/ | ceremony:create → signer:sign | Body CreateCeremonyRequestDto; 201 Created with CeremonyResponseDto. Audited. |
GET /api/v1/ceremonies/ | (inherits ceremony:read → signer: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}/approve | ceremony:approve → signer:sign | Body ApproveCeremonyRequestDto (reason, base64 signature, signingKeyId). Audited. |
POST /api/v1/ceremonies/{ceremonyId:guid}/execute | ceremony:execute → signer:admin | Audited. |
DELETE /api/v1/ceremonies/{ceremonyId:guid}?reason= | ceremony:cancel → signer:admin | 204 No Content. Audited. |
CeremonyOperationType: KeyGeneration, KeyRotation, KeyRevocation, KeyExport, KeyImport, KeyRecovery. CeremonyState: Pending, PartiallyApproved, Approved, Executed, Expired, Cancelled.
Policy naming note:
CeremonyEndpoints.csreferences the legacy raw policy names (ceremony:read/ceremony:create/ceremony:approve/ceremony:execute/ceremony:cancel), whichProgram.csregisters as backward-compatible aliases alongside theSignerPolicies.*constants (Signer.CeremonyRead, etc.). Both alias sets resolve to the samesigner:*scopes.
Key rotation & trust anchors (KeyRotationEndpoints.cs)
Route group /api/v1/anchors (group-level policy KeyManagement → signer:rotate).
| Method & route | Notes |
|---|---|
POST /api/v1/anchors/{anchorId:guid}/keys | Add a signing key; 201 Created with AddKeyResponseDto. Audited (AddKey). |
POST /api/v1/anchors/{anchorId:guid}/keys/{keyId}/revoke | Revoke 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/history | KeyHistoryResponseDto. |
GET /api/v1/anchors/{anchorId:guid}/keys/warnings | RotationWarningsResponseDto. |
Operational endpoints (Program.cs)
GET /healthz— liveness, anonymous,Predicate=false(always200while the process is responsive).GET /readyz— readiness, anonymous, runs all registered health checks.GET /— anonymous readiness string"StellaOps Signer service ready.".GET /internal/plugins/status- anonymous canonical plugin status report for Signer’s crypto provider packs. Basedefaultandoffline.verificationproviders are host-owned and required; regional packs are optional unless explicitly selected by a regional compose overlay.POST /internal/plugins/probe- anonymous deterministic dry-run probe using the sharedPluginProbeReportcontract. The base probe requiresdefaultandoffline.verificationto respond and leaves unmounted regional packs visible as non-blockingnotMounted.- Build-info endpoint mapped via
BuildInfoEndpointExtensions.MapBuildInfoEndpoint(operator verify aggregator). - OpenAPI document mapped anonymously (
MapOpenApi().AllowAnonymous(); default route/openapi/{documentName}.json).
Scopes
The Signer-specific canonical scopes (from StellaOpsScopes.cs) are:
signer:read— read-only access to Signer configuration and key metadata (DSSE verify, referrers, ceremony read).signer:sign— create signatures (DSSE sign, ceremony create/approve).signer:rotate— rotate Signer keys (the/api/v1/anchorskey-management group).signer:admin— administrative control (ceremony execute/cancel).
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.Infrastructureemits these customsigner.*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:
ConnectionStrings:KeyManagementbound to PostgreSQL; EF InMemory key-management storage is rejected.- Authority resource-server authentication; the stub bearer handler is rejected.
- PostgreSQL ceremony repository and audit sink; in-memory ceremony services are rejected.
- Configured PoE introspection, stateless quota enforcement, structured audit emission, and crypto-backed DSSE signing; in-memory PoE/quota/audit sinks and local HMAC signing are rejected.
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.
Related documentation
- Attestor architecture dossier — authoritative trust-domain design.
- Attestor module overview — evidence production and verification surface.
- Canonical scope catalog:
StellaOps.Auth.Abstractions/StellaOpsScopes.cs.
