Authority Crypto Provider and JWKS Contract

This document defines the minimum contract Authority must satisfy to support sovereign crypto providers (FIPS, GOST, SM, PQ-ready modes where enabled) while keeping exports deterministic and offline-verifiable.

1) Scope

This contract is written so downstream consumers (Gateway, services, CLI, Offline Kit tooling) can validate tokens and rotate trust roots without depending on Authority’s internal database schema.

2) Terms

3) Provider registry (required fields)

Each registered Authority signing key MUST have, at minimum:

4) JWKS export rules

4.1 Which keys are exported

4.2 kid composition (deterministic)

kid MUST be stable across restarts and across equivalent deployments.

Recommended minimum rule:

If Authority exposes both an all-profiles JWKS and a per-profile JWKS, the kid value must remain stable in both views.

4.3 Canonical JWKS JSON (deterministic)

5) Signing profiles

Authority SHOULD define named profiles (examples: default, ru-gost, pq-experimental) that map:

Profile selection MUST be deterministic given the same registry and request context.

5.1) Authority token and detached-signature signing contract

Production token issuance and detached-signature issuance use the same Authority signing registry as JWKS and revocation exports. This includes OpenIddict JWTs, revocation bundles, Vulnerability Explorer workflow and attachment tokens, Vulnerability Explorer permalinks, and notification ack tokens:

If any of those checks fail outside Development/Testing, Authority startup fails closed. It must never silently replace the configured provider with an ephemeral OpenIddict signing key.

6) Rotation and revocation expectations

8) Plugin signature enforcement (fail-closed)

Authority loads identity-provider plug-ins (OIDC / SAML / LDAP backends) through a PluginHost that scans a mounted plugin directory. Loading executable code from a mounted directory is a supply-chain boundary, so the host is fail-closed: a forged or unsigned bundle dropped into the plugin directory must not load.

8.1 Configuration surface

AuthorityPluginSettings (src/__Libraries/StellaOps.Configuration/AuthorityPluginSettings.cs) binds from the authority:plugins:* configuration keys and carries the enforcement contract:

KeyTypeDefaultMeaning
authority:plugins:enforceSignatureVerificationbooltrueWhen true, PluginHost rejects any discovered bundle that lacks a valid detached signature, and rejects all bundles when no usable verifier is configured.
authority:plugins:signature:providerstringoffline-dev-rsa-sha256Verifier provider. One of offline-dev-rsa-sha256 (air-gap default), cosign, or none.
authority:plugins:signature:publicKeyPathstring(unset)Trusted public key used to verify detached plug-in signatures.
authority:plugins:signature:cosignPathstring(unset)Path to the cosign executable (cosign provider only).
authority:plugins:signature:useRekorTransparencyLogboolfalseConsult Rekor (cosign provider only).
authority:plugins:signature:allowUnsignedboolfalseTreat a bundle with no detached .sig as valid.

NormalizeAndValidate() rejects the contradictory, insecure combination enforceSignatureVerification = true and signature:allowUnsigned = true at startup, so an operator cannot silently disable the gate while leaving enforcement nominally “on”. To permit unsigned bundles you must explicitly set enforceSignatureVerification = false.

Provider none yields no verifier; combined with the default enforceSignatureVerification = true this is the strongest posture — the host admits zero bundles. This is the intended behaviour when an air-gapped site ships only the host-owned standard plug-in and wants the directory scan to admit nothing.

8.2 Wiring (source of truth)

BuildPluginHostOptions(...) in src/Authority/StellaOps.Authority/StellaOps.Authority/Program.cs sets both the enforcement flag and the concrete verifier on the PluginHostOptions:

hostOptions.EnforceSignatureVerification = options.Plugins.EnforceSignatureVerification;
hostOptions.SignatureVerifier = AuthorityPluginSignatureVerifierFactory.CreateVerifier(
    options.Plugins.Signature ?? new AuthorityPluginSignatureOptions());

AuthorityPluginSignatureVerifierFactory (src/Authority/StellaOps.Authority/StellaOps.Authority/Plugins/AuthorityPluginSignatureVerifierFactory.cs) maps the configuration-bound AuthorityPluginSignatureOptions onto an IPluginSignatureVerifier: cosignCosignPluginVerifier, offline-dev-rsa-sha256OfflineDevRsaSha256PluginVerifier, nonenull. This mirrors the verifier selection used by the Excititor WebService/Worker plugin loaders so the same offline-dev-rsa-sha256 / cosign signing material verifies plug-ins across services.

The factory lives in the Authority host (not in the broadly consumed StellaOps.Configuration library) so only the host carries the dependency on the plugin-security primitives (StellaOps.Plugin.Security); the settings POCO stays dependency-free.

8.3 Scope and the host-owned standard plug-in

This gate applies to bundles discovered by the directory scan. The host-owned standard identity provider is referenced from the host output directory (it is not discovered by the scanned plugin directory), so it is unaffected by signature enforcement. With enforcement on, the admission outcomes are: a correctly signed bundle is admitted; an unsigned bundle, a tampered bundle, or a configured provider of none admits zero bundles (AuthorityPluginSignatureEnforcementTests / AuthorityPluginSignatureOptionsTests).

8.4 Catalog-bound provider identity

Signature verification proves bundle integrity, not that an operator descriptor selected the right provider implementation for its declared provider type. AuthorityProviderAdmission therefore binds the descriptor type to the cataloged assembly identity before registrar activation:

9) On-premise crypto-provider registry posture (no cloud-managed KMS)

Authority resolves its regional crypto provider through ICryptoProviderRegistry (§5.1), whose runtime catalogue is sourced from the mounted crypto registry at devops/etc/plugins/crypto/registry.yaml. StellaOps is self-hosted and sovereign-first, so no cloud-managed KMS adapter is registered or enabled as a default.

As of 46de746725 the cloud-managed KMS rows were removed from registry.yaml: the aws-kms and azure-keyvault provider entries are gone, and the file carries an explicit note that those adapters are not registered here. The on-premise KEK / HSM providers that remain are pkcs11 (in-process PKCS#11 HSM) and hashicorp-vault — the flagship KEK / secret backend (both ship enabled: false and are turned on per deployment). This aligns the registry with CryptoProviderPackCatalog, where the cloud-managed adapters are catalogued as ownership=Future, cloudManaged=true, productionAllowed=false and are denied admission at runtime.

Operator implication: there is no supported configuration in which Authority’s signing/KEK trust root is held by AWS KMS, GCP KMS, or Azure Key Vault. Use the PKCS#11 HSM provider or HashiCorp Vault. The separate signing-key IKmsClient facade (Cryptography module) still ships optional AWS/GCP signing adapters, but those are an explicit, non-default operator opt-in and are unrelated to this crypto-provider registry. See Cryptography architecture §0 / §4.9 for the full on-prem-first posture.