Platform Cryptography and Compliance Defaults
Scope
The setup wizard owns installation-scoped cryptography and compliance evidence defaults. It does not write per-tenant compliance overrides.
The merged crypto setup step is titled Cryptography in the UI and accepts both crypto and cryptography as API step IDs during the compatibility window. It captures:
- regional crypto profile:
international,eidas,russian, orchinese; - FIPS-140 mode as a sub-toggle for the International profile;
- optional NIS2, DORA, and CRA compliance evidence support flags.
Installation Settings
The wizard and CLI write the following keys to platform.environment_settings.
| Key | Values | Default |
|---|---|---|
Crypto:Region | international, eidas, russian, chinese | international |
Crypto:FipsMode | true, false | false |
Compliance:Nis2:Enabled | true, false | false |
Compliance:Dora:Enabled | true, false | false |
Compliance:Cra:Enabled | true, false | false |
Legacy lowercase setup keys such as crypto.provider and compliance.nis2.enabled are accepted by the setup apply path and converged to the canonical keys above. New clients must write the canonical keys.
Soft-Probe Contract
Compliance toggles are accepted even when adjacent systems are not configured yet. The setup service records warning checks instead of blocking apply:
| Toggle | Warning condition | Remediation route |
|---|---|---|
| NIS2 | Authority compliance-profile URL is missing | /setup/identity-access |
| DORA | Notify DORA information-sharing channel is missing | /setup/notifications |
| CRA | Manufacturer mailbox or intake key is missing | /setup/system/cryptography |
Warnings must be surfaced as operator remediation messages. They do not change the step apply result to failure.
CLI Surface
The CLI uses the Platform env-settings admin endpoint and does not maintain a parallel local state file for these defaults.
stella crypto provider set <international|eidas|russian|chinese>
stella crypto provider show
stella crypto algorithm set --provider international --signing ecdsa-p256
stella crypto fips enable
stella crypto fips disable
stella crypto status
stella compliance nis2 enable
stella compliance nis2 disable
stella compliance dora enable
stella compliance dora disable
stella compliance cra enable
stella compliance cra disable
stella compliance status
Reads call GET /platform/envsettings/db/. Writes call PUT /platform/envsettings/db/{key} with the canonical key and a string value. Commands include X-Tenant-Id for consistency with the Platform admin API, but the current store is installation-scoped by key.
Per-Tenant Overrides
The Configuration page at /setup/system/cryptography resolves per-tenant compliance-pack state over the installation defaults above.
Persistence stays in platform.tenant_compliance_profile. Migration 069_TenantComplianceProfile.sql created that table as a single profile row per tenant with profile_id constrained to world, fips, gost, sm, kcmvp, or eidas; migration 070_TenantCompliancePackOverrides.sql extended the same row with nullable nis2_enabled, dora_enabled, and cra_enabled columns; migration 074_TenantCompliancePackOverridesCraSplit.sql (OPS-039) split the single CRA column into cra_product_security_enabled and cra_technical_documentation_enabled to match CRA Regulation (EU) 2024/2847 two-instrument conformity model and the frontend assurance-frameworks tiles.
NULL means the tenant has no override for that pack. true and false are explicit tenant overrides. This is intentional: clearing an override must fall back to a true installation default instead of hard-coding false.
| Effective key | Resolution |
|---|---|
Compliance:Nis2:Enabled | tenant nis2_enabled when non-null, else installation default, else false |
Compliance:Dora:Enabled | tenant dora_enabled when non-null, else installation default, else false |
Compliance:Cra:ProductSecurity:Enabled | tenant cra_product_security_enabled when non-null, else installation default, else false |
Compliance:Cra:TechnicalDocumentation:Enabled | tenant cra_technical_documentation_enabled when non-null, else installation default, else false |
The legacy cra_enabled column is retained for rollback safety but is no longer read by the catalog. Migration 074 fans any historic single-CRA override out into both new columns on apply.
Backend API:
Endpoint shape is generated in Platform API Reference. Relevant operations:
GET /api/v1/admin/crypto-providers/compliance-overridesPUT /api/v1/admin/crypto-providers/compliance-overrides/{packId}DELETE /api/v1/admin/crypto-providers/compliance-overrides/{packId}GET /api/v1/admin/crypto-providers/compliance-profile/PUT /api/v1/admin/crypto-providers/compliance-profile/GET /api/v1/admin/crypto-providers/compliance-profile/operator-signing-enrollment
The tenant crypto profile remains the single-value profile_id on the same table and is managed through the compliance-profile operations listed in the generated block above.
The operator-signing projection is deliberately narrower than the general crypto-provider read surface. It derives the canonical decision-signing algorithm through SignatureProviderSelection and returns { profileId, algorithmId, providerName, browserEnrollmentSupported, enrollmentMethod, enrollmentRoute, dataAsOf }. providerName is the exact verification-provider identity to persist beside an enrolled public key, not necessarily the holder that performs private-key signing. Catalog aliases are narrowed to pluginProviderId; profile defaults are default (world), fips.ecdsa.soft (FIPS), ru.bouncycastle.gost (GOST), and cn.sm.soft (SM). The GOST/SM verification adapters expose public-material verification only; private signing stays with the CLI/smartcard/HSM holder selected outside this projection (for example PKCS#11, CryptoPro, or SmRemote). The response exposes no provider health, preference list, secret, or key material. World and FIPS return browser-webcrypto//administration/profile; GOST and SM return cli-smartcard//docs/operator-signing-keys#cli-smartcard. IssuerDirectory performs the final live-registry capability check, so an unmounted or incompatible provider fails closed during enrollment rather than being silently replaced by another route. The dedicated OperatorSigningEnrollmentRead policy accepts the narrow authority:signing-keys.enroll scope (as well as crypto:read or ops.admin), so granting enrollment does not grant the rest of the crypto catalog.
Runtime Plugin Boundary
Current source-closure evidence (2026-06-06) lives under docs/qa/feature-checks/runs/pluginized-compose/platform-regional-crypto-boundary-20260606/. The scoped Platform WebService ProjectReference audit scans src/Platform/StellaOps.Platform.WebService/StellaOps.Platform.WebService.csproj and reports zero optional regional provider implementation violations.
When Crypto:CredentialStore:PrimitiveRouter=plugin is enabled, Platform now uses ConnectorCredentials/RegionalCryptoPluginServiceCollectionExtensions.cs to load required mounted bundles from Crypto:CredentialStore:RegionalPlugins:RootPath (default /app/plugins/crypto) and Crypto:CredentialStore:RegionalPlugins:Profile (default base). Each provider bundle is admitted through the shared StellaOps.Plugin.Security.SignedRuntimePluginAdmission chain before .NET loads any assembly. The configured trust root comes from Crypto:CredentialStore:RegionalPlugins:TrustRootPath, then Crypto:RuntimePlugins:TrustRootPath, then the Platform env overrides, with a default of /app/etc/certificates/trust-roots/plugins/crypto/cosign.pub.
Each provider bundle is expected at <root>/<profile>/<plugin-id>/manifest.json, with the current required IDs:
| Plugin ID | Credential AEAD capability |
|---|---|
com.stellaops.crypto.sm | credential-aead:SM4-GCM |
com.stellaops.crypto.fips | credential-aead:AES-256-GCM |
com.stellaops.crypto.gost | credential-aead:GOST-GCM |
The manifest is authoritative for this host seam and must declare id equal to the bundle directory name, module crypto, the selected profile, contract version runtime-bundle.v1, the required credential AEAD capability, a bundle-relative assembly.path, assembly.sha256, and a conventional detached signature at <assembly>.sig (optionally recorded as assembly.signaturePath). entryTypes must identify exactly one concrete public ICryptoCapability when the assembly exposes more than one candidate. Platform validates identity, profile, contract version, declared capability, relative path containment, assembly SHA-256, and detached RSA/SHA-256 signature against the trust root before registering the provider behind the existing ICredentialAeadPluginAdapter bindings.
Platform-scoped acceptance evidence (2026-06-09) lives under docs/qa/feature-checks/runs/pluginized-compose/platform-regional-crypto-admission-20260609/. It covers signed good bundles, bad-signature fail-closed rejection, adjacent regional crypto dispatch tests, and the scoped ProjectReference audit. It does not claim DevOps compose/read-only mount or image-payload audit proof; shared compose and packaging files remain a separate acceptance lane.
All three endpoints require the existing platform.crypto.profile.admin policy (crypto:profile:admin or ops.admin). A tenantId query parameter is optional; when present it must match the envelope-bound tenant or Platform returns 404 tenant_not_found.
Writes and deletes emit platform.update_compliance_pack; audit details carry { tenant_id, pack_id, before, after }. The mutation response carries the same before/after values plus the merged configuration. The resolver also returns soft-prerequisite warnings with remediation routes so the UI can show non-blocking yellow strips for gaps such as missing NIS2 Authority mapping. These warnings are setup-readiness hints only. They are not live proof that NIS2, DORA, or CRA workflows produced compliant evidence.
Each pack also returns workflowReadiness and readinessGaps:
workflowReadiness | Meaning |
|---|---|
disabled | The effective pack value is false; no workflow evidence is expected. |
configuration_required | The pack is enabled, but one or more setup prerequisites are missing. |
evidence_pending | Setup prerequisites are present, but live upstream evidence still has to prove the workflow. |
For NIS2, an enabled pack always carries readiness gaps for live effectiveness evidence and reachability evidence until those upstream workflows provide their own proof. The Platform setup/profile endpoints must not be used as a compliance conclusion or as a substitute for live NIS2 effectiveness, reachability, Notify delivery, or signed export evidence.
CLI mirror:
stella crypto profile set --tenant <id> <world|fips|gost|sm|kcmvp|eidas>
stella crypto profile show --tenant <id>
stella compliance nis2 enable --tenant <id>
stella compliance nis2 disable --tenant <id>
stella compliance dora enable --tenant <id>
stella compliance dora disable --tenant <id>
stella compliance cra enable --tenant <id>
stella compliance cra disable --tenant <id>
stella compliance status --tenant <id>
Without --tenant, stella compliance ... continues to manage the installation defaults in platform.environment_settings. With --tenant, the commands call the override/profile endpoints above and do not write the installation-default keys.
Secret Provider Registry
Where the regional-crypto algorithm routing above decides how a secret is sealed, the secret provider decides where it lives and which backend resolves it. Platform is one of the three services that wire AddRoutingSecretProvider and resolve secret references by URL (the others are StellaOps.Integrations.WebService and StellaOps.ReleaseOrchestrator.WebApi).
- Built-in floor (default). The
builtinprovider is the mandatory, always-on, tenant-scoped store (ADR-031 §D1). A plain install uses it and only it. Vault / OpenBao are optional, opt-in external backends — there is no cloud-managed default. - Multi-provider registry (ADR-032).
Crypto:SecretProviders[]declares a registry of provider instances ({id, kind, address, auth}, kind ∈builtin | vault | openbao). A single install can mixbuiltin+ externalvault/openbaoinstances simultaneously (both external kinds are operator-run; the in-repo OpenBao compose profile is test-only dev/CI tooling — ADR-031 §D5 as amended).Crypto:SecretProvider:DefaultSealTargetpicks where NEW secrets seal when a reference omits an explicit target. The legacy singleCrypto:SecretProvider:Backendis honoured as a one-entry back-compat shim when no registry is declared. - URL secret references. Every stored secret is a non-secret URL pointer,
<scheme>://[<provider-id>/]<path>[#<version>](e.g.vault://prod-vault/secret/app/db/password#5,openbao://bao/kv/notify/smtp/password,builtin://integration:<guid>).RoutingSecretProviderdispatches each resolve/seal by parsing the URL. An unknown scheme or unknown provider-id is rejected loudly (fail-closed), never a silent fall-through. - Master key at setup. The setup wizard’s Master Key step gates on a probe-only seal/open round-trip against the resolved KEK (
POST /api/v1/admin/setup/crypto/master-key/probe), so boot fails closed rather than lazily on first seal/open. The KEK lifecycle itself is governed by ADR-007 (see the KEK rotation runbook). - Cross-service re-seal authorization. Platform owns the installation-level KEK rotation coordinator and fans work out to the owning Integrations and ReleaseOrchestrator stores. Its named clients forward the current validated operator Bearer token when present, or relay the gateway-signed identity envelope only after Platform verified it as
StellaRouterEnvelope. Each downstream service independently requires KEK read scope for population/progress and KEK rotate scope for start/abort (with the existingops.adminescape hatch), and requires an authenticated principal so network bypass alone is insufficient. The default internal transport is HTTP, not mTLS; certificate-bound direct listeners and client credentials remain required before an mTLS claim is valid.
Admin / CLI surface:
GET /api/v1/admin/crypto/secret-providers # enumerate registry (crypto:read)
GET /api/v1/admin/crypto/secret-providers/{id}/paths # browse path NAMES only
GET /api/v1/admin/setup/crypto/secret-provider/status # backend + KEK fingerprint
POST /api/v1/admin/setup/crypto/master-key/probe # seal/open probe (crypto:kek:rotate)
stella crypto secret-provider list | status | validate | paths --provider <id>
stella crypto master-key probe | generate | import
These admin endpoints never return secret bytes (path names + non-secret KEK fingerprints only). Provider enumeration is partial-safe: if one configured provider’s non-secret describe probe fails, the endpoint still returns 200, marks only that provider reachable: false, and does not serialize the probe exception or its potentially sensitive detail. See:
- Runbook: Multi-provider secret registry
- CLI: crypto command guide — secret-provider / master-key
- ADRs: ADR-031 (unified secret provider), ADR-032 (URL secret references)
- Spec: secret-reference URL scheme
- Live proof (2026-06-22, builtin + 2× Vault + test-only OpenBao):
docs/qa/feature-checks/runs/2026-06-22-secret-provider-live/
