Skip to content

Eight Compromised CA Keys and a Week of Emergency Upgrades: the eMudhra Incident

A P-384 key that should carry 384 bits of security ended up with a handful of effective bits. Fixing it took every major language ecosystem, and the keyword of this incident is not 'revoke' — it's 'rebuild'.

Authorhopes78 minutestls, pki, ca, emudhra, nodejs, go, java, supply-chain-security

The Short Version

If this one slipped past you last week, it's worth catching up on:

On October 17, 2026, a security researcher discovered that the private keys of eight intermediate CA certificates from eMudhra (emSign) could be derived by a third party. The keys were generated during a key ceremony on April 30, 2025; due to a flaw in the process, P-384 keys that should carry 384 bits of security ended up with only a few bits of effective entropy.

This is not the routine "revoke one TLS certificate" incident. Derivable private keys mean an attacker could, in theory, issue browser-trusted certificates for any domain and quietly man-in-the-middle it — and the code-signing intermediates could have lent malware a legitimate-looking signature.

So over the past week we watched an unusually broad, cross-ecosystem response:

  • Chrome, Mozilla and Apple blocked the six affected TLS intermediates; Microsoft blocked the two code-signing ones;
  • Go released 1.27.3 / 1.26.10, hard-coding a deny list into crypto/x509 (CVE-2026-111943);
  • Node.js shipped its October 21 security releases 22.23.4 / 24.22.1 / 26.12.1;
  • Python's certifi, pip and uv, Rust's rustls-webpki and webpki-roots, and PHP's composer/ca-bundle all followed;
  • Oracle issued an out-of-band security alert for Java on October 21–22 (CVE-2026-91403, CVSS 9.1);
  • Electron, curl, and the Debian/Ubuntu/Alpine CA bundles updated in step.

The good news: as of Talos's analysis on October 21, there was no sign of abuse in the wild, and Chrome found no rogue certificates in Certificate Transparency logs.

But "not abused yet" has never been a reason to skip an upgrade — the whole point of this emergency wave is to close the door before the abuse starts.

What Actually Happened

The timeline starts about eighteen months before discovery.

On April 30, 2025, eMudhra held a key-generation ceremony for 24 subordinate CAs. A flaw in a wrap/unwrap step produced a set of badly weakened P-384 keys. Per Cisco Talos's analysis, their effective entropy was 3 to 8 bits — against a required 384. Eight of these weak keys ended up on eight intermediate certificates: six TLS intermediates (EV/OV/DV, G3B and G3C variants) and two code-signing ones, all under emSign Root TLS CA – G3 and Root CS CA – G3, plus a cross-signed path to Comodo AAA.

A few bits of entropy means the private key isn't in "unguessable" territory — it's in "brute-force it in a moment" territory. Reports note that one recovered key's value was absurdly small.

Those keys sat in the wild for roughly 17 months before discovery on October 17, 2026. eMudhra has since revoked the affected certificates and stopped issuing from its G3 root. Mozilla Bugzilla #2083449 tracks the handling; Talos published the full analysis on October 21.

Why the Fix Had to Be So Heavy-Handed

For a routine certificate incident, the fix is "revoke and replace", and applications barely notice.

Not this time. The private keys themselves are derivable, so revocation isn't enough — every TLS client on the planet must reject these public keys, wherever they show up.

The complication: the places that trust these keys go far beyond browsers.

  • Some runtimes compile their trust store into the binary (Go's standard library, Rust's webpki-roots);
  • Some distribution channels maintain their own snapshots (Python's certifi, PHP's composer/ca-bundle, Linux distros);
  • And the JDK ships its own trust store.

So every ecosystem converged on the same fix: put the SPKI SHA-256 hashes of the eight compromised certificates on a deny list and ship it in an emergency release. The Go team went as far as hard-coding the list into crypto/x509.

That's what makes the keyword of this incident rebuild.

Upgrading the toolchain is only step one. In an already-compiled Go binary, the old deny list — or no list at all — is still baked in. In an already-published container image, the old ca-certificates are still there. An Electron app already delivered to users still runs the old framework. None of these get fixed by upgrading your dev machine — they must be rebuilt and redistributed.

Fix Versions Across Ecosystems

As announced between October 21 and 22:

Ecosystem Fixed versions Notes
Go 1.27.3 / 1.26.10 (CVE-2026-111943) SPKI deny list in crypto/x509; compiled binaries must be rebuilt
Node.js 22.23.4 / 24.22.1 / 26.12.1 (Oct 21 releases) TLS rejects chains containing blocked public keys; bundled CA update
Python certifi 2026.10.19; pip 26.2.2; uv 0.13.4 emSign G3 root removed from the trust store
Rust rustls-webpki 0.103.16; webpki-roots 1.0.10 Root data compiled into binaries updated
PHP composer/ca-bundle 1.6.0 / 1.4.16 No longer trusts the eight intermediates
Java CVE-2026-91403 (OpenJDK advisory, Oct 21) Upgrade the JDK, or replace blocked.certs, or force revocation checking
Electron 44.9.1 and up Apps must be repackaged and redistributed
curl / Linux distros updated ca-certificates Container images need rebuilding
Bun / FreeBSD no fix released as of Oct 21 Watch for updates, then upgrade immediately

Java: the Part That Needs More Than a Trust-Store Refresh

Java deserves its own section because the exposure and the fix are both easy to miss.

On October 21, OpenJDK published an advisory (CVE-2026-91403, CVSS 9.1, in javax.net.ssl): the JDK's bundled trust store contained the compromised eMudhra intermediates, so standalone runtimes trust any certificate chaining to them — including forged ones. The affected list is broad:

  • JDK 27.0.1, 25.0.5, 21.0.13, 17.0.21, 11.0.32.1, 8u504, and earlier.

Oracle issued an out-of-band security alert, and various CERTs started relaying it on October 22.

The OpenJDK advisory offers three paths, in priority order:

  1. Upgrade standalone JDK installs to the fixed releases;
  2. If you can't upgrade yet, replace $JDK_HOME/lib/security/blocked.certs with the official file (called blacklisted.certs on JDK 8) — back up your custom entries first;
  3. As mitigation, enable revocation checking: -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true, and verify signed JARs with jarsigner -verify -revcheck -strict.

Two details worth flagging: JDKs installed from OS repositories (Arch, Debian, Fedora, RHEL, Ubuntu, etc.) use the operating system's store, so updating the system ca-certificates suffices — but FreeBSD and Homebrew builds must be updated directly. And if a machine has several JDKs installed, every one of them needs this.

The Node Releases Also Contain an RCE

If you only watched the CA story, you may have missed the risk that hits Node users more directly: the October 21 Node.js security releases fixed around ten vulnerabilities, four of them High — and the heaviest one isn't about certificates at all:

CVE-2026-111497: an out-of-bounds write in the Brotli decoder that permits remote code execution, dubbed "Node2Shell" in echo of React2Shell. All three release lines are affected. It was reported by researchers from OpenAI Codex Security.

Also in the same batch:

  • two TLS certificate hostname-validation bypasses (IPs written with fullwidth digits, hosts that loosely resolve to an IP), High;
  • dns.resolve truncating names containing a NUL character, High;
  • entropy caches of crypto.randomUUID() / randomInt() persisted into startup snapshots and SEAs — every process then repeats the same "random" values, Medium. If you generate tokens or IDs with these, that one matters;
  • a keep-alive socket processing a response written before the request, non-canonical IPv4 hostname validation, and more.

Fixed versions: 22.23.4 / 24.22.1 / 26.12.1 — upgrade within your major line. Every EOL version remains affected; there is no lucky exemption.

What You Should Do Now

Match your stack:

Everyone:

# Upgrade local and CI toolchains (Node example)
nvm install 24.22.1 && nvm alias default 24.22.1

Then push your artifacts through the pipeline again: recompile, repackage, redeploy. For container images, pull the fresh base image before building — don't hit stale layer caches.

Go: after upgrading to 1.27.3 / 1.26.10, rebuild with go build and redeploy every service. Updating the Go version in CI without recompiling and redeploying fixes nothing.

Python: upgrade pip 26.2.2, certifi 2026.10.19, uv 0.13.4 — in CI and containers too.

Rust: cargo update -p rustls-webpki -p webpki-roots, then re-release.

PHP: composer update composer/ca-bundle.

Java: follow the OpenJDK advisory — upgrade or swap blocked.certs; count how many JDKs you have.

Electron apps: upgrade the framework, repackage, redistribute — installed old builds are not protected by the new release.

Not yet fixed: Bun and FreeBSD hadn't shipped fixes as of October 21. Watch their channels and upgrade the moment they do.

What This Site Did

The day I wrote this post, we closed the doors around here too:

  • The local dev machine's Node went from 24.14.1 (affected) to 24.22.1 (fixed);
  • The repo's .nvmrc moved from 22.22.2 to 22.23.4, with engines.node bumped to >=22.23.4, so everyone who clones this repo gets a patched toolchain;
  • pnpm install, pnpm lint, pnpm typecheck and pnpm build all pass on the new toolchain.

As for runtime, this site runs on Cloudflare Workers: outbound TLS trust is maintained platform-side and doesn't depend on this machine's ca-certificates. But the local dev and build environments still needed the upgrade — an artifact's trust boundary starts at the machine that builds it.

The most tangible takeaway from doing this: none of the usual tooling sees this layer. pnpm audit and friends only look at what's in package.json — not your toolchain, your base images, or your JDK.

Takeaways

Three things I want to remember from this one.

First, the foundation of digital trust is entropy and process. A strength of 384 bits collapsed to 3–8 because of one ceremony flaw — a difference of many orders of magnitude — and the error hid for 17 months. In PKI, "the randomness wasn't random enough" is not bad luck; it's a single point of failure that ceremonies, audits and verification exist to defend.

Second, the unit of remediation has shifted from "upgrade" to "rebuild". We used to say "upgrade to fix", implying a change of state somewhere. This time, toolchains, binaries, container images and shipped apps each needed an explicit rebuild. The deeper the supply chain, the longer the rebuild list.

Third, don't outsource security awareness to a single tool. Auditors and bots watch the thin application-dependency layer; CA stores, language toolchains and system certificate packages below that still depend on humans reading the advisories. The ecosystem's response speed this time — from discovery on October 17 to fixes across the board within a week — was genuinely good. But it only helps if you see the announcement.

Upgrade now, while the weak keys are still unused.

References

#tls#pki#ca#emudhra#nodejs#go#java#supply-chain-security

Related Posts