Regional signing productization (GOST / SM) — which tree signs, and how to turn it on
Status: Production holder composition implemented for PKCS#11 GOST and SmRemote HTTP; real CryptoPro/CSP, PKCS#11-token, and OSCCA SM-HSM liveness remains BLOCKED (AUTHCRYPTO-006/009, OSK-P6) Source: src/__Libraries/StellaOps.Cryptography.HostProviders/ (host registration), Signer src/Attestor/StellaOps.Signer/StellaOps.Signer.WebService/Program.cs, Attestor src/Attestor/StellaOps.Attestor/StellaOps.Attestor.WebService/AttestorWebServiceComposition.cs, Scanner Web src/Scanner/StellaOps.Scanner.WebService/Program.cs, Release Orchestrator src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Program.cs Sprints: SPRINT_20260704_010_Cryptography_regional_signing_productization (evidence-moat finding B4 / WS-6) and SPRINT_20260718_009_Authority_operator_signing_key_lifecycle (OSK-P6 personal-holder closure)
Why this page exists
The moat sentence promises regional-crypto signing (eIDAS / FIPS / GOST / SM). The GOST R 34.10-2012 and SM2 signing code has always been real (managed BouncyCastle), but before this sprint the deployed Signer registered only {default (ES256), offline.verification} and the Attestor only {default, Ed25519}. A gost / sm crypto profile therefore threw signing_not_supported at resolve time — the sovereignty claim was not deliverable out of the box.
This page collapses the single biggest source of confusion (there are two unrelated “crypto plugin” trees), documents how a regional profile is turned on end-to-end, and states the hardware requirement and the environment matrix precisely.
The two crypto plugin trees — which one feeds signing
There are two independent plugin families with similar names. Only one feeds the DSSE signing path.
Tree A — src/__Libraries/StellaOps.Cryptography.Plugin.* | Tree B — src/Cryptography/StellaOps.Cryptography.Plugin.* | |
|---|---|---|
| Contract | ICryptoProvider / ICryptoSigner (+ ICryptoProviderRegistry) | ICryptoCapability on CryptoPluginBase (PluginCapabilities.Crypto) |
| Feeds | DSSE signing & verification (Signer, Attestor, Authority) — the evidence chain | Credential-store AEAD, plugin-manifest capability catalogue, provider-pack packaging |
| GOST members | ru.openssl.gost (software sign), ru.bouncycastle.gost (verify-only), ru.pkcs11, ru.cryptopro.csp, ru.winecsp.http | StellaOps.Cryptography.Plugin.Gost |
| SM members | cn.sm.soft (software sign+verify outside Production; verification-only facade in Production), cn.sm.remote.http (HSM bridge) | StellaOps.Cryptography.Plugin.Sm |
| Turned on by | Registered into the host ICryptoProviderRegistry(this sprint) | Plugin-manifest / provider-pack bundle mount (STELLAOPS_CRYPTO_PROVIDERS, crypto-plugins-manifest.json) |
Rule of thumb: if you are debugging why a DSSE envelope isn’t GOST/SM-signed, you are in Tree A. Tree B’s manifest/pack config (the appsettings.crypto.russia.yaml Plugins:Enabled list) governs the credential-store/capability catalogue, not which ICryptoProvider the Signer/Attestor resolves for a DSSE signature.
Naming footgun inside Tree A
ru.openssl.gost does not call a native OpenSSL GOST engine. Despite the name (and the enginePath option in appsettings.crypto.russia.yaml, which the provider does not read), OpenSslGostProvider / OpenSslGostSigner sign with pure-managed BouncyCastle ECGost3410Signer over PEM key material. It is a software token, and is therefore treated as software-only for the Production guard below. The name refers to the OpenSSL PEM key format it loads, not a native engine.
How registration works now (RSP-1 / RSP-2)
StellaOps.Cryptography.HostProviders.RegionalCryptoRegistration.AddRegionalCryptoProviders(services, config, environment) is called by Signer, Attestor, Scanner Web, and Release Orchestrator before they construct their crypto registries:
- It reads the active crypto profile from
STELLAOPS_CRYPTO_PROFILE(the compose overlays already set this), falling back toStellaOps:Crypto:Profile.HostCryptoProfiles.Normalizecollapses deployment aliases (russia→gost,china/china-sm-hsm→sm, …) onto canonical families. - For a non-regional profile (
international/eidas/fips/pq) it registers nothing and returns an empty preferred-provider list — the base stack is byte-for-byte unchanged (regression testRegionalCryptoRegistrationTests.BaseProfile_RegistersNoRegionalProviders_AndStillResolvesEs256). - For
gostit always registersru.bouncycastle.gost(verify), addsru.openssl.gostonly in Development/Testing, and registers the productionru.pkcs11holder when a certified backend is confirmed. Forsm, Development/Testing getscn.sm.soft; Production gets a verification-onlycn.sm.softfacade plus thecn.sm.remote.httpsigning holder. The verification facade exposes no private-key or signing capability, even ifSM_SOFT_ALLOWEDor a software-provider option is set. Production also forces a live vendor-contract probe and explicit remote-key mapping;SkipProbe, contract-validation opt-out, and implicit key forwarding cannot be enabled by operator configuration. - The returned provider names are spliced into the host
ICryptoProviderRegistrypreferred order after{default, offline.verification}, so the base providers still win for ES256/Ed25519. - On the Signer, the host profile is also fed to
DsseSignerOptions.ActiveCryptoProfile, soCryptoDsseSignerpins the regional primary algorithm (GOST12-256 / SM2) and the envelope metadata (cryptoProfile+algorithmId) is honest — it never silently signs ES256 under a regional profile.
RSP-2 applies the same canonical profile-to-algorithm mapping to the two WS-2 paths that sign locally before Attestor submission. Scanner Web pins ScanAttestationOptions.Algorithm and resolves its configured SigningKeyId from the regional provider; the ES256 PEM importer is not used for regional keys. Release Orchestrator pins DsseSignerOptions.ActiveCryptoProfile / KmsAlgorithm and resolves the logical deployment-decision key from the regional registry. Policy verdict artifacts do not need a local regional registry: VerdictAttestationService submits the canonical predicate to Attestor, which is the signing authority for that path. Missing regional keys fail honestly; neither local host falls back to ES256.
On the Attestor these providers flow into AttestorSigningKeyRegistry as additional ICryptoProviders, so a configured GOST/SM signing-key entry resolves to the selected holder. Software entries use ru.openssl.gost / cn.sm.soft only in Development/Testing; Production entries use ru.pkcs11 (or an explicitly supplied ru.cryptopro.csp) / cn.sm.remote.http.
Environment matrix (the load-bearing trap)
Software regional signing is deliberately restricted, and two host guards must agree:
| Environment | Software GOST/SM provider registered? | Signer DSSE signer in use | Net result for regional signing |
|---|---|---|---|
| Development / Testing | Yes (HostProviders registers them) | HmacDsseSigner (Signer) / real signer (Attestor) | Attestor signs GOST/SM with software tokens; the Signer uses HMAC in local runtime |
| Staging / Production | No — fail-closed unless a certified backend is confirmed | CryptoDsseSigner (real DSSE) | Regional signing requires a certified backend; software is refused |
Why the split: the Attestor’s own AttestorSigningKeyRegistry (via CryptoProviderProductionGuard) allows the software providers cn.sm.soft / ru.openssl.gost only in Development/Testing. AddRegionalCryptoProviders matches that exactly, so a host never registers a provider its own guard would then reject. The Signer replaces its DSSE signer with HmacDsseSigner in local runtime (Development/Testing), so the Signer’s regional DSSE path is reachable only in Staging/Production — i.e. only with a certified backend. The software-token proof therefore lands on the Attestor in a Development/Testing environment; the Signer’s regional path is the certified-hardware path.
Production fail-closed (never ES256)
In Staging/Production, a regional profile without a confirmed certified backend throws a clear startup error naming the requirement (CryptoPro CSP ru.cryptopro.csp / PKCS#11 HSM ru.pkcs11 for GOST; OSCCA SM-HSM bridge cn.sm.remote.http for SM) and never falls back to ES256 — silently signing ES256 under a gost/sm profile would be a sovereignty violation. Confirm a certified backend with StellaOps:Crypto:CertifiedBackendConfirmed=true, or use a profile alias that names the HSM (e.g. china-sm-hsm, gost-hsm).
Overlay compose how-to
The regional overlays already set STELLAOPS_CRYPTO_PROFILE and mount the profile config for the Signer, Attestor, and dependent services:
# Russia / GOST
docker compose \
-f devops/compose/docker-compose.stella-ops.yml \
-f devops/compose/docker-compose.compliance-russia.yml up -d
# China / SM
docker compose \
-f devops/compose/docker-compose.stella-ops.yml \
-f devops/compose/docker-compose.compliance-china.yml up -d
For a software-token evaluation of the whole chain (non-production), add the eval overlay, which puts the Signer and Attestor in a Development runtime and enables the SM software-signing gate:
docker compose \
-f devops/compose/docker-compose.stella-ops.yml \
-f devops/compose/docker-compose.compliance-china.yml \
-f devops/compose/docker-compose.crypto-softtoken-eval.yml up -d
For Production, mount a certified backend and set StellaOps:Crypto:CertifiedBackendConfirmed=true (GOST via docker-compose.crypto-provider.cryptopro.yml; SM via docker-compose.crypto-provider.smremote.yml). Configure the signing-key entries:
- Attestor GOST:
Attestor:Signing:Keys[] = { keyId, provider: "ru.cryptopro.csp" | "ru.pkcs11", algorithm: "GOST12-256", providerKeyId }(software-token dev/test usesprovider: "ru.openssl.gost"withStellaOps:Crypto:OpenSsl:Keys[]PEM material). The portable Production host bindsStellaOps:Crypto:Pkcs11:Keys[](keyId,libraryPath, slot/token selector, private/public labels, PIN environment-variable name, mechanism, and certificate reference); CryptoPro remains an explicitly supplied distribution-specific provider. - Attestor SM:
Attestor:Signing:Keys[] = { keyId, provider: "cn.sm.remote.http", algorithm: "SM2", providerKeyId }, withStellaOps:Crypto:SmRemote = { baseAddress, requireVendorAdapterContract, expectedAdapterKind, expectedContractVersion, keys[] }.cn.sm.softremains Development/Testing only. The Production host forcesrequireVendorAdapterContract=true,skipProbe=false, andallowImplicitKeyMapping=false;keys[]must therefore map every logical personal key explicitly.
Hardware requirements (BLOCKED liveness)
| Profile | Production signing backend | Status |
|---|---|---|
| GOST | CryptoPro CSP (ru.cryptopro.csp, Windows, StellaOpsEnableCryptoPro=true build) or a PKCS#11 GOST HSM (ru.pkcs11, e.g. Rutoken/JaCarta) | Registration wired; hardware liveness BLOCKED (AUTHCRYPTO-006) |
| SM | OSCCA-certified SM HSM via the SmRemote bridge (cn.sm.remote.http) | Production holder and verification-only software facade wired; hardware liveness BLOCKED (AUTHCRYPTO-009 / OSK-P6) |
The software providers (ru.openssl.gost, cn.sm.soft) are for non-production evaluation only and must never be labelled production signing proof.
To enable regional signing in production (further actions, operator)
- Procure the certified backend for the target region: a CryptoPro CSP host or a PKCS#11 GOST HSM (Russia/GOST), or an OSCCA-certified SM HSM reachable by the SmRemote bridge (China/SM).
- Build/deploy the host with the provider enabled — GOST CryptoPro needs the
StellaOpsEnableCryptoPro=truebuild; PKCS#11 / SmRemote need the HSM endpoint + credentials. - Select the profile via
STELLAOPS_CRYPTO_PROFILE(orStellaOps:Crypto:Profile) —gost/sm(aliasesrussia/chinanormalize). In Production the registry fails closed if the profile is set but no certified backend resolves (never a silent ES256 fallback). - Apply the overlay compose (
# Russia / GOST,# China / SMblocks above) and provision the HSM key material per the PKCS#11 deployment health runbook. - Live proof is BLOCKED on hardware access (AUTHCRYPTO-006 / AUTHCRYPTO-009) — the hardware sign→verify→tamper roundtrip must be captured against the real CSP/HSM, not a software token. Tracked in
docs/implplan/SPRINT_20260704_010_Cryptography_regional_signing_productization.md(RSP-3) and the program closeout sprint.
Proof
- Live Attestor software-token proof (2026-07-16): scan, policy-verdict, and deployment-decision envelopes were SM2-signed by
cn.sm.soft, verified withstella attest verify --algorithm SM2, rejected after a valid-JSON payload tamper, and the base service was restored healthy. Evidence:2026-07-16-regional-signing-live. - Software-token DSSE roundtrip (sign → verify → tamper-fail) for GOST-256, GOST-512, and SM2:
src/__Libraries/__Tests/StellaOps.Cryptography.Tests/RegionalDsseRoundtripTests.cs— exercises the realOpenSslGostSignerandSmSoftCryptoProvider. - Host registration + fail-closed behaviour:
src/__Libraries/__Tests/StellaOps.Cryptography.Tests/RegionalCryptoRegistrationTests.cs. - CLI verification accepts
--algorithm ES256|PS256|GOST12-256|GOST12-512|SM2; regional public keys must be SubjectPublicKeyInfoPUBLIC KEYPEM. See the CLI architecture.
Related documentation
- Provider-selection routine:
signing-provider-selection.md - Multi-profile signing:
multi-profile-signing-specification.md - GOST provider validation:
gost-provider-validation.md - SM remote adapter contract:
sm-remote-vendor-adapter-contract.md - HSM PKCS#11 deployment health:
hsm-pkcs11-deployment-health.md
