Skip to content

The eMudhra CA Keys Were One Byte Long. Does Your Container Still Trust Them?

Published Last updated 6 min read

Key takeaways

  • Eight eMudhra intermediate CA private keys were tiny numbers such as 4, 56 and 115, so recovering them meant trying 1, 2, 3 and so on.
  • Chrome blocked the intermediates through CRLSet within hours, and Mozilla's OneCRL reached standard Firefox in under three hours.
  • Per Endor Labs, default TLS stacks in Python, Go, Node and most CLI tools do not check revocation, so only a trust store update removes the risk.
  • certifi 2026.10.19 and Alpine ca-certificates 20261020-r0 carry the change, while Java should be treated as unconfirmed until your JDK vendor says otherwise.
  • Rebuild base images on a schedule, for example weekly in CI with docker build --pull --no-cache, so trust store versions are something you can query.

Eight private keys behind eMudhra intermediate CAs turned out to be single-byte numbers, guessable in milliseconds. Chrome and Firefox blocked them the same day. Your base image, your Python bundle and your JVM keystore blocked nothing until someone rebuilt them.

TL;DR (for non-technical readers)

  • A certificate authority, one of the organizations that vouch for websites being genuine, created eight signing keys so weak that anyone could have worked them out.
  • Web browsers shut the door within hours. But servers, scripts, build systems and apps keep their own list of who to trust, and those lists do not update themselves.
  • The risk for a business is that an attacker could impersonate a trusted service to your software, not to your customers’ browsers.
  • Ask your tech team: “Where do our services get their list of trusted certificate authorities, and when was it last refreshed?”

What happened

The facts, from eMudhra’s statement, Scott Helme’s analysis and the dev.to summary:

Comparison of expected random 384-bit CA keys versus the tiny numbers 4, 56 and 115 that were actually found, so the search is just 1, 2, 3 and so on.

  • A researcher reported on October 17, 2026 that private keys of eight eMudhra intermediate CAs were compromised.
  • Helme found the keys were not random 384-bit values. They were tiny numbers such as 4, 56 and 115, so the “search” is trying 1, 2, 3 and so on. Google’s wording is that the keys were generated with insufficient randomness. That is technically true and a big understatement.
  • eMudhra says a flaw in the key wrap/unwrap step of a ceremony on April 30, 2025 produced a second, weak key set next to the strong one, and the weak set was what got published to root programs.
  • eMudhra also says root keys were not affected, and that none of the eight intermediates issued a certificate for a real site. One was in production but had issued nothing.
  • Chrome blocked the intermediates through CRLSet within hours. Mozilla’s OneCRL reached standard Firefox in under three hours.
  • According to Google’s security blog, the emSign ECC Root CA - G3 is also being removed from the Chrome Root Store, rolling out October 27, 2026.

The gap

Trust store map: browsers block compromised CAs in hours, while Debian/Ubuntu images, Python certifi, Java cacerts and vendored stores stay stale until rebuilt, upgraded or released.

Browser            -> fast distrust lane (CRLSet, OneCRL)   -> blocked in hours
Debian/Ubuntu img  -> ca-certificates package, baked in      -> stale until rebuilt
Python (certifi)   -> its own bundle                         -> stale until upgraded
Java (cacerts)     -> JDK keystore                           -> wait for JDK vendor
Vendored (Go, Node, mobile, firmware) -> frozen snapshots    -> depends on release

Per Endor Labs, certifi 2026.10.19 and Alpine ca-certificates 20261020-r0 carry the change, and Java should be treated as unconfirmed until your JDK vendor says otherwise. Their key point: default TLS stacks in Python, Go, Node and most CLI tools do not check revocation, so only a trust store update removes the risk.

What practitioners are saying

  • Process failure. On LWN, one commenter asked how an HSM could ever produce a one-byte key, suspecting the problem runs deeper than one buggy script.
  • Marketing versus reality. Another noted the CA advertised post-quantum and zero-trust language while shipping what amounted to one byte of entropy.
  • Storage doubts. A third pointed out that eMudhra’s trouble verifying its own CRL signatures suggests keys were not held in HSMs the way they should be.
  • A structural fix. One commenter proposed requiring HSM attestation for every root and intermediate, so key generation can be proven.
  • The “it was contained” view. Not everyone saw an emergency. Because no certificate chaining to the six TLS intermediates had been logged in Certificate Transparency, browsers that require CT would not have accepted a forged one anyway. Helme’s counterpoint is that curl and Python happily accepted a forged chain in his proof of concept.

My take

I side with the people who say the CA is the story and the people who say the trust store is the lesson, and I think both are right at different layers.

The browser response was excellent and I do not want to undersell it. Hours, not weeks. But that speed only protects the one client type built for it. The honest framing is that the web PKI has a fast lane for browsers and a slow lane for everything else, and most backend traffic is in the slow lane.

Where I disagree with the “contained” view: no certificate being logged is not a guarantee about the future. The keys are public, so the only thing standing between your internal client and a forged cert is whether your trust store still lists the parent. That is something you can fix with a rebuild, so do it.

For teams, this argues for one boring policy: base images are rebuilt on a schedule, and trust store versions are something you can query, not something you hope about. It also supports CA diversity and short certificate lifetimes, because swapping a CA is much easier when you have practiced it.

Try it in 15 minutes

1. List what makes outbound TLS calls

Five-step 15-minute trust store audit: list TLS callers, check OS bundle, check language bundles, inspect server certificates, rebuild images weekly.

Services, workers, CI runners, scripts. Ask “which trust store”, not “which version”.

2. Check the OS bundle in a running container

docker exec <container> sh -c 'ls -l /etc/ssl/certs/ca-certificates.crt; grep -c "BEGIN CERTIFICATE" /etc/ssl/certs/ca-certificates.crt'
docker exec <container> dpkg -l ca-certificates
docker exec <container> sh -c 'ls /etc/ssl/certs | grep -i emsign'

Compare the ca-certificates version with what your distro ships now (Endor lists Debian and Ubuntu fixed versions). A file date older than October 17 on a long-lived image means rebuild.

3. Check language bundles

python -c "import certifi; print(certifi.__version__, certifi.where())"
pip install --upgrade certifi
keytool -list -cacerts -storepass changeit | grep -i emsign

requests uses certifi unless you override verify=, so one machine can hold two different truths. For Java, changeit is the default cacerts password. The fix is a newer JDK build, not hand-editing the keystore.

4. See what a server presents

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
openssl x509 -in certificate.pem -noout -issuer -subject -fingerprint -sha256

If an issuer names an eMudhra or emSign intermediate, find out why and when it renews.

Make it stick

docker build --pull --no-cache -t myapp:$(date +%Y%m%d) .

Run that weekly in CI. Fresh base images carry fresh trust stores.

What to learn next

Question

If a CA you depend on were distrusted tomorrow, could you say in under ten minutes which of your services would still trust it?

Originally published on DEV. Discussion happens there.

Frequently asked questions

How do I check if my container still trusts the eMudhra CA?
Run docker exec <container> sh -c 'ls /etc/ssl/certs | grep -i emsign' and check the ca-certificates version with dpkg -l ca-certificates. A file date older than October 17 on a long-lived image means rebuild.
Why do curl and Python still trust compromised CAs after browsers block them?
Browsers have a fast distrust lane (CRLSet, OneCRL), but Python, Go, Node and most CLI tools do not check revocation. Their trust stores only change when you update or rebuild.
How do I update the Python and Java trust stores?
For Python, run pip install --upgrade certifi. For Java, the fix is a newer JDK build, not hand-editing the keystore.
Were eMudhra's root keys affected?
eMudhra says root keys were not affected, and that none of the eight intermediates issued a certificate for a real site. One was in production but had issued nothing.
Share Share on X Share on LinkedIn

About the author

Security Engineer & Technical Writer

Security engineer and technical writer based in Batam, Indonesia. I review and harden MCP servers and AI agents, build security automation, and write up what holds and what fails. I also train on DNS security as a PANDI .id Academy trainer.

All writing | RSS | Markdown