Skip to content

fix(proxy): leaf cert AKI must equal CA SKI; cap validity at 398 days (CA/B BR 2020-09)#92

Open
YiShan-X wants to merge 1 commit into
Mouseww:mainfrom
YiShan-X:fix/leaf-cert-aki-and-validity
Open

fix(proxy): leaf cert AKI must equal CA SKI; cap validity at 398 days (CA/B BR 2020-09)#92
YiShan-X wants to merge 1 commit into
Mouseww:mainfrom
YiShan-X:fix/leaf-cert-aki-and-validity

Conversation

@YiShan-X

Copy link
Copy Markdown

Summary

修复 MITM 代理签发叶子证书时的两个独立 bug:

  1. AKI 错位authorityKeyIdentifier 用了 keyIdentifier: true,node-forge 会拿叶子自己的公钥算 SHA-1 填进 AKI,每个域名都得到一个不同的"随机值"。RFC 5280 §4.2.1.1 规定 AKI 必须等于签发者的 SKI,否则 Android CertPathValidator、OkHttp、JSSE 等严格路径直接抛 TrustAnchor not found
  2. 叶子有效期过长LEAF_VALIDITY_DAYS = 825 是 2017-2020 间 Apple 的旧上限。CA/Browser Forum 自 2020-09-01 起把全行业叶子证书上限统一压到 398 天,Chrome / Firefox / iOS 15+ / Android Chrome 均按 398 天强校验,过长会直接返回 NET::ERR_CERT_VALIDITY_TOO_LONG

兼容性

LEAF_VALIDITY_DAYS365对所有 Apple 平台安全

Apple 版本 允许上限 365 是否 OK
iOS 14 及更早 825 天 ✅ 远低于
iOS 15+(2021-09 起) 398 天 ✅ 低于且留 33 天缓冲
macOS(任一当前版本) 398 天 ✅ 同上

AKI 改动只影响 ASN.1 结构语义,对所有 RFC 5280 实现的客户端都更正确,没有兼容性回归。

Reproduction (before fix)

同一张 CA(SKI = C7:75:A9:B7:F6:05:3C:C0:B6:16:3E:FF:9C:35:5C:3E:73:40:5D:BF)下:

mumu.163.com 叶子 AKI www.aliyun.com 叶子 AKI 有效期
修复前 92:05:7F…(≠ CA SKI) B7:C1:64…(≠ CA SKI) 824 天(>398)
修复后 C7:75:A9:B7…(= CA SKI) C7:75:A9:B7…(= CA SKI) 365 天

Verification

# 拉起 MITM 代理(默认 127.0.0.1:8888)
# 然后:
openssl s_client -connect www.aliyun.com:443 -proxy 127.0.0.1:8888 -showcerts </dev/null \
  | openssl x509 -noout -dates -ext authorityKeyIdentifier

# 期望:
#   notBefore=...
#   notAfter=...  # 与 notBefore 间隔 < 398 天
#   X509v3 Authority Key Identifier:
#       C7:75:A9:B7:F6:05:3C:C0:B6:16:3E:FF:9C:35:5C:3E:73:40:5D:BF

# 链路校验:
openssl verify -CAfile "$APPDATA/anything-analyzer/mitm-ca/ca-cert.pem" \
  -purpose sslserver leaf.pem
# 期望:leaf.pem: OK

端到端复现脚本(可放 tools/):CA + 两个域名叶子证书,用 openssl dump AKI/SKI/dates 并做 openssl verify

Diff

--- a/src/main/proxy/ca-manager.ts
+++ b/src/main/proxy/ca-manager.ts
@@ -6,7 +6,11 @@
 const CA_KEY_FILE = "ca-key.pem";
 const CA_CERT_FILE = "ca-cert.pem";
 const CA_VALIDITY_YEARS = 10;
-const LEAF_VALIDITY_DAYS = 825; // Apple max
+// Chrome / Chromium / Firefox / iOS 15+ / Android 自 2020-09-01 起统一上限 398 天
+// (CA/Browser Forum BR §6.3.2)。825 是 2017-2020 间 Apple 旧值,不再被任何现代
+// 浏览器接受 (Android Chrome 直接报 ERR_CERT_VALIDITY_TOO_LONG)。
+// 留 33 天缓冲避免边界期 cert 被拒。
+const LEAF_VALIDITY_DAYS = 365;
 const CACHE_MAX_SIZE = 500;
@@ -182,7 +186,10 @@
       { name: "subjectKeyIdentifier" },
       {
         name: "authorityKeyIdentifier",
-        keyIdentifier: true,
+        // AKI 必须等于 CA 的 SKI (RFC 5280 §4.2.1.1);node-forge 的
+        // keyIdentifier:true 会错用叶子自己的公钥,导致每个域名 AKI 都是
+        // "随机值",Android CertPathValidator 会判定 trust anchor 不匹配。
+        keyIdentifier: this.caCert!.generateSubjectKeyIdentifier().getBytes(),
       },
     ]);

Related

Checklist

  • 不影响 Apple 平台(365 < 398 新上限且 < 825 旧上限)
  • 通过 openssl verify -purpose sslserver 严格校验
  • 不改动磁盘上的 CA 文件(重启即生效,无需重装用户已信任的 CA)
  • 叶子缓存 contextCache 在进程重启后清空,无兼容性回归

Two independent bugs in CaManager.issueLeafCert were causing MITM
leaf certs to be rejected by Android Chrome and strict CertPathValidator
clients:

1. authorityKeyIdentifier.keyIdentifier was set to true, causing
   node-forge to compute the AKI from the LEAF's own public key. Per
   RFC 5280 §4.2.1.1 the AKI must equal the issuer's SKI; with the
   buggy code each hostname got a random AKI value, breaking the
   trust-anchor lookup in CertPathValidator / OkHttp / JSSE.

2. LEAF_VALIDITY_DAYS was 825 (Apple's pre-2020 max). Since 2020-09-01
   the CA/Browser Forum caps all leaf certs at 398 days; Chrome /
   Firefox / iOS 15+ / Android Chrome enforce it strictly and reject
   longer certs with ERR_CERT_VALIDITY_TOO_LONG.

Fix:
- Use this.caCert.generateSubjectKeyIdentifier().getBytes() for the AKI.
- Lower LEAF_VALIDITY_DAYS to 365 (safely below 398 for all modern
  browsers AND below the legacy 825 cap for iOS <=14 / older macOS,
  so Apple platforms are unaffected).

Also adds verify-aki-fix.cjs: a reproducible verification script that
generates a CA + two leaf certs via node-forge and prints AKI/SKI/
dates so a reviewer can confirm the fix end-to-end.

Verified locally on Windows / Electron 35.7.5: openssl verify
-purpose sslserver passes, AKI equals CA SKI for every leaf.
@YiShan-X YiShan-X closed this Jul 18, 2026
@YiShan-X
YiShan-X deleted the fix/leaf-cert-aki-and-validity branch July 18, 2026 07:31
@YiShan-X
YiShan-X restored the fix/leaf-cert-aki-and-validity branch July 18, 2026 07:35
@YiShan-X YiShan-X reopened this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant