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
- Provider registry binding for Authority signing keys.
- JWKS export rules (which keys are exposed and how
kidis computed). - Signing profile selection rules (how Authority chooses keys/algorithms for different operations).
- Determinism requirements for JWKS JSON and key identifiers.
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
- Provider: a crypto implementation or backend (software, HSM, KMS) identified by
provider_id. - Key: a signing key (or key handle) identified by
key_idwithin a provider. - Profile: a named signing policy that constrains algorithm choices and key selection.
kid: the key identifier exposed in JWT headers and JWKS entries.
3) Provider registry (required fields)
Each registered Authority signing key MUST have, at minimum:
provider_id: stable provider identifier (string).key_id: stable key identifier within the provider (string).alg: signing algorithm identifier as used by JWT/JWS (string).usage: at leastsig(signing). If other usages are supported, they must be explicit.tenant_scope(optional): when set, the key is only valid for the specified tenant(s); when absent, it is platform-wide.
4) JWKS export rules
4.1 Which keys are exported
- JWKS MUST include only keys usable for token verification by downstream services.
- Exported keys MUST be filtered by:
- profile rules (do not export keys that cannot be selected by any active profile), and
- tenant scope (do not export tenant-scoped keys into other tenants’ JWKS views if per-tenant JWKS is supported).
- The global
/jwksendpoint exports only platform-wide configured signing or verification keys:signing.activeKeyId, explicitly configuredsigning.additionalKeys, and enabled notification ack-token active/additional keys. Provider-held keys that are not configured for Authority signing or ack-token verification are not exported, and keys with tenant-scope metadata are withheld from the global view until a per-tenant JWKS endpoint exists.
4.2 kid composition (deterministic)
kid MUST be stable across restarts and across equivalent deployments.
Recommended minimum rule:
- Compute
kidfrom the public key material plus the profile discriminator:kid = sha256(public_key_spki_bytes || ":" || profile_id)
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)
- JWKS JSON MUST be canonicalized for reproducible hashing and offline verification:
- stable key ordering (sort by
kidascending), - stable field ordering within a key object,
- no incidental whitespace dependence for hashing or signatures.
- stable key ordering (sort by
5) Signing profiles
Authority SHOULD define named profiles (examples: default, ru-gost, pq-experimental) that map:
- allowed algorithms (
algallowlist), - allowed providers (provider allowlist),
- selection precedence when multiple keys match.
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:
signing.activeKeyIdselects the activekid.signing.keySourceloads or registers the provider key/handle throughIAuthoritySigningKeySource.signing.providerselects the regional crypto provider throughICryptoProviderRegistry.ICryptoSigner.ExportPublicJsonWebKey()MUST return a public JWK with matchingkid,alg, andkty; this is the verification key served by/jwks.ICryptoSigner.SignAsync()MUST return a signature compatible with the configured JWSalg.- Every Authority signing path MUST fail closed if the resolved signer
KeyIdorAlgorithmIddiffers from the configured key and algorithm for that path. - Provider hints are binding where configured. A missing or unsupported configured provider must fail startup, rotation, or issuance instead of falling back to another compatible provider.
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
- Rotation MUST preserve the ability to verify previously issued (unexpired) tokens.
- Revocation exports and JWKS exports MUST be consumable offline (cacheable and distributable in Offline Kit bundles).
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:
| Key | Type | Default | Meaning |
|---|---|---|---|
authority:plugins:enforceSignatureVerification | bool | true | When 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:provider | string | offline-dev-rsa-sha256 | Verifier provider. One of offline-dev-rsa-sha256 (air-gap default), cosign, or none. |
authority:plugins:signature:publicKeyPath | string | (unset) | Trusted public key used to verify detached plug-in signatures. |
authority:plugins:signature:cosignPath | string | (unset) | Path to the cosign executable (cosign provider only). |
authority:plugins:signature:useRekorTransparencyLog | bool | false | Consult Rekor (cosign provider only). |
authority:plugins:signature:allowUnsigned | bool | false | Treat 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: cosign → CosignPluginVerifier, offline-dev-rsa-sha256 → OfflineDevRsaSha256PluginVerifier, none → null. 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:
standardis host-owned and cannot be replaced by an external assembly path.- Runtime IdP providers (
oidc,saml,ldap,harness) must declare the cataloged assembly identity for that provider type. - Unknown provider types and future MFA provider types are rejected before load.
- TOTP, WebAuthn, email, and SMS MFA rows in
devops/etc/authority/plugins/registry.yamlare disabled until those provider boundaries exist; MFA assurance currently comes from loaded IdP plugins that report themfacapability.
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.
10) Related docs
- Authority module dossier:
docs/modules/authority/architecture.md - Authority operations (key rotation, backup/restore):
docs/modules/authority/operations/ - Gateway tenant/auth header contract:
docs/api/gateway/tenant-auth.md - Cryptography architecture (on-prem-first KEK posture, provider catalog):
docs/modules/cryptography/architecture.md
