Skip to main content

Signing Key Policy

The SigningKeyPolicy managed configuration section chooses the key algorithm for each of a Trust Domain's signing authorities. These are the private keys the Trust Domain Server uses to sign the credentials it issues.

Every Trust Domain deployment has a signing key set containing up to four authority keys:

Signing authoritySignsDefault algorithm
x509X.509-SVIDsrsa-2048 (RSA, 2048-bit)
jwtJWT-SVIDsrsa-2048 (RSA, 2048-bit)
witWIT-SVIDs (only when WIT-SVID issuance is enabled)ec-p256 (ECDSA, NIST P-256)
oauthOAuth access tokensrsa-4096 (RSA, 4096-bit)

Those defaults apply to any authority you leave unset.

SigningKeyPolicy governs the keys that sign credentials. To choose the algorithm of the leaf key pairs your workloads receive, use svidKeyType in the SVIDIssuancePolicy section.

A change does not take effect immediately

A key's algorithm is fixed for the lifetime of the key set that contains it. After you edit the policy, the configuration and the running keys differ until the next rotation. See Rotation timing before you change anything.

Policy template

The complete section, with every authority set explicitly:

section: SigningKeyPolicy
schema: v1
spec:
x509:
# Key algorithm for the authority that signs X.509-SVIDs. Default: rsa-2048.
signingKeyType: rsa-2048
jwt:
# Key algorithm for the authority that signs JWT-SVIDs. Default: rsa-2048.
signingKeyType: rsa-2048
wit:
# Key algorithm for the authority that signs WIT-SVIDs. Default: ec-p256.
# Applies only when WIT-SVID issuance is enabled.
signingKeyType: ec-p256
oauth:
# Key algorithm for the authority that signs OAuth access tokens. Default: rsa-4096.
signingKeyType: rsa-4096

Every block and every field is optional.

Accepted values

Any authority may be set to any of these four key types:

ValueAlgorithm
rsa-2048RSA, 2048-bit
rsa-4096RSA, 4096-bit
ec-p256ECDSA, NIST P-256
ec-p384ECDSA, NIST P-384

Scope

SigningKeyPolicy is configured on the entire Trust Domain. Each Trust Domain deployment must use the same SigningKeyPolicy.

Key sets, however, are per deployment, and each deployment rotates on its own schedule. Every deployment resolves the same policy, but each one picks it up at its own next key set preparation. The end state is uniform. See Rotation timing.

Applying the policy

Apply the section with spirlctl config set trust-domain. The Trust Domain ID can be found via spirlctl trust-domain info <trust-domain-name>.

Changing one authority

To change only the X.509 signing key and leave jwt, wit, and oauth on their defaults, omit their blocks entirely:

spirlctl config set trust-domain --id $TRUST_DOMAIN_ID - <<EOF
section: SigningKeyPolicy
schema: v1
spec:
x509:
signingKeyType: ec-p384
EOF

Moving from RSA to EC

To move X.509, JWT, and WIT to EC-P384 while leaving OAuth on its RSA default:

section: SigningKeyPolicy
schema: v1
spec:
x509:
signingKeyType: ec-p384
jwt:
signingKeyType: ec-p384
wit:
signingKeyType: ec-p384
# oauth omitted to keep rsa-4096, which has wider verifier support among
# OAuth and MCP relying parties.

Apply it:

spirlctl config set trust-domain --id $TRUST_DOMAIN_ID signing-key-policy.yaml

When no policy is configured

A Trust Domain with no SigningKeyPolicy uses the default key algorithm for every signing authority. Running every authority on its default is a fully supported configuration.

Four ways of saying "use the default", all equivalent:

What you writeEffect
No SigningKeyPolicy section at allAll four authorities on their defaults
Section present, authority block omittedThat authority on its default
Block present but empty (wit: {})That authority on its default
signingKeyType: ""That authority on its default
Defaults are per authority, not global

An unset value resolves against that authority's own default. Omitting wit leaves it on ec-p256. The wit authority does not fall back to rsa-2048. Writing signingKeyType: "" for wit keeps EC-P256 rather than downgrading it. Likewise, an unset oauth stays on rsa-4096.

Validating before you apply

spirlctl config validate trust-domain runs the same admission checks as config set without writing anything:

$ spirlctl config validate trust-domain --id $TRUST_DOMAIN_ID signing-key-policy.yaml
Trust Domain configuration is valid.
Sections: SigningKeyPolicy

Invalid values are rejected during configuration, naming the authority block at fault:

$ spirlctl config validate trust-domain --id $TRUST_DOMAIN_ID bad.yaml
Error: configuration validation failed

SigningKeyPolicy/v1:
/oauth/signingKeyType: "ed25519" is not a valid key type (supported: rsa-2048, rsa-4096, ec-p256, ec-p384)

Observing the actual algorithm

The Algorithm column reports what each key actually is, not what the policy asked for. The Trust Domain Server reads the algorithm from the key itself.

Those two can differ, which is why the distinction matters:

  • You changed the policy but nothing has rotated yet. The column still shows the old algorithm, because the old key is still the one signing.
  • An extension webhook generated a different algorithm than the one requested. The column shows what the webhook actually produced.
  • The authority is delegated to an external CA. No Defakto configuration produced that key, so the column is the only place its algorithm appears.

Listing a deployment's key sets

spirlctl trust-domain deployment keyset list reports an Algorithm column in the signing-keys table:

$ spirlctl trust-domain deployment keyset list \
--trust-domain example.com \
--deployment-name us-west
Deployment: us-west
Signing Keys:
Type Keyset ID Issued At Expires At State Algorithm
X.509 ks_abc1234xyz 2024-01-22 00:00:00 +0000 UTC 2025-01-22 00:00:00 +0000 UTC Active EC-P384
JWT ks_abc1234xyz 2024-01-22 00:00:00 +0000 UTC 2025-01-22 00:00:00 +0000 UTC Active RSA-4096

With --output json, the algorithm appears as key_algorithm on each signing key.

The WIT row appears only when WIT-SVID issuance is enabled for your organization. A key set prepared while WIT-SVID issuance was off contains no key for that authority and emits no row at all. That is distinct from a row showing -, which means a key exists but its algorithm was not reported.

Trust Domain signing status

The same table appears under each deployment's signing authority status:

spirlctl trust-domain info example.com --show-signing-status

In --output json, each signing key gains an algorithm field.

Rotation timing

A key's type is fixed for the lifetime of the key set that contains it. Setting signingKeyType does not touch any existing key. The new key type takes effect the next time a key set is prepared.

A key set is scoped to a single Trust Domain deployment and holds one key per enabled authority. Those keys are generated together when the key set is prepared, and they rotate together. So while the policy is read once at the Trust Domain level, it is applied independently by each deployment, at that deployment's own rotation schedule. Two deployments of the same Trust Domain can be mid-rotation at different times, and until both have rotated they can be signing with different algorithms.

Under default settings, a key set lives 90 days and a replacement is prepared 14 days before the active one expires. These timings apply per deployment, measured from that deployment's last rotation. In the worst case, a configuration change can take:

  • Up to ~76 days before a new key set is even prepared with the new type (90 − 14 = 76 days from that deployment's last rotation).
  • Plus up to ~7 more days before that prepared key set is activated and starts signing. The activation threshold requires a minimum age on the prepared key set.

Each deployment reaches those milestones on its own clock. To apply the change everywhere at once, force-rotate each deployment.

If you edit SigningKeyPolicy and check back a week later expecting new certificates, you will see none. The previous key set is still active and still signing with its original algorithm. The absence of new certificates is expected, and the Algorithm column is how you confirm it.

Reading a mid-rotation table

During a deployment's rotation window both of its key sets are present, and the two algorithms sit side by side. Each key set contributes one row per authority, sharing a keyset ID:

Signing Keys:
Type Keyset ID Issued At Expires At State Algorithm
X.509 ks_newerkeys 2026-08-11 14:20:10 +0000 UTC 2026-11-09 14:20:10 +0000 UTC Prepared EC-P384
JWT ks_newerkeys 2026-08-11 14:20:10 +0000 UTC 2026-11-09 14:20:10 +0000 UTC Prepared EC-P384
X.509 ks_olderkeys 2026-08-11 13:14:48 +0000 UTC 2026-11-09 13:14:48 +0000 UTC Active RSA-2048
JWT ks_olderkeys 2026-08-11 13:14:48 +0000 UTC 2026-11-09 13:14:48 +0000 UTC Active RSA-2048

Read together with the State column, this answers the following three questions during a migration:

  1. Did the policy change take? A Prepared row with the new algorithm means yes.
  2. What is signing right now? The Active row's algorithm — here, still RSA-2048.
  3. What must relying parties accept? Both, across the whole rotation window.

Applying the change immediately

If you need the new key type sooner, force-rotation prepares and activates a new key set on demand. Force-rotation acts on one deployment, so repeat the sequence for each deployment you want switched over:

# 1. Find the deployment name.
spirlctl trust-domain deployment list --trust-domain example.com

# 2. Prepare a new key set, which picks up the new SigningKeyPolicy settings.
spirlctl trust-domain deployment keyset prepare \
--trust-domain example.com \
--deployment-name us-west

# 3. Confirm the new key set has the algorithm you expect.
spirlctl trust-domain deployment keyset list \
--trust-domain example.com \
--deployment-name us-west

# 4. Activate it. It starts signing immediately.
spirlctl trust-domain deployment keyset activate KEY_SET_ID \
--trust-domain example.com \
--deployment-name us-west

Step 3 is the point of the Algorithm column: Verify the prepared key set is what you asked for before activating it, rather than discovering afterwards that it is not.

Force-rotation applies the new key type immediately upon activation. The ~76-day and ~7-day waits do not apply on this path.

note

Key set preparation uses a conflict policy of FAIL rather than queue. A concurrent prepare on the same deployment errors rather than waiting, so do not run step 2 twice.

Relying-party impact

If you switch an authority from RSA to EC, every consumer that verifies signatures from that authority must be able to accept an EC-signed credential before the switch takes effect. That includes mTLS peers checking your X.509 chain, JWT and WIT verifiers, and OAuth or MCP relying parties.

Most JOSE and X.509 libraries handle both transparently. If you know of a relying party pinned to RSA specifically, such as a hardcoded alg check or a certificate-pinning setup that also assumes an algorithm, coordinate with them first. Because rotation is gradual you have a window. The old key set keeps signing until the new one activates. The eventual cutover is not optional once the new key set takes over.

Delegated X.509 authorities

If your X.509 authority is delegated to an external CA, its algorithm is reported too, read from the certificate you supplied, exactly as for a managed key. Two differences apply:

  • A delegated trust anchor belongs to no key set, so there is no key set ID to correlate against. For a managed authority, the algorithm and key set ID together tell you which rotation produced the key.
  • SigningKeyPolicy has no effect on it. The key was generated outside Defakto, and the policy governs only keys Defakto generates.

Extension key managers

If your Trust Domain's signing keys are backed by a customer-implemented extension webhook key manager rather than a built-in backend, Defakto cannot verify at save time that your implementation supports the key type you are requesting. The value is validated for syntax and passed straight through.

If your webhook cannot generate the requested type, the failure appears as a failed rotation, not a saved-but-broken configuration. The prepare call to your key manager fails and the previous key set stays active. There is no outage and no silent fallback to a different algorithm. Issuance continues on the old key until you either fix the webhook or change the requested type back to one it supports.

If a webhook produces a different algorithm than requested, the Algorithm column shows what was actually generated, not what was asked for.

Rolling back

Deleting the SigningKeyPolicy section entirely, deleting one authority's block, or deleting just the signingKeyType field within a block all do the same thing: That authority reverts to its built-in default.

Rollback follows the same timing as any other change, taking effect at the next key set preparation for that authority, not retroactively. A key set already prepared or active with the type you are rolling back from keeps that type until it is replaced, and the Algorithm column keeps showing it, correctly, until then. Use force-rotation if you need the rollback to apply immediately.