eMudhra CA 私钥安全事件:Java 运行环境(JRE)风险评估与修复指南
© All rights reserved. • 西安知行软件有限公司 • 陕ICP备09022277号
适用对象:运行 Java 应用(使用 JRE/JDK 建立 HTTPS/TLS 连接)的系统管理员与运维人员
推荐方案:从 JRE 证书库cacerts中删除受影响的根证书。我们提供 Windows 与 Linux/Docker 命令行脚本,见第 4 节。
摘要
- 发生了什么: eMudhra 的 8 个中间 CA 私钥因随机数缺陷可被轻易算出,任何人都能用它们伪造任意网站的 HTTPS 证书。相关证书已被吊销,Chrome、Firefox 和操作系统已完成拦截。
- 为什么 Java 应用仍受影响: Java 使用自己的证书库(JRE 中的
cacerts),不使用操作系统证书库,并且默认不检查证书吊销。我们实测,未修复的 JRE 仍会接受这些伪造链路上的证书。 - 风险程度: 攻击者需要能拦截 Java 应用的网络流量(如同一内网、DNS 劫持、恶意代理)。目前未见实际利用的报告,整体风险较低,但建议尽快处理。
- 您需要做什么:
- 找到 Java 应用实际使用的 JRE(4.2 节)。
- 运行我们提供的脚本,先检查再修复(Windows 见 4.3 节,Linux/Docker 见 4.4 节)。
- 重启 Java 应用,并按 4.6 节验证。
- 请记住: 升级或重装 JRE 后,
cacerts会被覆盖,需要重新运行检查。如果您使用的软件自带 JRE,请关注软件供应商发布的修复版本。
1. 问题简介
2026 年 10 月 17 日,安全研究人员报告 eMudhra 的 8 个中间 CA(Intermediate CA)私钥可被轻易恢复。这些 P-384 椭圆曲线私钥的随机性严重不足,实际私钥整数均小于 256。8 个密钥中,6 个用于 TLS 证书签发,2 个用于代码签名。eMudhra / Sectigo 已吊销相关证书,Chrome 和 Firefox 也已部署针对性的信任阻断。
本事件涉及中间 CA,并非 eMudhra 根 CA 私钥泄露。虽然官方称受影响中间 CA 没有签发最终实体证书,但这不等于尚未更新的 TLS 客户端没有被伪造证书攻击的可能。
2. 影响范围和安全风险
2.1 可能受影响的运行环境
- Java 服务使用默认 JRE/JDK
cacerts或自定义 TrustStore,且其信任锚可建立通往上述受影响中间 CA 的证书链。 - 运行的 Java TLS 客户端未可靠验证吊销状态(包括完全未检查 CRL/OCSP,或吊销服务不可达时允许继续连接)。
- 使用 JSSE / HTTPS / HTTP 客户端 / TLS Socket 的出站连接;也可能涉及 Java 应用充当 TLS 服务端并使用客户端证书认证的情形(取决于其信任链验证方式)。
- 使用独立证书库和验证器的组件(如原生 OpenSSL、嵌入式运行时或第三方 TLS Provider)需要分别检查,不应假定修改 Java
cacerts就能覆盖。
已知可能涉及的信任路径如下。Mozilla 公告列出了 3 个 TLS 根,另有 1 个根只涉及代码签名。指纹见附录表 C。
emSign Root TLS CA - G3:6 个 TLS 中间 CA 的直接签发者。emSign ECC Root CA - G3:可经emSign Root TLS CA - G3等下级链,连到受影响的中间 CA。AAA Certificate Services:Sectigo 对全部 8 个中间 CA 做了交叉签名。emSign Root CS CA - G3:2 个代码签名中间 CA 的签发者,仅影响代码签名。
重要: 只删除其中一个根是不够的,只要还信任其他任一路径,风险就仍然存在。例如,已从 cacerts 中删除 emSign ECC Root CA - G3,但仍信任 AAA Certificate Services,那么经 Sectigo 交叉签名的受影响中间 CA 证书链依然有效。所以修复时必须把这些根一起删除(第 4 节脚本会自动处理)。但不要删除所有 Comodo/Sectigo 根 CA,那样会影响正常 HTTPS 连接,只需删除 AAA Certificate Services 这一个。
2.2 安全风险
- TLS 服务器身份冒充: 攻击者能够为任意域名构造被某些未修复客户端接受的伪造证书。
- 中间人攻击(MITM): 如攻击者还能控制 DNS、代理或网络流量,可能读取或修改 TLS 通信。
- 代码签名信任风险: 两个受影响的代码签名 CA 对相关代码签名验证场景也有潜在影响;普通 HTTPS 客户端的主要关注对象是 6 个 TLS 中间 CA。
私钥可恢复不等于已经发生实际入侵;是否成功攻击仍取决于证书用途、具体信任路径、证书透明度、吊销检查和网络条件。
3. 安全风险自测方法
以下方法用于初步排查。TrustStore 中存在某个根证书并不单独证明可以遭受攻击;TrustStore 中不存在它,也不证明其他信任路径或 TLS 实现已经安全。
3.1 检查 JDK 版本及实际使用的 TrustStore
java -version
keytool -list -cacerts -storepass changeit
确保使用的是应用实际使用的 JRE 中的 java 和 keytool(查找方法见 4.2 节),并核对是否通过 -Djavax.net.ssl.trustStore=... 设置了自定义库(或应用代码自行加载了其他 TrustStore)。多个 JDK/JRE、容器镜像和应用自带运行时应逐一排查。
3.2 检查常见相关根 CA
Linux / macOS:
keytool -list -cacerts -storepass changeit | grep -Ei 'emsign|emudhra|aaa_certificate_services'
Windows PowerShell:
keytool -list -cacerts -storepass changeit | Select-String 'emsign|emudhra|aaa_certificate_services'
重点观察 emSign ECC Root CA - G3 和 AAA Certificate Services。注意:名称匹配仅用于粗略排查,更准确的方法是直接运行第 4 节脚本的检查模式,它按证书指纹匹配,不会修改任何文件。
3.3 用测试站点确认实际连接行为(可选)
第三方安全研究人员提供了两个测试站点,它们使用的正是受影响中间 CA 签发的证书:
https://dv-g3b-emudhra.we-have-a-problem.com/:eMudhra 原签发路径。https://dv-g3b-aaa-emudhra.we-have-a-problem.com/:Sectigo(AAA Certificate Services)交叉签名路径。
如果应用支持配置出站 HTTPS 连接(如 HTTP/REST 连接、Webhook、连接测试按钮),可以临时配置一个指向上述地址的连接,再观察结果:
| 结果 | 含义 |
|---|---|
| 连接成功 | 存在风险:该 JRE 仍信任受影响的证书链,需要按第 4 节修复 |
报证书错误,如 PKIX path building failed、unable to find valid certification path to requested target |
该 JRE 已不信任受影响的证书链 |
| 超时或无法解析域名 | 网络不通(如需代理或防火墙限制),无法据此判断 |
注意:
- 这些证书已被吊销,但 Java 默认不检查吊销状态,因此未修复的 JRE 通常仍会连接成功。我们在 Temurin 17.0.5 上实测:修复前两个地址都能正常访问,执行第 4 节脚本后都报
PKIX path building failed。 - 浏览器、
curl和 Windows 自带工具会使用操作系统的证书库并检查吊销,通常已经拒绝这两个地址。它们的结果不能代表 Java 应用的状态。 - 两个站点都要测试,只有两个都失败才说明两条信任路径均已阻断。
- 站点由第三方提供,可能随时下线。无法访问时,以第 4 节脚本检查模式的结果为准。
- 测试完成后请删除临时添加的连接配置。
4. 安全修复
4.1 修复思路
Java 验证 HTTPS 证书时,只信任 JRE 证书库 cacerts(或应用另行指定的 TrustStore)中的根证书。受影响的 8 个中间 CA 并不在 cacerts 里:它们由服务器在握手时发送,Java 再沿着证书链追溯到 cacerts 中的根。因此,最直接的修复方法是:从 cacerts 中删除能为这些中间 CA 担保的根证书。这样无论攻击者走 eMudhra 原签发路径,还是走 Sectigo 交叉签名路径,证书链都无法建立。
可任选以下一种方式:
- 升级软件:如果软件供应商已发布内置修复后 JRE 的新版本,直接升级。请使用完整安装包,确保内置 JRE 一并更新。升级后仍建议用 4.3 或 4.4 节脚本的检查模式确认。
- 运行修复脚本:使用 4.3 节(Windows)或 4.4 节(Linux / Docker)的脚本,删除证书库中的受影响证书。
脚本按证书 SHA-256 指纹精确匹配,不按名称匹配(指纹见附录表 B、表 C),会删除以下证书:
- 3 个 emSign 根:
emSign ECC Root CA - G3、emSign Root TLS CA - G3、emSign Root CS CA - G3。 - 1 个 Sectigo 旧根:
AAA Certificate Services。 - 证书库中如果手工导入过受影响的 16 张中间 CA 证书,也一并删除。
不同 JRE 版本内置的根不同。例如 Temurin 17.0.20 只含 emSign ECC Root CA - G3,而 17.0.5 和 8u332 还含 AAA Certificate Services。脚本只删除实际存在的条目,其他证书不受影响。
兼容性影响: 删除后,证书链只能追溯到上述 4 个根的网站将无法通过该 JRE 访问。emSign 根主要服务于 eMudhra 签发的证书。
AAA Certificate Services是 Sectigo(原 Comodo)的旧根,现在的 Sectigo 证书通常链接到USERTrust等其他根,一般不受影响。脚本不会删除其他 Sectigo/Comodo 根。如果业务需要连接使用 eMudhra 证书的服务器,请先在测试环境确认。
为什么不只使用
blocked.certs: JDK 的lib/security/blocked.certs只对 Java 自带的 TLS 实现(JSSE)生效。部分第三方 TLS 组件只读取cacerts,会忽略它。此外它必须列全 16 个中间证书指纹(含交叉签名),且不是所有 JDK 发行版都支持手工修改。删除根证书对所有读取cacerts的组件都有效。
4.2 准备:确认实际使用的 JRE
一台服务器上可能装有多个 JRE(系统 Java、软件安装目录自带的 jre、容器镜像等),必须修复应用实际使用的那一个。最可靠的办法是查看正在运行的 Java 进程。
Windows(以管理员身份打开 PowerShell,否则看不到服务进程):
Get-Process | ForEach-Object {
$jvm = $_.Modules | Where-Object ModuleName -eq 'jvm.dll' | Select-Object -First 1
if ($jvm) { [pscustomobject]@{ PID = $_.Id; Name = $_.ProcessName; JavaHome = (Split-Path (Split-Path (Split-Path $jvm.FileName))) } }
} 2>$null | Format-Table -AutoSize
该命令也能找到以 Windows 服务方式内嵌运行、进程名不是 java.exe 的 Java 程序。输出中的 JavaHome 就是 4.3 节需要的路径。
Linux:
for p in $(pgrep -x java); do
echo "PID $p Java home: $(sudo readlink -f /proc/$p/exe | sed 's#/bin/java$##')"
done
另外请注意:
- 如果启动参数或环境变量
JAVA_TOOL_OPTIONS中带有-Djavax.net.ssl.trustStore=<路径>,说明应用使用自定义证书库,需要用脚本的-Keystore(Windows)或KEYSTORE=(Linux)参数另外处理它。如果该证书库密码不是默认的changeit,还需传入-StorePass或STOREPASS=。 - 软件安装目录下自带的 JRE 通常位于
<安装目录>\jre\下的某个子目录。 - JDK 8 可直接传入 JDK 目录或其下的
jre目录,脚本会自动找到jre\lib\security\cacerts。 - 修改证书库前,应用不必停止,但修改后必须重启才会生效。
4.3 Windows 修复脚本
-
将下面的脚本保存为
Remove-eMudhraCA.ps1。 -
以管理员身份打开 PowerShell,进入脚本所在目录。
-
先用检查模式查看,不做任何修改(将路径替换为 4.2 节找到的
JavaHome):powershell -ExecutionPolicy Bypass -File .\Remove-eMudhraCA.ps1 -JavaHome "C:\path\to\jre" -CheckOnly -
确认后去掉
-CheckOnly执行删除:powershell -ExecutionPolicy Bypass -File .\Remove-eMudhraCA.ps1 -JavaHome "C:\path\to\jre" -
重启使用该 JRE 的所有 Java 应用或服务。
可选参数:-Keystore <路径>(自定义证书库)、-StorePass <密码>(默认 changeit)。不传 -JavaHome 时使用环境变量 JAVA_HOME。
param(
[string]$JavaHome = $env:JAVA_HOME,
[string]$Keystore,
[string]$StorePass = 'changeit',
[switch]$CheckOnly
)
$ErrorActionPreference = 'Continue'
# SHA-256 fingerprints: 4 roots + 8 eMudhra intermediates + 8 Sectigo (AAA) cross-signs
$Distrusted = @{
'86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B' = 'emSign ECC Root CA - G3'
'D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4' = 'AAA Certificate Services'
'7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D' = 'emSign Root TLS CA - G3'
'F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5' = 'emSign Root CS CA - G3'
'E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838' = 'EM EV TLS CA - G3B'
'98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B' = 'EM OV TLS CA - G3B'
'42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493' = 'EM DV TLS CA - G3B'
'432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3' = 'EM EV TLS CA - G3C'
'CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F' = 'EM OV TLS CA - G3C'
'0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19' = 'EM DV TLS CA - G3C'
'9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4' = 'EM OV CS CA - G3B'
'4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1' = 'EM OV CS CA - G3C'
'579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512' = 'EM EV TLS CA - G3B (AAA cross-sign)'
'5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699' = 'EM OV TLS CA - G3B (AAA cross-sign)'
'45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE' = 'EM DV TLS CA - G3B (AAA cross-sign)'
'FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F' = 'EM EV TLS CA - G3C (AAA cross-sign)'
'7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489' = 'EM OV TLS CA - G3C (AAA cross-sign)'
'62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72' = 'EM DV TLS CA - G3C (AAA cross-sign)'
'A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8' = 'EM OV CS CA - G3B (AAA cross-sign)'
'84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD' = 'EM OV CS CA - G3C (AAA cross-sign)'
}
if (-not $JavaHome) { Write-Host 'ERROR: Java home not found. Pass it with -JavaHome.'; exit 1 }
$keytool = Join-Path $JavaHome 'bin\keytool.exe'
if (-not $Keystore) {
$Keystore = Join-Path $JavaHome 'lib\security\cacerts'
$jdk8 = Join-Path $JavaHome 'jre\lib\security\cacerts'
if (-not (Test-Path $Keystore) -and (Test-Path $jdk8)) { $Keystore = $jdk8 }
}
if (-not (Test-Path $keytool)) { Write-Host "ERROR: keytool not found: $keytool"; exit 1 }
if (-not (Test-Path $Keystore)) { Write-Host "ERROR: keystore not found: $Keystore"; exit 1 }
Write-Host "Keystore: $Keystore"
function Get-DistrustedEntries {
# Force English output so parsing works on Chinese Windows.
$out = & $keytool '-J-Duser.language=en' '-J-Duser.country=US' -list -v -keystore $Keystore -storepass $StorePass 2>$null
$total = 0; $alias = $null
foreach ($line in $out) {
if ($line -match '^Alias name: (.+)$') { $alias = $Matches[1]; $total++ }
elseif ($alias -and $line -match '^\s*SHA256:\s*([0-9A-F:]+)') {
$fp = $Matches[1] -replace ':', ''
if ($Distrusted.ContainsKey($fp)) { [pscustomobject]@{ Alias = $alias; Name = $Distrusted[$fp] } }
$alias = $null
}
}
if ($total -eq 0) { throw 'Could not read any entry from the keystore (wrong password or path?).' }
}
try {
$found = @(Get-DistrustedEntries)
if ($found.Count -eq 0) { Write-Host 'OK: no affected certificate in this keystore.'; exit 0 }
$found | ForEach-Object { Write-Host "Found: $($_.Name) [alias: $($_.Alias)]" }
if ($CheckOnly) { Write-Host "$($found.Count) affected certificate(s) found. Run again without -CheckOnly to remove."; exit 2 }
$backup = "$Keystore.bak-$(Get-Date -Format yyyyMMddHHmmss)"
Copy-Item $Keystore $backup -ErrorAction Stop
Write-Host "Backup: $backup"
foreach ($e in $found) {
& $keytool '-J-Duser.language=en' -delete -keystore $Keystore -storepass $StorePass -alias $e.Alias 2>$null
if ($LASTEXITCODE -ne 0) { throw "Failed to delete '$($e.Name)'. Run PowerShell as Administrator." }
Write-Host "Removed: $($e.Name)"
}
$left = @(Get-DistrustedEntries)
if ($left.Count -gt 0) { throw "Still present: $(($left | ForEach-Object Name) -join '; ')" }
Write-Host 'OK: all affected certificates removed. Restart every Java application that uses this JRE.'
exit 0
} catch {
Write-Host "ERROR: $_"
exit 1
}
4.4 Linux / Docker 修复脚本
-
将下面的脚本保存为
remove-emudhra-ca.sh。如果是在 Windows 上编辑后再复制到 Linux,请先转换换行符:sed -i 's/\r$//' remove-emudhra-ca.sh。 -
检查模式(只查看,不修改):
sudo sh remove-emudhra-ca.sh /path/to/jre --check -
执行删除:
sudo sh remove-emudhra-ca.sh /path/to/jre -
重启使用该 JRE 的所有 Java 应用或服务(例如
sudo systemctl restart <服务名>)。
自定义证书库或密码:sudo KEYSTORE=/path/to/truststore.jks STOREPASS=<密码> sh remove-emudhra-ca.sh /path/to/jre。
#!/bin/sh
# Usage: sh remove-emudhra-ca.sh <java-home> [--check]
# Optional env: KEYSTORE (default <java-home>/lib/security/cacerts), STOREPASS (default changeit)
set -u
if [ $# -lt 1 ]; then echo "usage: sh remove-emudhra-ca.sh <java-home> [--check]"; exit 1; fi
JAVA_DIR=$1
MODE=${2:-}
KEYTOOL="$JAVA_DIR/bin/keytool"
if [ -z "${KEYSTORE:-}" ]; then
KEYSTORE="$JAVA_DIR/lib/security/cacerts"
[ ! -f "$KEYSTORE" ] && [ -f "$JAVA_DIR/jre/lib/security/cacerts" ] && KEYSTORE="$JAVA_DIR/jre/lib/security/cacerts"
fi
STOREPASS=${STOREPASS:-changeit}
# SHA-256 fingerprints: 4 roots + 8 eMudhra intermediates + 8 Sectigo (AAA) cross-signs
DISTRUSTED='
86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B emSign ECC Root CA - G3
D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4 AAA Certificate Services
7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D emSign Root TLS CA - G3
F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5 emSign Root CS CA - G3
E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838 EM EV TLS CA - G3B
98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B EM OV TLS CA - G3B
42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493 EM DV TLS CA - G3B
432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3 EM EV TLS CA - G3C
CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F EM OV TLS CA - G3C
0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19 EM DV TLS CA - G3C
9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4 EM OV CS CA - G3B
4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1 EM OV CS CA - G3C
579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512 EM EV TLS CA - G3B (AAA cross-sign)
5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699 EM OV TLS CA - G3B (AAA cross-sign)
45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE EM DV TLS CA - G3B (AAA cross-sign)
FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F EM EV TLS CA - G3C (AAA cross-sign)
7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489 EM OV TLS CA - G3C (AAA cross-sign)
62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72 EM DV TLS CA - G3C (AAA cross-sign)
A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8 EM OV CS CA - G3B (AAA cross-sign)
84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD EM OV CS CA - G3C (AAA cross-sign)
'
[ -x "$KEYTOOL" ] || { echo "ERROR: keytool not found: $KEYTOOL"; exit 1; }
[ -f "$KEYSTORE" ] || { echo "ERROR: keystore not found: $KEYSTORE"; exit 1; }
echo "Keystore: $KEYSTORE"
# Prints "<name>|<alias>" for each affected entry; exits non-zero if the keystore cannot be read.
find_distrusted() {
"$KEYTOOL" -J-Duser.language=en -J-Duser.country=US -list -v -keystore "$KEYSTORE" -storepass "$STOREPASS" 2>/dev/null |
awk -v list="$DISTRUSTED" '
BEGIN { n = split(list, l, "\n"); for (i = 1; i <= n; i++) if (length(l[i]) > 65) bad[substr(l[i], 1, 64)] = substr(l[i], 66) }
/^Alias name: / { alias = substr($0, 13); total++; next }
alias != "" && /^[ \t]*SHA256:/ { fp = $2; gsub(":", "", fp); if (fp in bad) print bad[fp] "|" alias; alias = "" }
END { if (total == 0) exit 2 }'
}
FOUND=$(find_distrusted) || { echo "ERROR: could not read the keystore (wrong password or path?)"; exit 1; }
if [ -z "$FOUND" ]; then echo "OK: no affected certificate in this keystore."; exit 0; fi
echo "$FOUND" | while IFS='|' read -r name alias; do echo "Found: $name [alias: $alias]"; done
if [ "$MODE" = "--check" ]; then echo "Affected certificate(s) found. Run again without --check to remove."; exit 2; fi
BACKUP="$KEYSTORE.bak-$(date +%Y%m%d%H%M%S)"
cp -p "$KEYSTORE" "$BACKUP" || { echo "ERROR: backup failed (run as root?)"; exit 1; }
echo "Backup: $BACKUP"
echo "$FOUND" | while IFS='|' read -r name alias; do
if "$KEYTOOL" -J-Duser.language=en -delete -keystore "$KEYSTORE" -storepass "$STOREPASS" -alias "$alias" >/dev/null 2>&1
then echo "Removed: $name"; else echo "Failed to delete: $name (run as root?)"; fi
done
LEFT=$(find_distrusted) || { echo "ERROR: could not re-read the keystore"; exit 1; }
if [ -n "$LEFT" ]; then echo "ERROR: still present:"; echo "$LEFT"; exit 1; fi
echo "OK: all affected certificates removed. Restart every Java application that uses this JRE."
Docker 镜像: 修改正在运行的容器只是临时措施,容器重建后会恢复原状。请在 Dockerfile 中加入以下步骤并重新构建镜像(如果镜像以非 root 用户运行,请在此步骤前临时切换为 USER root):
COPY remove-emudhra-ca.sh /tmp/
RUN sh /tmp/remove-emudhra-ca.sh "$(dirname "$(dirname "$(readlink -f "$(command -v java)")")")" \
&& rm /tmp/remove-emudhra-ca.sh
4.5 脚本结果说明
| 退出码 | 含义 | 处理方式 |
|---|---|---|
0 |
证书库中已没有受影响证书(本次已删除,或原本就没有) | 如有删除,重启相关 Java 应用 |
1 |
出错:找不到 JRE 或证书库、密码错误、权限不足等 | 按提示处理。权限问题请以管理员或 root 身份运行 |
2 |
仅检查模式:发现受影响证书,未做修改 | 去掉检查参数再运行一次 |
脚本删除前会自动备份为 cacerts.bak-<时间戳>,删除后会重新读取证书库,确认受影响证书已全部清除。脚本可重复运行。
4.6 修复后验证
- 再次运行检查模式,应输出
OK: no affected certificate in this keystore.,退出码为0。 - 确认已重启使用该 JRE 的所有 Java 应用或服务。
- 确认应用常用的 HTTPS 连接(如合作方接口、云服务)仍然正常。
- (可选)按 3.3 节再测试一次两个测试站点,两个都应报证书错误。
4.7 回滚与注意事项
- 回滚: 将备份文件复制回原位置并重启应用。
- Windows:
Copy-Item "<cacerts路径>.bak-<时间戳>" "<cacerts路径>" -Force - Linux:
sudo cp -p <cacerts路径>.bak-<时间戳> <cacerts路径>
- Windows:
- JRE 被替换后需重新执行: 升级或重装 JRE、或升级软件时替换了内置 JRE,
cacerts会被新文件覆盖。完成这类操作后,请重新运行检查模式。 - 逐一处理: 每个 JRE、每个自定义证书库、每个容器镜像都需要单独处理。
- 范围说明: 本修复只影响使用该 JRE 证书库的 Java 程序,不修改操作系统证书库、浏览器或使用 OpenSSL 等其他 TLS 库的组件,这些需要按各自厂商的指引处理。
4.8 修复完成检查清单
- [ ] 已列出服务器上所有正在运行的 Java 进程及其 JRE 路径(4.2 节)。
- [ ] 每个 JRE 的
cacerts和每个自定义证书库,检查模式均返回OK。 - [ ] 已重启所有相关 Java 应用或服务。
- [ ] 应用常用的 HTTPS 连接正常。
- [ ] Docker 镜像已在 Dockerfile 中加入修复步骤并重新构建、部署。
- [ ] 已记录:今后升级 JRE 或软件后需重新执行检查。
附录:受影响证书清单
以下数据来自 Mozilla 安全团队在 oss-security 发布的通告。我们于 2026-10-22 从 crt.sh 下载了全部原始证书,逐一重新计算并核对。修复脚本内置的指纹即来自表 B 和表 C;安全团队也可以用这些数据核对资产扫描或其他安全设备的规则。
勘误: Mozilla 原通告中有 2 个 SPKI 只有 63 位十六进制,少了最后一位:
EM EV TLS CA - G3B缺末尾的1,EM OV TLS CA - G3B缺末尾的b。直接照抄原通告的规则永远不会命中这两个中间 CA。下表 A 已改正。其余 6 个 SPKI 和全部 16 个证书 SHA-256 均与原通告一致。
表 A:受影响公钥(SHA-256 SPKI,小写十六进制)
eMudhra 原证书和 Sectigo 交叉签名证书使用同一把公钥,因此每个中间 CA 只有一条。适用于支持按公钥(SPKI)阻断的安全设备或 TLS 组件。
| 受影响 CA | 用途 | SHA-256 SPKI |
|---|---|---|
| EM EV TLS CA – G3B | TLS | e0254c7fcdc265e2f0f8a428dabb75ab4ae039b4872d62f5ee57f0877d5a8a41 |
| EM OV TLS CA – G3B | TLS | 43bffa19d6ef651451f6841e019931e96f6f2eaeff28ab4b6f1ce4576de2717b |
| EM DV TLS CA – G3B | TLS | ebed6475c2c2d80fcb199f3fe8dd6e6b3ce60b77eb15d4e42c3dec0c6f5b5105 |
| EM EV TLS CA – G3C | TLS | 5c0660a2b164910d8ff589c18e14552e9e0ec237dcefe2cb736d41a4811f81a0 |
| EM OV TLS CA – G3C | TLS | 63ecefee431d49523bc1136c4e72d0206a477086caf606cd5cf025686c109cf8 |
| EM DV TLS CA – G3C | TLS | 49dd353dc2b919a3770645117d0e60c16115e7d1bc259eefe734b00ea434fe5f |
| EM OV CS CA – G3B | Code signing | ec4ffa1ad0ac88081314b7aaffef6a91f97cd57291bb5ba686c5415f5eb4f6b6 |
| EM OV CS CA – G3C | Code signing | 92c063d67e8f14db71d288e11266ffdaf69cbf115f7bab14f3bc2f68d25e0e30 |
表 B:受影响中间 CA 证书(整张证书的 SHA-256 指纹)
共 16 张:8 张 eMudhra 原证书,以及 8 张 Sectigo(AAA Certificate Services)交叉签名证书。只能按证书指纹匹配的机制(如 JDK blocked.certs、Windows“不受信任的证书”存储、资产扫描工具)必须两组都列入,只列原证书会漏掉交叉签名路径。
| 受影响 CA | 签发者 | 证书 SHA-256 | 到期 |
|---|---|---|---|
| EM EV TLS CA – G3B | emSign Root TLS CA – G3 | E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838 |
2040-04-29 |
| EM OV TLS CA – G3B | emSign Root TLS CA – G3 | 98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B |
2040-04-29 |
| EM DV TLS CA – G3B | emSign Root TLS CA – G3 | 42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493 |
2040-04-29 |
| EM EV TLS CA – G3C | emSign Root TLS CA – G3 | 432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3 |
2040-04-29 |
| EM OV TLS CA – G3C | emSign Root TLS CA – G3 | CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F |
2040-04-29 |
| EM DV TLS CA – G3C | emSign Root TLS CA – G3 | 0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19 |
2040-04-29 |
| EM OV CS CA – G3B | emSign Root CS CA – G3 | 9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4 |
2040-04-29 |
| EM OV CS CA – G3C | emSign Root CS CA – G3 | 4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1 |
2040-04-29 |
| EM EV TLS CA – G3B | AAA Certificate Services | 579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512 |
2029-01-01 |
| EM OV TLS CA – G3B | AAA Certificate Services | 5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699 |
2029-01-01 |
| EM DV TLS CA – G3B | AAA Certificate Services | 45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE |
2029-01-01 |
| EM EV TLS CA – G3C | AAA Certificate Services | FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F |
2029-01-01 |
| EM OV TLS CA – G3C | AAA Certificate Services | 7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489 |
2029-01-01 |
| EM DV TLS CA – G3C | AAA Certificate Services | 62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72 |
2029-01-01 |
| EM OV CS CA – G3B | AAA Certificate Services | A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8 |
2029-01-01 |
| EM OV CS CA – G3C | AAA Certificate Services | 84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD |
2029-01-01 |
表 C:相关根 CA
这些根本身没有泄露,但都能为受影响的中间 CA 担保,因此修复脚本会把它们从 JRE 证书库中删除。
| 根 CA | 涉及路径 | 证书 SHA-256 | SHA-256 SPKI |
|---|---|---|---|
| emSign Root TLS CA – G3 | 6 个 TLS 中间 CA 的签发者 | 7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D |
c619e4780ed8e0abeda24ad732567de4383a7ea941338f6d1a7d1b49ac792fe9 |
| emSign ECC Root CA – G3 | 经 emSign 下级链 | 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B |
8d417db2dd8bf5e3084d1e3f196d583849d81bdd4c00c70b9d39369e96b8c782 |
| AAA Certificate Services | Sectigo 交叉签名(全部 8 个) | D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4 |
bd153ed7b0434f6886b17bce8bbe84ed340c7132d702a8f4fa318f756ecbd6f3 |
| emSign Root CS CA – G3 | 2 个代码签名中间 CA 的签发者 | F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5 |
0a6c2b2ca5b364207954171987ac9d2178561569d8205457e77ca90c32ede282 |


