/** * eMudhra 中间证书信任检测 * * 2026 年 10 月,eMudhra 在 emSign Root TLS CA - G3 下签发的 6 张中间证书(EM DV/OV/EV TLS CA - G3B/G3C) * 被确认存在风险,Go 在 go1.27.3 / go1.26.10 中把它们加入了内置黑名单(GO-2026-6809 / CVE-2026-111943)。 * 这些中间证书同时有 Sectigo 的交叉签名,可以链到几乎所有系统都信任的 AAA Certificate Services 根证书。 * 本插件参照 https://emudhra.we-have-a-problem.com/#check ,通过客户端的 Go 后端(Plugins.HttpGet)访问 12 个测试站点,判断客户端是否仍然信任它们。 */ // 与 https://emudhra.we-have-a-problem.com/#check 保持一致:每张中间证书各测两条链, // 一条直接链到 emSign 根证书,一条经 Sectigo 交叉签名链到 AAA Certificate Services const TEST_SITES = [ { host: 'dv-g3b-emudhra.we-have-a-problem.com', type: 'DV', ca: 'EM DV TLS CA - G3B', root: 'emSign Root TLS CA - G3' }, { host: 'dv-g3c-emudhra.we-have-a-problem.com', type: 'DV', ca: 'EM DV TLS CA - G3C', root: 'emSign Root TLS CA - G3' }, { host: 'ov-g3b-emudhra.we-have-a-problem.com', type: 'OV', ca: 'EM OV TLS CA - G3B', root: 'emSign Root TLS CA - G3' }, { host: 'ov-g3c-emudhra.we-have-a-problem.com', type: 'OV', ca: 'EM OV TLS CA - G3C', root: 'emSign Root TLS CA - G3' }, { host: 'ev-g3b-emudhra.we-have-a-problem.com', type: 'EV', ca: 'EM EV TLS CA - G3B', root: 'emSign Root TLS CA - G3' }, { host: 'ev-g3c-emudhra.we-have-a-problem.com', type: 'EV', ca: 'EM EV TLS CA - G3C', root: 'emSign Root TLS CA - G3' }, { host: 'dv-g3b-aaa-emudhra.we-have-a-problem.com', type: 'DV', ca: 'EM DV TLS CA - G3B', root: 'AAA Certificate Services' }, { host: 'dv-g3c-aaa-emudhra.we-have-a-problem.com', type: 'DV', ca: 'EM DV TLS CA - G3C', root: 'AAA Certificate Services' }, { host: 'ov-g3b-aaa-emudhra.we-have-a-problem.com', type: 'OV', ca: 'EM OV TLS CA - G3B', root: 'AAA Certificate Services' }, { host: 'ov-g3c-aaa-emudhra.we-have-a-problem.com', type: 'OV', ca: 'EM OV TLS CA - G3C', root: 'AAA Certificate Services' }, { host: 'ev-g3b-aaa-emudhra.we-have-a-problem.com', type: 'EV', ca: 'EM EV TLS CA - G3B', root: 'AAA Certificate Services' }, { host: 'ev-g3c-aaa-emudhra.we-have-a-problem.com', type: 'EV', ca: 'EM EV TLS CA - G3C', root: 'AAA Certificate Services' } ] const ADVISORY_URL = 'https://pkg.go.dev/vuln/GO-2026-6809' const TEST_SITE_URL = 'https://emudhra.we-have-a-problem.com/#check' const FIXED_APP_VERSION = 'v1.28.1' // 结果分类 const RESULT = { pending: { text: '检测中', color: 'default' }, vulnerable: { text: '仍信任', color: 'red' }, blocked: { text: '已拦截', color: 'green' }, untrusted: { text: '证书不受信任', color: 'cyan' }, unknown: { text: '无法判断', color: 'orange' } } /** @type {EsmPlugin} */ export default (plugin) => { const configBool = (key, fallback) => { const raw = plugin[key] if (typeof raw === 'boolean') return raw if (typeof raw === 'string' && raw.trim()) return raw.trim() === 'true' return fallback } const configNumber = (key, fallback, min, max) => { const n = Number(plugin[key]) if (!Number.isFinite(n) || n <= 0) return fallback return Math.min(max, Math.max(min, Math.round(n))) } const errorText = (err) => { const text = typeof err === 'string' ? err : err?.message || String(err) // 去掉 Go 错误里的 `Get "https://...": ` 前缀 return text .replace(/^[A-Za-z]+ "[^"]*": /, '') .replace(/^tls: failed to verify certificate: /, '') .trim() } // 按 Go 的错误文本分类: // - x509: no valid chains built → Go 内置黑名单生效(go1.27.3+ / go1.26.10+) // - 其它证书错误(unknown authority / not trusted 等)→ 系统本身就不信任这条链 const classifyError = (text) => { if (/no valid chains built/i.test(text)) return 'blocked' if (/x509:|certificate|tls: failed to verify/i.test(text)) return 'untrusted' return 'unknown' } const getProxy = async () => { if (!configBool('UseKernelProxy', false)) return '' if (typeof Plugins.GetRequestProxy === 'function') { return (await Plugins.GetRequestProxy('kernel')) || '' } const endpoint = Plugins.useKernelApiStore().getProxyEndpoint?.() return endpoint ? `http://${endpoint.host}:${endpoint.port}` : '' } const testSite = async (site, proxy, timeout) => { try { const { status, body } = await Plugins.HttpGet( `https://${site.host}/`, { Connection: 'close' }, { Proxy: proxy, Insecure: false, Redirect: false, Timeout: timeout } ) const matched = typeof body === 'string' && body.includes('successfully connected') return { result: 'vulnerable', detail: `已连接(HTTP ${status}${matched ? ',测试页确认' : ''})` } } catch (err) { const detail = errorText(err) return { result: classifyError(detail), detail } } } const getAppInfo = () => { let env = {} try { env = Plugins.useEnvStore().env || {} } catch {} return { title: Plugins.APP_TITLE || env.appName || '客户端', version: Plugins.APP_VERSION || env.appVersion || '', platform: [env.os, env.arch].filter(Boolean).join('/') } } const compareVersion = (a, b) => { const pa = String(a) .replace(/^v/, '') .split(/[.-]/) .map((n) => parseInt(n) || 0) const pb = String(b) .replace(/^v/, '') .split(/[.-]/) .map((n) => parseInt(n) || 0) for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0) } return 0 } const openPanel = () => { const { defineComponent, reactive, computed, h, resolveComponent } = Vue const component = defineComponent({ template: /* html */ `