Compliance signing: provider-selection routine & production handoff
Audience. Operators and implementers wiring production signing for compliance evidence (decision capsules, SoA/export bundles, verdict-ledger/attestations, regulatory payloads). This is the handoff doc for SPRINT_20260530_024 (production signing): it states exactly what to set so production signing is a configuration/selection exercise, not a code change.
Authored by SPRINT_20260530_023 (compliance signing through the crypto abstraction
- provider-selection routine).
1) The contract
Every compliance-evidence signing path routes through the Stella Ops cryptographic abstraction — ICryptoProviderRegistry → ICryptoProvider → ICryptoSigner (src/__Libraries/StellaOps.Cryptography/). No call site performs a raw/direct crypto call to produce compliance evidence, and no call site hard-codes a provider. The specific provider is chosen by configuration/selection.
There are two orthogonal selection inputs:
| Input | Config key(s) | What it picks |
|---|---|---|
| Registry profile | StellaOps:Crypto:Registry:ActiveProfile + :PreferredProviders (env STELLAOPS__CRYPTO__REGISTRY__ACTIVEPROFILE); regional bundles via STELLAOPS_CRYPTO_PROFILE | the provider (which ICryptoProvider signs) |
| Compliance profile | Crypto:Compliance:ProfileId (env STELLAOPS_CRYPTO_COMPLIANCE_PROFILE); world/fips/gost/sm/kcmvp/eidas | the algorithm family for a signing purpose |
See docs/security/crypto-profile-configuration.md for the full region/profile table and the CLI selection surface (stella crypto profiles select <profile>).
2) The provider-selection routine
The single routine lives in the core library:
src/__Libraries/StellaOps.Cryptography/SignaturePurpose.cs— well-known signing purposes:capsule-seal,evidence-bundle,soa-export,verdict-attestation,regulatory-payload,generic-dsse.src/__Libraries/StellaOps.Cryptography/SignatureProviderSelection.cs— the routine.
SignatureProviderSelection.Resolve(
new SignatureSelectionRequest(
Purpose: SignaturePurpose.CapsuleSeal,
ProfileId: "<active compliance profile>", // optional
AlgorithmOverride: "<configured Signing:Algorithm>", // optional
ProviderOverride: "<configured Signing:Provider>")) // optional
=> SignatureSelection(AlgorithmId, ProviderHint?)
A call site requests a signer by purpose; the routine resolves the canonical algorithm for (profile + purpose), threads any explicit override, and the registry then resolves the concrete provider from the active profile’s preferred order. This is the asymmetric-signing analogue of ComplianceProfile.GetAlgorithmForPurpose (hashing) and GetHmacAlgorithmForPurpose (HMAC).
Algorithm defaults (behaviour-preserving)
| Purpose | world / international / kcmvp | eidas | fips | gost | sm | pq |
|---|---|---|---|---|---|---|
capsule-seal | Ed25519 (deterministic audit-chain default) | ES256 | ES256 | GOST12-256 | SM2 | Dilithium3 |
| all others | ES256 | ES256 | ES256 | GOST12-256 | SM2 | Dilithium3 |
These reproduce what the call sites resolve today (e.g. CryptoDsseSigner.TryResolveProfileAlgorithm, the live EvidenceLocker capsule config’s ED25519), so swapping in a production provider does not change the dev-profile signature shape (golden-path determinism preserved).
On-prem-first guard (non-negotiable)
ProviderHint is only non-null when an operator passes an explicit ProviderOverride. The routine never defaults to a cloud-managed KMS. Sovereign software providers (eu.eidas.soft, fips.ecdsa.soft, GOST, SM), an in-process PKCS#11 HSM, or the local bouncycastle.ed25519 dev provider are the only defaults. A cloud KMS, if ever supported, is an explicit opt-in provider plugin selected via ProviderOverride / registry preferred order — never a default. (MEMORY: feedback_on_prem_first_no_cloud_defaults; Vault is the flagship KEK/secret backend.)
The deploy-time pack/config contract is static and local: devops/etc/crypto-plugins-manifest.json names runtime provider IDs and, when different, the signed packId; devops/etc/plugins/crypto/registry.yaml names profile-level provider packs; and devops/etc/appsettings.crypto.*.yaml selects runtime provider IDs such as offline.verification, ru.openssl.gost, ru.pkcs11, ru.cryptopro.csp, ru.winecsp.http, cn.sm.soft, cn.sm.remote.http, eu.eidas.soft, fips.ecdsa.soft, pq.soft, and sim.crypto.remote.
Dev fallback reached through the routine
The dev bouncycastle.ed25519 ephemeral path is a fallback reached through the routine, not a hard-coded branch: on the world/international profile the routine resolves Ed25519 for capsule-seal, and the registry routes Ed25519 to the BouncyCastle Ed25519 provider. So moving to production is purely a profile/provider selection change.
3) Where the routine is consumed
CryptoRegistryCapsuleSigner (EvidenceLocker capsule sealing) resolves its signing algorithm + provider through SignatureProviderSelection.Resolve(...) with the configured Signing:Algorithm / Signing:Provider as explicit overrides (SPRINT_20260530_023 TASK-023-3). The other compliance signers (EvidenceSignatureService, Nis2SoaCryptoProviderSigner, CraTechFileCryptoProviderSigner, AttestorSigningService, CryptoDsseSigner, DoraInfoSharingSignerService, VexOverrideSigningKeyProvider) already resolve the provider through ICryptoProviderRegistry.ResolveSigner and may adopt the routine for their algorithm decision over time. See the call-site routing table in docs/implplan/SPRINT_20260530_023_*.md.
Legacy bypass (contained, opt-in only)
The legacy raw-crypto capsule signers Ed25519CapsuleSigner / EcdsaCapsuleSigner (src/EvidenceLocker/StellaOps.EvidenceLocker/Capsules/CapsuleSigner.cs) call BCL crypto directly and do not route through the abstraction. They are reachable only when EvidenceLocker:Signing:UseCryptoRegistry=false AND raw key material (KeyMaterial:Ed25519PrivateKeyBase64 / :EcPrivateKeyPem) is configured — an explicit operator opt-in, retained for backward-compat verification of previously-written envelopes. The live golden-path config sets UseCryptoRegistry=true (so the live chain uses CryptoRegistryCapsuleSigner), and the production startup gate (EvidenceLockerRuntimeConfigurationValidator.ValidateCapsuleSigner) refuses to ship unsigned capsules. Production deployments MUST keep UseCryptoRegistry=trueso no compliance signing path bypasses the abstraction.
4) Production-signing handoff (what SPRINT_20260530_024 must set)
To plug in a real production provider/key, you change configuration and key provisioning, not signer code. For each region:
4.1 Pick the profiles
Crypto:Compliance:ProfileId(orSTELLAOPS_CRYPTO_COMPLIANCE_PROFILE) — the compliance profile (fips/eidas/gost/sm/kcmvp/world). Drives the algorithm family the routine resolves.StellaOps:Crypto:Registry:ActiveProfile+:PreferredProviders— order the active region’s providers first (e.g.eu.eidas.soft,fips.ecdsa.soft,ru.openssl.gost,ru.pkcs11,cn.sm.remote.http, an HSM provider). The bundled regional compose files (devops/compose/docker-compose.compliance-{eu,china,russia}.yml) setSTELLAOPS_CRYPTO_PROFILEand the regional gates.
4.2 Enable the chosen provider’s gate
The software compliance providers are gated off by default and must be explicitly allowed (do not enable for a profile you are not certifying):
| Provider | Env gate | Algorithms |
|---|---|---|
eu.eidas.soft | EIDAS_SOFT_ALLOWED=1 | ES256/ES384 |
fips.ecdsa.soft | FIPS_SOFT_ALLOWED=1 | ES256/384/512 |
cn.sm.soft | SM_SOFT_ALLOWED=1 | SM2 |
ru.openssl.gost | STELLAOPS_CRYPTO_ENABLE_RU_OPENSSL (default on Linux) | GOST12-256/512 |
| PQ soft | PQ_SOFT_ALLOWED=1 | Dilithium3 / Falcon512 |
These software providers are non-certified (QSCD / FIPS module not enforced). Making any provider “qualified eIDAS” / certified-FIPS is a separate decision, out of scope here. Keep
production=falselabels for dev/sim providers.
4.3 Provision the key on the provider (not raw bytes in app config)
Per-service the signer reads a logical KeyId + optional Provider hint and asks the registry for that key. The production pattern is to pre-provision the key on the chosen provider/HSM (Vault-wrapped or PKCS#11), not to paste raw private bytes into config. For EvidenceLocker capsule sealing the relevant keys are:
EvidenceLocker:Signing:UseCryptoRegistry=true(keep this true in production).EvidenceLocker:Signing:KeyId= the logical key handle on the provider.EvidenceLocker:Signing:Provider= the provider id (e.g.eu.eidas.soft,fips.ecdsa.soft, an HSM provider). Leave empty to let the registry pick from the profile order.EvidenceLocker:Signing:Algorithm= optional explicit pin. If omitted, the routine fills the profile default forcapsule-seal.EvidenceLocker:Signing:AllowEphemeralKeyMaterial=falsein production (the ephemeral path is local-harness/dev only).
For the other signers, the analogous keys live under their own option sections (<service>:Signing:KeyId|Provider|Algorithm); the resolution semantics are identical because they all go through ICryptoProviderRegistry.ResolveSigner.
4.4 Production preconditions checklist
- [ ] Compliance + registry profiles set for the target region.
- [ ] Chosen provider’s env gate enabled; provider appears first in the registry preferred order.
- [ ] Signing key provisioned on the provider/HSM (Vault-wrapped or PKCS#11);
KeyId/Providerpoint at it. - [ ]
AllowEphemeralKeyMaterial=falseandAllowUnsignedCapsules=false. - [ ]
EvidenceLocker:Signing:UseCryptoRegistry=true(no legacy raw-crypto bypass). - [ ] No cloud-managed KMS configured as a default (sovereign/HSM only).
- [ ] JWKS export +
CryptoProviderMetricsshow the chosen provider id; a fixed-message sign/verify round-trips (evidence perdocs/security/crypto-profile-configuration.md).
5) Related docs
docs/ops/production-signing-runbook.md— the operator on-ramp: turns this handoff into a step-by-step release runbook for each signing surface (EvidenceLocker capsule key + ExportCenter NIS2 SoA key), with before/after config diffs and the JWKS / trust-root offline-verify procedure.docs/security/crypto-profile-configuration.md— region/profile selection + CLI.docs/modules/cryptography/architecture.md— abstraction & purpose model.docs/modules/cryptography/multi-profile-signing-specification.md— multi-profile signing.docs/compliance/eidas-sim-crypto-demo.md— theeu.eidas.softprovider demo.
