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.*
ContractICryptoProvider / ICryptoSigner (+ ICryptoProviderRegistry)ICryptoCapability on CryptoPluginBase (PluginCapabilities.Crypto)
FeedsDSSE signing & verification (Signer, Attestor, Authority) — the evidence chainCredential-store AEAD, plugin-manifest capability catalogue, provider-pack packaging
GOST membersru.openssl.gost (software sign), ru.bouncycastle.gost (verify-only), ru.pkcs11, ru.cryptopro.csp, ru.winecsp.httpStellaOps.Cryptography.Plugin.Gost
SM memberscn.sm.soft (software sign+verify outside Production; verification-only facade in Production), cn.sm.remote.http (HSM bridge)StellaOps.Cryptography.Plugin.Sm
Turned on byRegistered 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:

  1. It reads the active crypto profile from STELLAOPS_CRYPTO_PROFILE (the compose overlays already set this), falling back to StellaOps:Crypto:Profile. HostCryptoProfiles.Normalize collapses deployment aliases (russiagost, china/china-sm-hsmsm, …) onto canonical families.
  2. 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 test RegionalCryptoRegistrationTests.BaseProfile_RegistersNoRegionalProviders_AndStillResolvesEs256).
  3. For gost it always registers ru.bouncycastle.gost (verify), adds ru.openssl.gost only in Development/Testing, and registers the production ru.pkcs11 holder when a certified backend is confirmed. For sm, Development/Testing gets cn.sm.soft; Production gets a verification-only cn.sm.soft facade plus the cn.sm.remote.http signing holder. The verification facade exposes no private-key or signing capability, even if SM_SOFT_ALLOWED or 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.
  4. The returned provider names are spliced into the host ICryptoProviderRegistry preferred order after {default, offline.verification}, so the base providers still win for ES256/Ed25519.
  5. On the Signer, the host profile is also fed to DsseSignerOptions.ActiveCryptoProfile, so CryptoDsseSigner pins 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:

EnvironmentSoftware GOST/SM provider registered?Signer DSSE signer in useNet result for regional signing
Development / TestingYes (HostProviders registers them)HmacDsseSigner (Signer) / real signer (Attestor)Attestor signs GOST/SM with software tokens; the Signer uses HMAC in local runtime
Staging / ProductionNo — fail-closed unless a certified backend is confirmedCryptoDsseSigner (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:

Hardware requirements (BLOCKED liveness)

ProfileProduction signing backendStatus
GOSTCryptoPro 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)
SMOSCCA-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)

  1. 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).
  2. Build/deploy the host with the provider enabled — GOST CryptoPro needs the StellaOpsEnableCryptoPro=true build; PKCS#11 / SmRemote need the HSM endpoint + credentials.
  3. Select the profile via STELLAOPS_CRYPTO_PROFILE (or StellaOps:Crypto:Profile) — gost / sm (aliases russia / china normalize). In Production the registry fails closed if the profile is set but no certified backend resolves (never a silent ES256 fallback).
  4. Apply the overlay compose (# Russia / GOST, # China / SM blocks above) and provision the HSM key material per the PKCS#11 deployment health runbook.
  5. 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