d4sn3st.devArchiv / Blog

SECURITY // 07 November 2026

CA Keys of Eight Bits: The eMudhra Failure in Plain Terms

Eight intermediate certificates issued by eMudhra carried private keys of a single byte, one of them literally the number 4. I rebuilt the case on my cyberdeck on October 23, 2026: curl and Python with default settings accepted a revoked certificate with HTTP 200. The article explains why revocation alone changes nothing and the five fixes I shipped on my own setup.

Autor
Marcel
Lesezeit
1 MIN

I ran a small experiment on the morning of October 23, 2026, on my agent deck: a Linux container on an Android Pixel 6a, plain system curl without flags, and Python 3.12.3 with the default OpenSSL bundle. The control case was https://untrusted-root.badssl.com/, a certificate without a legitimate root. curl refused it with error 60, Python refused it with CERTIFICATE_VERIFY_FAILED, so trust validation works on this machine. The real test followed: https://revoked.badssl.com/, a certificate that has been formally revoked. curl answered HTTP 200. Python answered HTTP 200. No warning, no prompt, straight through. That result is the entire eMudhra story in miniature. In the week of October 17, 2026, the industry learned that eight intermediate certificates issued by the certificate authority eMudhra carried private keys of a single byte. One of those keys is the number 4, another is 56, another is 115. Scott Helme recovered all of them from the public certificates on his laptop in under a second. The browsers have handled the fallout. Everything that is not a browser, and my agent automation is part of that group, is where the open questions are still sitting.

Nobody had to steal anything

Most key incidents begin with a theft, and theft leaves traces. This one begins with a construction decision. On Saturday, October 17, 2026, a researcher reported to Apple, Google, Microsoft, Mozilla and eMudhra that the private keys for eight eMudhra intermediates were compromised. Chrome shipped a CRLSet update blocking them the same evening, Firefox followed a few hours later through OneCRL. On October 20, Mozilla made the case public, shipped an NSS update that removes the eMudhra root from the trust store, and attached the affected certificates to the announcement. Chrome confirmed the block for its coming Root Store version 43. The eight intermediates were created in April 2025: six TLS issuing CAs covering domain validation, organization validation and extended validation, two generations each, plus two code signing CAs. In May 2025 Sectigo cross-signed every one of them from its AAA Certificate Services root. The timeline matters because the failure did not start when somebody noticed it. The keys were effectively public from the day they were generated, in April 2025, for anyone who knew where to look.

The table below reproduces Scott Helme's verification. He pulled all sixteen certificates, each key pair issued once by eMudhra and once through the Sectigo cross-sign, and every single one fell over instantly:

| Intermediate | Purpose | Private key d | | --- | --- | --- | | EM DV TLS CA G3B | TLS | 156 | | EM DV TLS CA G3C | TLS | 230 | | EM EV TLS CA G3B | TLS | 115 | | EM EV TLS CA G3C | TLS | 56 | | EM OV TLS CA G3B | TLS | 155 | | EM OV TLS CA G3C | TLS | 4 | | EM OV CS CA G3B | Code signing | 66 | | EM OV CS CA G3C | Code signing | 237 |

Valid points, terrible numbers

A private key on an elliptic curve such as P-384 is simply a number, conventionally written d. The public key inside the certificate is d multiplied by a fixed curve point G. Computing forward is cheap, computing backward is considered practically impossible, and the entire trust model rests on that impossibility holding only when d is enormous. A proper P-384 key should be a random 384 bit number, roughly 2^384 possibilities, a number with about 115 decimal digits. The eMudhra keys do not satisfy that condition. In hex notation they carry 47 zero bytes and a single data byte, which shrinks the search space from 10^115 to 256 values. Recovering a key means counting: 1 times G, 2 times G, onward until the certificate matches, at most 255 steps. Nothing about these certificates looks wrong. A public key derived from d equal to 4 is a perfectly valid point on the curve, the encoding is correct, the curve is correct, every structural check passes. The only way to catch this is to try, and trying costs milliseconds on ordinary hardware. A certificate authority that issues keys like these has not opened a hole at the edge of its system. It has switched off the mathematics in the middle.

They are inherently weak single-byte keys that anyone can factor, and the issue cannot be contained. (Ian Carroll, on the disclosure thread)

Seventeen months without an alarm

For all practical purposes the keys were public from the day each certificate existed. April 2025 to October 2026 is seventeen months during which anyone who saw any of these certificates could have lifted the keys. The incident therefore produces a level of uncertainty that standard key compromise procedures do not handle: there is no theft to date, no access window to reconstruct, no log that could prove it did not happen. Chrome argues that because Chrome enforces Certificate Transparency, no forged TLS certificate chaining to these CAs ever appeared in the CT logs. That is reassuring for browsers. Certificate Transparency detects mis-issuance after the fact, it does not prevent it, and a large share of client software never checks CT at all. An empty CT log is evidence for the browser world and a guess for everything else. The lack of proof cuts both ways, and that is exactly why the case cannot be contained.

What my own stack told me

The environment was a Linux container in a PRoot distribution on Android, the kind of setup most agent decks run on. The control case matters because it separates two failures that often get blurred together. My bundle rejects an unknown root, so the trust decision is intact, and it still sailed past a revoked certificate, so the revocation layer is the one that is absent. That combination is precisely what makes the eMudhra case uncontainable. Not a broken root that every browser would notice immediately, but revocation status, something the default code paths of most TLS clients never query. My own automation is a textbook example of the exposure: radar queries, update jobs and installers on this deck run through exactly the default configuration I just measured. An attacker would not have needed to break anything, only to get a certificate for the right name issued under the compromised CAs and to sit somewhere on the network path. Scott Helme ran the higher value version of that test with a local server presenting a certificate for api.stripe.com, a name under which hardly anyone willingly accepts an attacker. With default settings, curl printed SSL certificate verify ok, Python completed the handshake, and Node reported authorized true on every Linux and macOS machine he tested. On Windows, CryptoAPI accepted the chain until revocation checking was enabled, and even then only one of the two revocation routes returned revoked. The layer where this incident should have landed does not exist in most stacks.

Everything that is not a browser

Updated mainstream browsers get off comparatively easily. Chrome and Firefox block the intermediates directly and both require Certificate Transparency, Safari requires CT as well. Everything else is the real story. Non-browser TLS clients with default settings share three habits: they trust a CA bundle on disk, they do not check revocation by default, and they do not check CT. Whether eMudhra and Sectigo have revoked these intermediates does not change their behavior at all. The exposed classes form a long list. Installers and self updaters that pull scripts over HTTPS, including rustup, nvm, the Homebrew installer, countless install.sh pages and IoT firmware updaters, place unsigned code on the machine, often as root. CI and CD runners on stale base images that fetch dependencies, push artifacts and keep cloud tokens, registry logins and signing keys in their environments. Server to server connections such as payment providers, webhooks, OAuth token exchanges and cloud SDKs that carry their own certifi bundles. Embedded and operational technology devices with frozen bundles, cameras, routers, cars, medical devices, payment terminals and industrial controllers. Helme's estimate reaches to 2039, the year by which many of those bundles are expected to keep trusting the eMudhra root. And outside of TLS the same weakness lands in Windows code signing: Windows trusts the eMudhra CS root directly, so malware signed under the compromised code signing keys looks like software from a real publisher to anything that does not check revocation.

Five fixes worth shipping today

None of the useful responses require waiting for a vendor. First, refresh CA bundles everywhere, on workstations, inside containers and inside CI images. Mozilla removed the root on October 20, and on Debian family systems the change travels with the ordinary ca-certificates updates the moment they are applied. Second, rebuild CI environments instead of running months old base layers for jobs that negotiate over the network. Third, switch on revocation and CT checking where a connection is critical, the matching curl flags in sensitive scripts, library level revocation settings in application code, deliberate configuration instead of defaults. Fourth, retire blind curl pipe sh on machines that matter, pin signed releases, verify checksums, document the installer paths you keep. Fifth, and this is the lesson that matters for agent operation: updates are part of the security architecture, not housekeeping. An agent that runs automation across the network every day drives on the same default paths as any other client, with the same bundle and the same gaps. My note for this deck now reads: whatever trusts must know what it trusts and how often it looks again.

The system never promised something it then failed to deliver. It simply never promised to check revocation for you. Whoever delegates trust to machines has to delegate the checking as well, or delegate the excuses along with it.

FAQ

Do I need to worry if I only use an updated browser? Browser risk is mostly handled, Chrome and Firefox block the intermediates directly and require Certificate Transparency. The concern starts one layer down, with every client that is not a browser. Can the keys be fixed without updating every client? No. Nobody stole them and nothing was leaked, they were weak at generation time. The only correction is revoking trust in them, and trust removal requires updates on the client side. Who is exposed today? Everything outside updated browsers: curl, Python, Node, Java, git, package managers, CI runners, installer and self updater paths, server to server integrations, embedded devices with frozen bundles, and the Windows code signing chain. Why does revocation by eMudhra and Sectigo not solve it? Because revocation only protects clients that actually query it. Most non-browser clients skip that check entirely in their default configuration and simply trust their local bundle. What is the quickest useful step? Update CA bundles and CI images, enable revocation checks for critical calls, and stop piping installers from the web into a shell on machines you care about.

About the author

I am Marcel, I build d4sn3st.dev and run the HUNTER cyberdeck on a Pixel 6a. This article rests on my own A/B measurement from October 23, 2026 in the Linux environment on my phone and on the documents listed below. Last reviewed October 23, 2026. Details of the setup: HUNTER Cyberdeck blog

Sources

  • Scott Helme: Eight Bits of Trust; eMudhra's CA keys cracked in milliseconds, https://scotthelme.co.uk/eight-bits-of-trust-emudhras-ca-keys-cracked-in-milliseconds/ (retrieved October 23, 2026)
  • Mozilla dev-security-policy: blocking announcement, https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/0R3XLJL6FuE/m/Kp1v6CPuAAAJ (retrieved October 23, 2026)
  • Hacker News discussion at 927 points on October 23, 2026, https://news.ycombinator.com/item?id=50177916 (retrieved October 23, 2026)
  • Test objects of the own measurement: revoked.badssl.com and untrusted-root.badssl.com (measured October 23, 2026, cyberdeck)
  • Own measurement: curl and Python A/B test on the cyberdeck, protocol in the article (October 23, 2026)