Production signing runbook (compliance evidence)

Source verification — 2026-10-07: checked against f9dbf896333339ed6ed3e655d4e8e3a793cc38fe. Evidence owns capsule signing and verification-key publication; OfflineKit owns NIS2 SoA exports. The configuration and source checks below do not establish a deployed key, successful seal, export, or offline verification. Re-verify the named owner source and selected Compose chain before an operational window.

This runbook is the signing procedure referenced by the release checklist. Signing keys are operator-owned secrets. No shared signing key ships with the product. Use the existing cryptography library and the configured key for the operation; obtain key material from an approved local secret source or an enrolled provider. Never copy a predecessor seed, generate a substitute key to clear a failed check, or paste private material into a Compose file or this document.

Vault is the flagship secret backend. Provider and custody selection must match the operator’s sovereign deployment. A software provider name or a profile switch does not prove hardware custody, certification, or qualified signing. This procedure supplies no legal-compliance or eIDAS-qualified acceptance verdict.

SurfaceOwning serviceConfigurationPublic verification material
Decision-capsule sealEvidence’s locker moduleEvidenceLocker:SigningCapsule JWKS (§4).
NIS2 SoA exportOfflineKit’s export moduleExport:Nis2SoaMatching configured trust root carried in the AirGap bundle (§5).

1) How a signer chooses its algorithm + provider

Both surfaces sign through ICryptoProviderRegistry, but their selection inputs are different:

Re-verify src/__Libraries/StellaOps.Cryptography/SignatureProviderSelection.cs, src/Evidence/__Libraries/StellaOps.EvidenceLocker/Capsules/CryptoRegistryCapsuleSigner.cs, and the SoA signer under src/OfflineKit/__Libraries/StellaOps.ExportCenter.Core/Adapters/Nis2/. The registry must support the chosen algorithm and resolve the configured key id on the intended provider. An enabled provider alone does not enroll a key.

1.1 Three distinct config inputs (do not conflate)

The compliance profile (Crypto:Compliance:ProfileId, with environment override STELLAOPS_CRYPTO_COMPLIANCE_PROFILE) chooses the purpose default where the caller uses SignatureProviderSelection. Registry active-profile and preferred provider settings choose provider order. STELLAOPS_CRYPTO_PROFILE selects the regional Compose overlay. Inspect the selected overlay and actual caller inputs; these settings do not replace one another.

1.2 Per-purpose algorithm defaults

SignatureProviderSelection supplies Ed25519 for CapsuleSeal under the world default and ES256 for SoaExport. Regional defaults apply only when that routine is called and no algorithm override is supplied. The checked-in Evidence family pins ED25519; the checked-in OfflineKit SoA configuration pins ES256. Those are source configuration choices, not a live signing or regional-certification verdict.

1.3 Provider ids and their gates

Use crypto profile configuration and the selected provider’s registration to verify its id, gate, supported algorithm and key enrollment. eu.eidas.soft and fips.ecdsa.soft are software providers; enabling their gates does not establish qualified or certified custody. Keep test simulation providers out of release-evidence signing and cloud-managed KMS out of default deployment selection.

2) Surface A — EvidenceLocker decision-capsule key

2.1 Current source configuration

The consolidated evidence-web / evidence-worker roles use devops/compose/docker-compose.evidence.yml. Its signing anchor sets UseCryptoRegistry=true, Algorithm=ED25519, KeyId=evidence-locker-capsule-key, Provider=bouncycastle.ed25519, AllowEphemeralKeyMaterial=false and AllowUnsignedCapsules=false.

The anchor requires STELLAOPS_EVIDENCELOCKER_CAPSULE_ED25519_SEED_B64 from an approved secret source for KeyMaterial:Ed25519PrivateKeyBase64; there is no repository seed default. This private input is not made public by calling it a lab or stable key. An operator selecting provider-held custody must review the selected Compose chain and remove that material input and its mandatory interpolation from the deployment configuration once the provider owns the key.

CryptoRegistryCapsuleSigner first resolves the configured key through the registry. If resolution fails, it can provision explicitly configured key material; ephemeral generation requires the separate opt-in. Keep the ephemeral option disabled for protected evidence. Key identity and provider enrollment must be verified before sealing, rather than inferred from a configuration value.

2.2 Production wiring

  1. Keep UseCryptoRegistry=true; select the operation’s algorithm, provider and configured key id using the installed cryptography/provider workflow.
  2. Enroll the configured key on that provider, or supply the existing durable operator-owned material through the approved secret injection path. Do not invent PEM encodings or reuse a key from another signing operation.
  3. For provider-held custody, omit raw KeyMaterial inputs and verify the selected Compose chain no longer requires them. For supplied material, verify it matches the configured algorithm and key identity. Keep secrets outside committed configuration and review rendered settings locally without recording secrets.
  4. Keep AllowEphemeralKeyMaterial=false and AllowUnsignedCapsules=false.
  5. Verify the advertised public key and an actual protected seal/export (§4).

2.3 The production startup gate

EvidenceLockerRuntimeConfigurationValidator.ValidateCapsuleSigner accepts UseCryptoRegistry=true without checking that the provider holds the key. Startup success therefore does not establish usable custody. The non-local validator also rejects AllowUnsignedCapsules=true.

On signing failure, CryptoRegistryCapsuleSigner.SignPayloadAsync returns an unsigned result with a failure status. CapsuleService refuses protected TLPT and decision-capsule sealing when that result is unsigned and unsigned capsules are disabled. Inspect the operation result; do not describe the signer itself as always throwing. JWKS failure remains a separate unavailable read (§4).

3) Surface B — ExportCenter NIS2 SoA export key

3.1 Current source configuration

The export module is composed by offlinekit-web in devops/compose/docker-compose.offlinekit-consolidated.yml. It sets Export:Nis2Soa:SigningKeyId, SigningAlgorithmId, SigningProviderHint, ExpectedSignerKeyId, TrustRootMode and TrustRoots. The deployed-file input is SigningPrivateKeyPemFile, selected by STELLAOPS_EXPORTCENTER_NIS2_SOA_SIGNING_KEY_PEM_FILE. This is an operator-owned read-only mounted key, not a shipped PEM.

AddNis2SoaExportServices delegates provisioning to OperatorMountedSigningKeys.AddOperatorMountedSigningKey. A configured mounted file wins over SigningPrivateKeyPem; a missing, unreadable or empty file fails closed and never falls back to inline material. Inline material exists for controlled embedded/test hosts and must not appear in deployed configuration.

A pre-registered registry does not cause the helper to ignore a configured mounted file: it reads that file before deciding how to register the material. When another registry exists, mounted material can be retained beside it. A provider-owned key and an authoritative configured file are separate inputs; do not leave a stale file path and expect registry registration to bypass it. Neither registry registration nor parsed material proves that the SoA signer can resolve the configured key. Test the actual operation after configuration.

3.2 Production wiring

  1. Use the operator’s existing enrolled SoA key and configure the same key id, algorithm and provider hint expected by the owner signer.
  2. For mounted custody, mount the operator-owned EC P-256 private key read-only and point SigningPrivateKeyPemFile at the container path. Use the existing configuration/provisioning library; this runbook supplies no key-generation command or substitute key. Verify path, access and actual signer resolution.
  3. For provider-held custody, enroll the key in the registered sovereign provider and leave both SigningPrivateKeyPemFile and SigningPrivateKeyPem unset. Remove the file input from the selected deployment chain; the checked-in family supplies a path default unless the operator overrides that configuration.
  4. Configure the matching public trust root: TrustRootId, KeyId, PublicKeyPem, Fingerprint, valid NotBeforeUtc / NotAfterUtc bounds and SignerProfiles. The checked-in public-key and fingerprint inputs come from machine-local configuration, not a built-in signing key. Review validity for each release.
  5. Keep TrustRootMode=AirGap for inline offline-verification roots and align ExpectedSignerKeyId with the accepted signing identity. Verify the bundle against the intended public key, rather than only its self-declared identity.
  6. Configure the Authority tenant-profile read and its service-account secret through approved local pointers. Messaging dispatch cannot assume a forwardable inbound bearer. The service-account grant must satisfy the actual owner policy.

The signed run is POST /api/offlinekit/v1/exports/nis2/soa/runs. It inherits ExportViewer from the SoA group and adds ExportOperator for POST runs. The caller must satisfy both canonical claims, export.viewer and export.operator, plus any configured resource-server RequiredScopes. The shared handler requires each scope by exact normalized claim membership; Operator does not imply Viewer. Descriptive export:operate metadata is not that complete requirement. Re-verify the host’s configured defaults before the signing window.

4) Surface A verification key — capsule JWKS (offline verify)

GET /api/evidence/v1/evidence/capsules/signing-keys/jwks is the anonymous public key read mapped by Evidence. No private key is returned. CapsuleSigningKeyService publishes available signing-root public keys, including rotation/archive entries, or resolves the existing configured CapsuleSeal key through ConfiguredPurposeSigningKeyResolver. The read does not generate a key or seal evidence. If it cannot obtain a usable key set, the endpoint returns 503 with capsule_signing_key_unavailable.

  1. Fetch the JWKS from the accepted service boundary. Retain the public key whose kid matches the exported envelope’s key id; do not assume keys[0] is active.
  2. Export the sealed capsule with POST /api/evidence/v1/evidence/capsules/{id}/export and retain its manifest, DSSE envelope and verification material.
  3. Recompute the canonical manifest hash and verify the DSSE payload/signature using the declared supported algorithm and matching public key. Do not assume every provider uses Ed25519 or that every JWK has only an x component.
  4. Perform verification in the intended disconnected environment and record the actual result. Key publication alone does not establish a successful seal or offline round trip. Rotation must preserve verification material for earlier protected evidence; see bundle rotation.

5) Surface B verification key — SoA embedded trust root (offline verify)

The AirGap SoA bundle carries the configured public trust root. A successful owner run reports verification.isValid with its verified key/trust-root identity. Retain the exact returned bundle and verify that artifact offline with the asymmetric verifier in src/OfflineKit/__Libraries/StellaOps.ExportCenter.Core/Adapters/Nis2/Nis2SoaBundleVerifier.cs. Its asymmetric verification path uses the configured public trust roots and expected signing identity. Record the actual bundle hash and verification result.

The stella export nis2-soa --signing-key-file ... command is a separate local HMAC export path: Nis2SoaCommandGroup creates a new bundle from snapshot/profile inputs and reports its own offlineVerified result. It does not verify the server-produced ES256 bundle. Do not use that result as production asymmetric export acceptance or pass the server’s private key into that command.

6) eIDAS QES (qualified) opt-in posture

eu.eidas.soft is software signing. An algorithm, provider gate or compliance profile selection is not qualified-signing evidence. Record the operator’s accepted custody and verification requirements separately; do not advertise a software signature as qualified. For the explicit provider bridge and its acceptance requirements, use the eIDAS QSCD/QTSP Bridge Contract. This source review verifies no installed qualified provider or legal posture.

7) Configuration review before a signing window

7.1 Evidence capsule (evidence-web + evidence-worker)

Review EvidenceLocker:Signing in the selected family and overlays. Record the algorithm, provider and key id without exposing private material. Verify the key through the existing provider/configuration workflow, keep both unsigned and ephemeral flags false, and remove material inputs only when provider-held custody is enrolled and the deployment chain agrees. Run the actual §4 seal/export check.

7.2 OfflineKit NIS2 SoA (offlinekit-web)

Review Export:Nis2Soa and the selected read-only mount. Mounted custody needs the authoritative accessible file; provider-held custody needs that file input absent and the configured key already enrolled. In both cases, the signer must resolve the key and the matching public trust root must be complete and valid. Run the owner export and verify its exact asymmetric artifact (§5).

8) Production preconditions checklist