Authoring certificate profiles
Certificate profiles govern what a certificate may be: the allowed key types and sizes, extended key usages, validity ceiling, name constraints, and which enrollment protocols may use the profile. Every issuance path validates a request against its bound profile before anything is signed, so a non-compliant request is rejected with a clear reason rather than minting an out-of-policy certificate.
Profiles are tenant-scoped, versioned, and event-sourced: editing a profile appends a full profile event, projects a new active read-model version, and leaves prior versions resolvable for audit and for certificates issued under them. A read-model rebuild restores profile versions from the event log. Every create/update and every profile-gated issuance decision is recorded with the actor who made it.
Lifecycle renewal uses the identity's explicit profile_name (or profile)
attribute. Without that attribute, it recovers the named profile from the identity's
first accepted issuance event. This also covers existing endpoint enrollments: a
mail identity issued under a mail profile keeps that profile when an operator
changes the default to a Java profile. The configured default applies only when
the identity has neither an explicit nor a retained named profile. Older issuance
events without a profile binding remain unassigned.
The next renewal uses the active revision of the selected profile and limits the requested validity to its ceiling. An agent renewal job retains the exact revision and effective validity selected at handoff; editing the profile or changing the default does not rewrite queued work. A missing profile or missing, mismatched, or ambiguous retained issuance evidence stops renewal instead of choosing another default. Permission and approval checks remain separate from these certificate constraints; the original issuance approval cannot authorize a new operation.
For the platform CA and signer-backed private CAs, max_validity covers the
entire signed interval, including the NotBefore backdate used for clock skew.
With the default five-minute backdate, a two-minute or five-minute ceiling leaves
no usable certificate lifetime. Endpoint enrollment and identity issuance reject
these profiles during preview and again before queuing issuance. Choose a ceiling
that also leaves enough time to deploy and renew the certificate, then preview
again. A seven-minute ceiling leaves at most about two minutes after issuance;
it is a diagnostic example, not a recommended operating lifetime. External CAs
apply their own backdating rules; this local admission check does not qualify an
external CA's certificate validity or delivery.
Previously accepted jobs keep their original profile revision. A local signing
refusal records the fixed certificate_profile_validity_refused class. Updated
host agents report a typed validity refusal without copying signing error text
into permanent history. If a failed first issuance is pinned to an unusable
profile, retry readiness explains why a new reviewed issuance or replacement is
required. Editing the active profile does not rewrite that command, and a retry
cannot bypass its original validity ceiling.
Recovering an approved request with an unusable rule
An approved request keeps the rule version its approver reviewed. If that version leaves no usable lifetime, approving it again or changing the active version does not repair the request. Its original approval remains part of the audit history.
In Operations, open Requests waiting for approval, expand the issuance request history, and read the refusal. The original requester can withdraw the request and use Start a new request. The console carries the known credential name, a still visible owner, and purpose into a new form. Select a usable rule explicitly and supply a requester-generated CSR; the console does not copy the old rule, CSR or approval. Preview the exact request, submit it, and have a different approver review it before issuance. An approver acting for someone else must ask that requester to open the new request. Withdrawal preserves the old request's history.
The local lifetime refusal is HTTP 422 with problem extensions
"retryable": false and "recovery_required": "new_issuance_request". These
extensions distinguish this permanent refusal from a temporary issuance failure.
The console offers Retry safely for ordinary retryable failures, reusing the
saved request identity and issuance key. Other explicit recovery prerequisites are
shown as Retry after recovery; resolve the reported prerequisite first. Error
wording alone never authorizes a replacement or changes the approved rule.
The registration-authority (RA) separation
The RA role model separates who may request a certificate from who may approve/issue it:
- the built-in
ra-officerrole may author profiles (profiles:write) and request certificates (certs:request), but cannot issue them (certs:issue); - an operator/approver holds
certs:issueand authorizes what a requester cannot self-issue.
This means a requester cannot self-issue: issuance requires the separate approval permission.
Profile fields
A profile spec is JSON:
| Field | Meaning |
|---|---|
allowed_key_algorithms |
permitted key algorithms, e.g. ["ECDSA","RSA"] (empty = any) |
min_rsa_bits / min_ecdsa_bits |
minimum key strength floors; an unset min_rsa_bits enforces 2048 bits, and an explicit lower value is honored but raises a profile-rsa-floor-below-default finding |
allowed_ekus |
permitted extended key usages, e.g. ["serverAuth"] (empty = any) |
max_validity |
validity ceiling as a duration, e.g. "2160h" (0 = no ceiling) |
allowed_protocols |
enrollment protocols that may use this profile, e.g. ["api","acme"] |
allowed_dns_suffixes |
name constraint on SAN dNSNames (empty = unconstrained) |
acme_device_attestation |
explicit, default-off TPM device-attest-01 policy: operator roots, device identifier allowlist, COSE algorithms, and maximum proof age |
public_tls |
the profile issues publicly trusted TLS server certificates (default false); requires serverAuth to be allowed, and the lifetime-reduction readiness report flags a max_validity over the CA/Browser Forum's dated maximum, or none |
Public TLS profiles and the dated maximum
trstctl's own CA is never publicly trusted, so public_tls is your statement
about the CA behind the profile. Publicly trusted TLS certificates issued from
2026-03-15 may be valid at most 200 days, from 2027-03-15 100 days, and from
2029-03-15 47 days, counting notBefore through notAfter inclusive: a ceiling of
exactly 4800h is 201 days. Use 4799h59m59s for 200 days, 2399h59m59s for
100 and 1127h59m59s for 47. Issuance does not refuse a longer ceiling; the
readiness report flags the profile under every step it exceeds.
RSA floor below the default
An unset min_rsa_bits means 2048: an RSA request below it is refused, and
the refusal says the floor is the default. A profile may still set a lower
floor explicitly, for example to keep issuing to a legacy appliance that
cannot take a larger key. That version issues as configured, and while it is
the active version a profile-rsa-floor-below-default finding in the
Findings queue names the profile, the version and the floor. A later version
at or above 2048 bits (or a restore of one) marks the finding remediated with
that version as its evidence; lowering the floor again reopens it. The finding
can also be investigated or accepted as a risk until a date, like any other.
TPM device-attest-01
An ACME profile may add TPM-backed device identity as a fourth challenge without
removing or weakening http-01, dns-01, or tls-alpn-01. The feature is
default-off. Enabling it requires all of the following:
format: "tpm"(the first and only supported attestation format);- one or more operator-controlled X.509 trust roots in
attestation_roots_pem; - an explicit exact-name or
*.suffixdevice identifier allowlist; - allowed COSE signature algorithm numbers, such as ES256
-7; and - a positive
max_ageno greater than 24 hours.
The device submits a fresh TPM WebAuthn attestation through internal/crypto.
trstctl binds that proof to
the tenant, ACME account, order, challenge, token, outer JWS nonce, device
identifier, CSR public key, and issuance time. The validated key digest is stored
in the ACME event stream and replayed after restart; finalization fails if the
CSR changes. Trust is entirely operator-supplied and no manufacturer metadata
service is contacted.
Creating and listing profiles
Via the CLI (at parity with the REST API):
# Create (or version) a profile. Writing requires the profiles:write permission.
echo '{
"name": "web-server",
"spec": {
"allowed_key_algorithms": ["ECDSA","RSA"],
"min_rsa_bits": 3072,
"min_ecdsa_bits": 256,
"allowed_ekus": ["serverAuth"],
"max_validity": "2160h",
"allowed_protocols": ["api","acme"],
"allowed_dns_suffixes": ["example.com"],
"acme_device_attestation": {
"enabled": true,
"format": "tpm",
"attestation_roots_pem": ["-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"],
"allowed_identifiers": ["*.devices.example.com"],
"allowed_algorithms": [-7],
"max_age": "5m"
}
}
}' | trstctl-cli profiles create -f -
# List the active profiles, and resolve a specific prior version.
trstctl-cli profiles list
trstctl-cli profiles get-version web-server 1
A re-create of the same name publishes a new version and activates it; the previous
version stays resolvable by number.
Recovering a known-good version
Recovery is append-only. trstctl never edits an old row or flips an old version back to active. Instead, it copies the reviewed historical spec into one new active version. That keeps the full story readable: the bad version still exists for audit, the recovered version says exactly where it came from, and certificates already issued keep the profile-version evidence they originally used.
First create an effect-free review. The body pins the active version you saw and records why recovery is needed:
cat > profile-restore.json <<'JSON'
{
"expected_active_version": 2,
"reason": "Recover the last known-good 24-hour web TLS rule"
}
JSON
trstctl-cli profiles restore-preview web-server 1 -f profile-restore.json
The preview reports the historical source, current active version, new version, semantic spec digest, request fingerprint, risks, and verification steps. It writes no event, creates no approval, changes no rule, and contacts no external system. After checking that receipt, submit the same body:
trstctl-cli profiles restore web-server 1 \
--idempotency-key profile-recovery-2026-08-28 \
-f profile-restore.json
The server rechecks expected_active_version while holding the projection lock. If
another operator created a newer version after the preview, recovery returns 409
and creates nothing; preview again instead of rolling over the newer decision. The
same idempotency key returns the original result and cannot create another version.
If either the current or historical rule requires approval, recovery is parked for
a different operator under the existing profile dual-control gate.
What a profile rejects
An issuance bound to web-server above is rejected, with the reason, when it asks for a
disallowed key algorithm, an RSA key below 3072 bits, an EKU outside serverAuth, a
validity longer than 2160h, a protocol other than api/acme, or a SAN outside
example.com. A compliant request is signed normally.
The checks that need no key run when the request is made. An identity issuance through
the API whose profile does not permit the api protocol, whose validity it caps below
what the issuance asks, or whose names it does not permit (the names in the requester's
CSR, or the identity's own name when there is none) is refused at the transition with
409 and code profile_refused, naming the profile, its version and the refusal, before
anything is recorded; the transition preview returns a plan that is not ready and says
the same (profile_refused, profile_refusal). The transition pins the profile revision
it admitted, so a later profile edit cannot strand queued work. The checks that need the
key (algorithm, strength, EKUs) run when the certificate is minted; a refusal there
happens before any signature, retries on the job's own budget, and never opens the
tenant's outbox circuit, so other issuance is not delayed.