Weak keys
A key can be weak whatever its algorithm and size. In October 2026 eight eMudhra intermediate CAs turned out to have P-384 private keys that were small integers (4, 56, 66, 115, 155, 156, 230 and 237): anyone could compute them from the public key. trstctl checks every public key it rates for that kind of structural weakness, not only for its size.
The checks
The checks live in internal/crypto/weakkey and are modeled on the badkeys
project (badkeys.info, MIT licence). They are re-derived from the published
definitions; no badkeys code or data is copied.
| Check | What it finds |
|---|---|
ec_small_scalar |
An elliptic-curve key whose private scalar k, or n − k, is at most 2^24 + 2^16, on P-224, P-256, P-384 and P-521. Baby-step giant-step over a table of the x-coordinates of 1·G … 2^16·G, built once per curve; about 4 ms per P-384 key. Comparing x-coordinates is what also catches n − k. |
rsa_small_factor |
An RSA modulus divisible by a prime below 2^16. |
rsa_close_primes |
Two RSA primes so close that Fermat's method factors the modulus within 100 rounds. |
rsa_roca |
The ROCA structure (CVE-2017-15361, Infineon RSALib): the modulus lies in the subgroup generated by 65537 modulo each of the primes 3 … 167. |
rsa_blocklist |
A key on an operator-supplied blocklist (below). |
rsa_public_exponent |
An exponent no RSA key can have: below 3, or even. |
rsa_shared_factor |
Two moduli in the same inventory that share a prime (Bernstein's batch GCD). Both can be factored. A key reused by several certificates is one modulus, not a shared factor. Batch GCD is a background routine, never run in a request path, bounded to 20,000 distinct moduli per run. |
key_size, key_algorithm, signature_hash |
When a whole certificate is rated: RSA below 2048 bits, a curve below 256 bits, DSA, or a signature over MD2, MD5 or SHA-1. |
Operator blocklists
trstctl ships no third-party key list. Debian's 2008 OpenSSL weak-key list
(openssl-blacklist) is GPL-3, so the check reads files you supply, in that
package's format: one entry per line, the last 20 hex digits of
SHA-1("Modulus=" + uppercase hex modulus + "\n"), # comments allowed.
TRSTCTL_TRUST_WEAK_KEY_BLOCKLIST_FILES=/etc/trstctl/blacklist.RSA-1024,/etc/trstctl/blacklist.RSA-2048
The same setting is trust.weak_key_blocklist_files in the configuration file.
A missing, empty or malformed file fails startup instead of checking nothing.
Where the checks run
Private keys an agent finds on a host. Every RSA size and curve is named (an RSA-1024 key used to be reported with no algorithm at all). A key below the floors, or with a structural weakness, is reported with
key_strength: weak, the reason inkey_weak_reason, and risk 95 instead of 85.Rogue certificate detection (
GET /api/v1/revocation/rogue-certificates, the console's Estate health tab). An inventory certificate whose bytes trstctl holds is rated from them: its key, its signature hash, and the distrust list by key, recorded chain and authority key identifier. A row that kept no bytes borrows the verdict its discovery reader recorded, or is rated from its recorded key-algorithm name and issuer name. A weak key is theweak_key_algorithmfinding with its reasons inweak_key_reasons; a certificate that is, or chains to, a distrusted CA or key is thedistrusted_issuerfinding (risk 95, critical) withdistrustnaming the entry and how it matched:itself,chain,issuer_key_id, orissuer_name(the weakest: only the recorded issuer name matched). The recommendation is to replace it from a trusted issuer: claim it as a managed identity if it is not one and move it with a migration run. The old check read only the bare algorithm name, so every ECDSA certificate was flagged weak and no RSA-1024 certificate ever was.Certificates an agent finds (files, Kubernetes secrets, PKCS#11 tokens, the Windows store, and OS, NSS, browser and Java trust stores). The agent rates each one while it holds the bytes, with the other certificates found in the same file as its chain, and reports the verdict as metadata (
key_strength,key_weak_reason,distrust_entry,distrust_how,distrust_reason,distrust_subject) with the risk raised to 85 for a weak key and 95 for a distrusted certificate or issuer. A host trust store that holds emSign ECC Root CA - G3 is such a finding (distrust_how: itself).The control plane re-rates every agent report against its own distrust list (shipped entries plus
trust.distrust_file): by the reported key and certificate hashes, the authority key identifier and the issuer name. An agent's own distrust claim is replaced, never trusted; its weak-key reasons stand, since only it held the bytes. So an older agent, or an entry added after the agent rated a certificate, still produces the finding.Cloud certificate discovery (AWS ACM, Azure Key Vault, Google Certificate Manager) rates each certificate from the bytes the provider returned, chain included where it comes back.
The CT monitor rates each hit it keeps (only those: a log batch can hold thousands of entries), from the logged certificate or precertificate; the rogue-certificate posture shows the verdict on the CT finding.
A relay's network scan observes the whole served chain: the leaf's key hash and authority key identifier, each served issuer's key and certificate hashes (up to eight), and the weak-key checks that fire on the leaf. The scan protocol admits no certificate bytes and no free text, so the relay sends those hashes and closed check codes only; the control plane matches them against its own distrust list and words the reasons.
The CBOM (
POST /api/v1/cbom/scans, the console's Algorithms and future readiness screen). A TLS endpoint's certificate-key asset is rated from the served leaf and the served chain while the scan holds them. A key with a structural weakness, or a certificate that depends on a distrust-list entry, isweakand out of policy whatever its algorithm and size. Its reasons say why (weak key: …, or the entry and how it matched) and name the replacement path: plan its re-issue through the crypto migration flow (POST /api/v1/pqc/migrations/planwith the asset's id).Every requester key trstctl certifies. A subject key that fails a structural check (small factor, close primes, ROCA, an operator blocklist hit, a small EC scalar, a bad RSA exponent) is refused before anything is signed, and the refusal names the check: ACME (
badCSR, 400), EST and CMP (400, the check in the response), SCEP (the issuance evidence names it; the client sees a refusal), the REST identity transition withsubject_csr_pem(400 before the transition is recorded), the Vault-compatible PKI engine (422), SPIFFE X.509-SVIDs, agent enrollment, CA authority issuance, and an external CA (422, before any connector is called). Size and algorithm floors are not part of this: they stay with each profile's key policy (allowed_key_algorithms,min_rsa_bits). An unsetmin_rsa_bitsis 2048 bits; a profile that sets a lower floor explicitly still issues below it and raises aprofile-rsa-floor-below-defaultfinding until a later version raises it. Ed25519 and post-quantum keys have no structural checks and pass. The same paths refuse a key the tenant has recorded as compromised: one that a certificate of the tenant carried when it, or its identity, was revoked with reasonkeyCompromise(CA/B BR 6.1.1.3, condition 4). The key is recorded with the report, not with the publication: an identity'skeyCompromiserevocation records its certificates' SPKI SHA-256 in the same transaction, a revocation queued at an external CA records it when queued, and a direct revocation records it as it projects (backfilled at startup for earlier revocations). The check is one indexed lookup by tenant and hash, with no cap on how many revocations a tenant records. A bootstrap enrollment is checked for structural weakness before its single-use token is consumed; a key recorded as compromised is only known after the token names its tenant, so that refusal consumes the token.The CA key ceremony. The signer refuses a weak key it generates, mints or imports; an HSM or KMS key that fails is zeroized and the operation fails with
provider_key_weak. No weak key is certified as a CA, and an imported or cross-signed CA certificate that is weak or on the distrust list is refused. Before a signer-held key is certified or bound to an import, it signs a fresh challenge that must verify against the exact public key going into the certificate. An offline root, whose key never enters the signer, can sign its import ceremony's challenge on the offline system; without that signature the import records that possession was not proven. See the key ceremony runbook.Removing a distrusted root from a host. A trust-store finding the distrust list names itself can be removed through trust distribution, by the agent that found it, when the host profile allows the path and the file holds only that root. See CA, trust-store & private-key discovery.
The inventory weak-key audit. Every six hours the leader runs batch GCD over each tenant's distinct RSA public keys: its CA authorities, the inventory certificates whose bytes trstctl holds, and the keys discovery readers report. Agents, cloud discovery and the CT monitor attach an RSA certificate's public key (
rsa_spki, the base64 SubjectPublicKeyInfo); the control plane keeps it only when it hashes to the reportedspki_sha256. Each run is a discovery run under theweak-key-audit:batch-gcdsource, and every place a key that shares a prime appears gets aweak-keyfinding (risk- that the rogue-certificate posture shows as a weak key, even for a
certificate whose bytes trstctl holds. One run checks at most 20,000 distinct
keys, CA authorities first, then inventory certificates, then reported keys;
a larger inventory's run is
partialand says how many keys it left out. Relay network scans report key hashes only, so their keys are not in the audit yet: that is what comes next for it.
- that the rogue-certificate posture shows as a weak key, even for a
certificate whose bytes trstctl holds. One run checks at most 20,000 distinct
keys, CA authorities first, then inventory certificates, then reported keys;
a larger inventory's run is
Operator blocklists apply to what agents find. A reported RSA key is rated again on the control plane, which holds the operator's blocklists (
trust.weak_key_blocklist_files); agents do not.
The distrust list
The shipped distrust list (internal/crypto/trust/distrust.json) names the
three emSign roots and the eight weak intermediates by public key, and now by
subject key identifier too. A certificate whose authority key identifier names
one of them was issued under it, so a leaf under one of the eight intermediates
is found even when no chain was recorded with it.