@agentunion/fastaun 0.5.22 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/_packed_docs/CHANGELOG.md +33 -0
  3. package/_packed_docs/INDEX.md +32 -16
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +22 -18
  5. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
  6. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -7
  7. package/_packed_docs/sdk/INDEX.md +21 -6
  8. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
  9. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +123 -72
  10. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +173 -96
  11. package/dist/agent-md.d.ts +16 -2
  12. package/dist/agent-md.js +60 -29
  13. package/dist/agent-md.js.map +1 -1
  14. package/dist/aid-store.d.ts +2 -0
  15. package/dist/aid-store.js +76 -19
  16. package/dist/aid-store.js.map +1 -1
  17. package/dist/auth.d.ts +6 -0
  18. package/dist/auth.js +205 -69
  19. package/dist/auth.js.map +1 -1
  20. package/dist/client.d.ts +12 -10
  21. package/dist/client.js +193 -112
  22. package/dist/client.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/keystore/aid-db.d.ts +1 -8
  25. package/dist/keystore/aid-db.js +46 -204
  26. package/dist/keystore/aid-db.js.map +1 -1
  27. package/dist/keystore/index.d.ts +0 -15
  28. package/dist/keystore/local-token-store.d.ts +1 -6
  29. package/dist/keystore/local-token-store.js +0 -15
  30. package/dist/keystore/local-token-store.js.map +1 -1
  31. package/dist/org-protocol.d.ts +10 -0
  32. package/dist/org-protocol.js +100 -12
  33. package/dist/org-protocol.js.map +1 -1
  34. package/dist/ownership-v21.d.ts +2 -2
  35. package/dist/ownership-v21.js +25 -12
  36. package/dist/ownership-v21.js.map +1 -1
  37. package/dist/ownership-workflow.d.ts +30 -59
  38. package/dist/ownership-workflow.js +260 -1667
  39. package/dist/ownership-workflow.js.map +1 -1
  40. package/dist/tools/cross-sdk-agent.d.ts +1 -2
  41. package/dist/tools/cross-sdk-agent.js +7 -14
  42. package/dist/tools/cross-sdk-agent.js.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +1 -1
package/dist/auth.js CHANGED
@@ -348,7 +348,7 @@ function _strictCrlRevokedSerials(pem, issuer, now) {
348
348
  throw new AuthError('service event CRL entry is invalid');
349
349
  revoked.add(serial.content.toString('hex').replace(/^0+/, '') || '0');
350
350
  }
351
- return revoked;
351
+ return { revokedSerials: revoked, nextRefreshAt: nextUpdate * 1000 };
352
352
  }
353
353
  function _verifyOwnershipOcsp(der, certificate, issuer, now) {
354
354
  const outer = _ownershipDer(der);
@@ -405,6 +405,7 @@ function _verifyOwnershipOcsp(der, certificate, issuer, now) {
405
405
  throw new StateError('OCSP response is not current', { stringCode: 'unavailable', retryable: true });
406
406
  const responses = _ownershipDerChildren(tbs[offset++], 0x30);
407
407
  let status;
408
+ let nextRefreshAt = 0;
408
409
  for (const entry of responses) {
409
410
  const single = _ownershipDerChildren(entry, 0x30);
410
411
  if (single.length < 4)
@@ -425,6 +426,7 @@ function _verifyOwnershipOcsp(der, certificate, issuer, now) {
425
426
  if (single[3].tag !== 0xa0)
426
427
  throw new StateError('OCSP nextUpdate is missing', { stringCode: 'unavailable', retryable: true });
427
428
  const nextUpdate = _ownershipOcspTime(_ownershipDerChildren(single[3])[0]);
429
+ nextRefreshAt = nextUpdate * 1000;
428
430
  if (thisUpdate > now + 5 || thisUpdate < now - 30 || nextUpdate <= now || nextUpdate <= thisUpdate)
429
431
  throw new StateError('OCSP validity window is not current', { stringCode: 'unavailable', retryable: true });
430
432
  if (single[1].tag === 0x80 && single[1].content.length === 0)
@@ -438,7 +440,7 @@ function _verifyOwnershipOcsp(der, certificate, issuer, now) {
438
440
  }
439
441
  if (!status)
440
442
  throw new ValidationError('OCSP does not match the requested certificate', { stringCode: 'invalid' });
441
- return status;
443
+ return { status, nextRefreshAt };
442
444
  }
443
445
  // ── HTTP 辅助 ─────────────────────────────────────────────────
444
446
  /** 将 WebSocket URL 转换为 HTTP URL + 路径 */
@@ -531,16 +533,21 @@ export class AuthFlow {
531
533
  _rootCerts;
532
534
  // Gateway CA 链缓存:gateway_url -> PEM 字符串数组
533
535
  _gatewayChainCache = new Map();
536
+ _gatewayChainLoads = new Map();
534
537
  // Gateway CRL 缓存:gateway_url -> CRL 缓存条目
535
538
  _gatewayCrlCache = new Map();
536
539
  // Gateway OCSP 缓存:gateway_url -> (serial_hex -> OCSP 缓存条目)
537
540
  _gatewayOcspCache = new Map();
538
- // 证书链验证结果缓存:cert_serial -> verified_at (ms)
541
+ // 证书链验证结果缓存:材料与用途 -> expires_at (ms)
539
542
  _chainVerifiedCache = new Map();
543
+ _strictPkiCache = new Map();
544
+ _peerPkiCache = new Map();
545
+ _strictPkiEpoch = 0;
540
546
  // Gateway CA 链预验证标记:cache_key -> verified
541
547
  _gatewayCaVerified = new Map();
542
548
  // 调用方注入的内存私钥(由 AUNClient.loadIdentity 传入,不走 tokenStore 解密)
543
549
  _memIdentity = null;
550
+ _identityPkiContext = '';
544
551
  constructor(opts) {
545
552
  this._logger = opts.logger ?? _noopLogger;
546
553
  this._tokenStore = opts.tokenStore;
@@ -552,7 +559,7 @@ export class AuthFlow {
552
559
  this._rootCaPath = opts.rootCaPath ?? null;
553
560
  this._net = opts.net ?? null;
554
561
  this._verifySsl = opts.verifySsl ?? false;
555
- this._chainCacheTtl = (opts.chainCacheTtl ?? 86400) * 1000; // 转为毫秒
562
+ this._chainCacheTtl = Math.min(opts.chainCacheTtl ?? 3600, 3600) * 1000; // 转为毫秒
556
563
  this._rootCerts = this._loadRootCerts(this._rootCaPath);
557
564
  }
558
565
  // ── 公开方法 ────────────────────────────────────────────────
@@ -561,6 +568,10 @@ export class AuthFlow {
561
568
  * AuthFlow 内部不再从 tokenStore 解密私钥,只使用此处注入的明文。
562
569
  */
563
570
  setIdentity(identity) {
571
+ const context = JSON.stringify([identity?.aid ?? '', identity?.cert ?? '']);
572
+ if (this._identityPkiContext !== context)
573
+ this.invalidateStrictPkiCache();
574
+ this._identityPkiContext = context;
564
575
  this._memIdentity = identity;
565
576
  if (identity?.aid)
566
577
  this._aid = String(identity.aid);
@@ -936,7 +947,13 @@ export class AuthFlow {
936
947
  const nowMs = Date.now();
937
948
  _ensureCertTimeValid(cert, 'peer certificate', nowMs);
938
949
  if (strictRevocation) {
939
- const chain = (await this._fetchGatewayCaChain(gatewayUrl, expectedAid, true)).map(_loadX509);
950
+ const epoch = this._strictPkiEpoch;
951
+ const cacheKey = JSON.stringify([gatewayUrl, expectedAid, cert.fingerprint256, this._loadTrustedRoots().map((root) => root.fingerprint256)]);
952
+ const cached = this._strictPkiCache.get(cacheKey);
953
+ if (cached && cached.epoch === epoch && cached.status === 'good' && cached.expiresAt > Date.now())
954
+ return;
955
+ this._strictPkiCache.delete(cacheKey);
956
+ const chain = await this._loadGatewayCaChain(gatewayUrl, expectedAid, false, true);
940
957
  if (!chain.length)
941
958
  throw new AuthError('service event certificate chain is unavailable');
942
959
  const current = Math.trunc(Date.now() / 1000);
@@ -946,12 +963,26 @@ export class AuthFlow {
946
963
  const crl = await this._fetchGatewayCrl(gatewayUrl, chain[0], true);
947
964
  if (crl.revokedSerials.has(_certSerialHex(cert)))
948
965
  throw new AuthError('service event certificate has been revoked');
949
- await this._fetchGatewayOcspStatus(gatewayUrl, cert, chain[0], true);
966
+ const ocsp = await this._fetchGatewayOcspStatus(gatewayUrl, cert, chain[0], true);
950
967
  if (_certSubjectCN(cert) !== expectedAid)
951
968
  throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}`);
969
+ const expiresAt = Math.min(tStart + 3600_000, Date.parse(cert.validTo), ...chain.map((ca) => Date.parse(ca.validTo)), crl.nextRefreshAt, ocsp.nextRefreshAt);
970
+ if (epoch === this._strictPkiEpoch && expiresAt > Date.now()) {
971
+ this.cleanExpiredCaches();
972
+ if (this._strictPkiCache.size >= 2048)
973
+ this._strictPkiCache.delete(this._strictPkiCache.keys().next().value);
974
+ this._strictPkiCache.set(cacheKey, { epoch, expiresAt, status: ocsp.status });
975
+ }
952
976
  return;
953
977
  }
978
+ const epoch = this._strictPkiEpoch;
979
+ const cacheKey = JSON.stringify([gatewayUrl, expectedAid, cert.fingerprint256, this._loadTrustedRoots().map((root) => root.fingerprint256)]);
980
+ const cached = this._peerPkiCache.get(cacheKey);
981
+ if (cached && cached.epoch === epoch && cached.expiresAt > Date.now())
982
+ return;
983
+ this._peerPkiCache.delete(cacheKey);
954
984
  await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
985
+ let revocationChecked = true;
955
986
  try {
956
987
  await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
957
988
  }
@@ -959,6 +990,7 @@ export class AuthFlow {
959
990
  const errMsg = e instanceof Error ? e.message : String(e);
960
991
  if (/revoked/i.test(errMsg))
961
992
  throw e instanceof AuthError ? e : new AuthError(errMsg);
993
+ revocationChecked = false;
962
994
  this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
963
995
  }
964
996
  try {
@@ -968,6 +1000,7 @@ export class AuthFlow {
968
1000
  const errMsg = exc instanceof Error ? exc.message : String(exc);
969
1001
  if (/revoked/i.test(errMsg))
970
1002
  throw exc instanceof AuthError ? exc : new AuthError(errMsg);
1003
+ revocationChecked = false;
971
1004
  this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
972
1005
  }
973
1006
  // 检查 CN 匹配
@@ -975,6 +1008,20 @@ export class AuthFlow {
975
1008
  if (cn !== expectedAid) {
976
1009
  throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cn || 'none'}`);
977
1010
  }
1011
+ if (revocationChecked && epoch === this._strictPkiEpoch) {
1012
+ const chain = this._gatewayChainCache.get(`${gatewayUrl}:${expectedAid}`);
1013
+ const crl = this._gatewayCrlCache.get(gatewayUrl);
1014
+ const ocsp = this._gatewayOcspCache.get(gatewayUrl)?.get(_certSerialHex(cert));
1015
+ if (chain && crl && ocsp) {
1016
+ const expiresAt = Math.min(tStart + 3600_000, Date.parse(cert.validTo), chain.expiresAt, crl.nextRefreshAt, ocsp.nextRefreshAt);
1017
+ if (expiresAt > Date.now()) {
1018
+ this.cleanExpiredCaches();
1019
+ if (this._peerPkiCache.size >= 2048)
1020
+ this._peerPkiCache.delete(this._peerPkiCache.keys().next().value);
1021
+ this._peerPkiCache.set(cacheKey, { epoch, expiresAt });
1022
+ }
1023
+ }
1024
+ }
978
1025
  this._logger.debug(`verifyPeerCertificate exit: elapsed=${Date.now() - tStart}ms aid=${expectedAid}`);
979
1026
  }
980
1027
  catch (err) {
@@ -1056,30 +1103,12 @@ export class AuthFlow {
1056
1103
  validateAgentMdCertificate(response.body, aid, Math.trunc(now), { now: Math.trunc(now), requireCurrentValidity: true });
1057
1104
  if (certFingerprint(response.body) !== fingerprint)
1058
1105
  throw new ValidationError('ownership certificate DER fingerprint mismatch', { stringCode: 'invalid' });
1059
- const chainResponse = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/chain/${encodeURIComponent(aid)}`));
1060
- if (chainResponse.status !== 200 || !chainResponse.body.trim())
1061
- throw unavailable('ownership certificate chain is unavailable');
1062
- let chain;
1063
- try {
1064
- chain = _splitPemBundle(chainResponse.body).map(_loadX509);
1065
- }
1066
- catch {
1067
- throw new ValidationError('ownership certificate chain PEM is invalid', { stringCode: 'invalid' });
1068
- }
1106
+ const epoch = this._strictPkiEpoch;
1107
+ const chain = [...await this._loadGatewayCaChain(gatewayUrl, aid, true)];
1069
1108
  if (chain[0] && !chain[0].ca && _certSubjectCN(chain[0]) === aid)
1070
1109
  chain.shift();
1071
1110
  if (!chain.length)
1072
1111
  throw unavailable('ownership issuer chain is unavailable');
1073
- let current = cert;
1074
- for (let index = 0; index < chain.length; index++) {
1075
- const issuer = chain[index];
1076
- const constraints = _ownershipCertConstraints(issuer);
1077
- if (constraints.pathLen !== undefined && index > constraints.pathLen)
1078
- throw new ValidationError('ownership certificate path length is invalid', { stringCode: 'invalid' });
1079
- if (!constraints.basicPresent || !issuer.ca || !constraints.keyUsagePresent || !constraints.keyCertSign || Date.now() < Date.parse(issuer.validFrom) || Date.now() > Date.parse(issuer.validTo) || !current.checkIssued(issuer) || !current.verify(issuer.publicKey))
1080
- throw new ValidationError('ownership certificate chain is invalid', { stringCode: 'invalid' });
1081
- current = issuer;
1082
- }
1083
1112
  let roots;
1084
1113
  try {
1085
1114
  roots = this._loadTrustedRoots();
@@ -1087,8 +1116,23 @@ export class AuthFlow {
1087
1116
  catch {
1088
1117
  throw unavailable('ownership trust anchors are unavailable');
1089
1118
  }
1090
- if (!current.checkIssued(current) || !current.verify(current.publicKey) || !roots.some((root) => root.raw.equals(current.raw)))
1091
- throw new ValidationError('ownership certificate chain is not anchored by a trusted root', { stringCode: 'invalid' });
1119
+ const staticKey = JSON.stringify(['ownership', gatewayUrl, aid, cert.fingerprint256, chain.map((item) => item.fingerprint256), roots.map((root) => root.fingerprint256)]);
1120
+ if ((this._chainVerifiedCache.get(staticKey) ?? 0) <= Date.now()) {
1121
+ let current = cert;
1122
+ for (let index = 0; index < chain.length; index++) {
1123
+ const issuer = chain[index];
1124
+ const constraints = _ownershipCertConstraints(issuer);
1125
+ if (constraints.pathLen !== undefined && index > constraints.pathLen)
1126
+ throw new ValidationError('ownership certificate path length is invalid', { stringCode: 'invalid' });
1127
+ if (!constraints.basicPresent || !issuer.ca || !constraints.keyUsagePresent || !constraints.keyCertSign || Date.now() < Date.parse(issuer.validFrom) || Date.now() > Date.parse(issuer.validTo) || !current.checkIssued(issuer) || !current.verify(issuer.publicKey))
1128
+ throw new ValidationError('ownership certificate chain is invalid', { stringCode: 'invalid' });
1129
+ current = issuer;
1130
+ }
1131
+ if (!current.checkIssued(current) || !current.verify(current.publicKey) || !roots.some((root) => root.raw.equals(current.raw)))
1132
+ throw new ValidationError('ownership certificate chain is not anchored by a trusted root', { stringCode: 'invalid' });
1133
+ if (epoch === this._strictPkiEpoch)
1134
+ this._chainVerifiedCache.set(staticKey, Math.min(Date.now() + this._chainCacheTtl, Date.parse(cert.validTo), ...chain.map((item) => Date.parse(item.validTo))));
1135
+ }
1092
1136
  const ocspResponse = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${cert.serialNumber.toLowerCase()}`));
1093
1137
  if (ocspResponse.status !== 200)
1094
1138
  throw unavailable('ownership OCSP is unavailable');
@@ -1109,7 +1153,7 @@ export class AuthFlow {
1109
1153
  throw new ValidationError('ownership OCSP Base64 is invalid', { stringCode: 'invalid' });
1110
1154
  let status;
1111
1155
  try {
1112
- status = _verifyOwnershipOcsp(Buffer.from(ocsp.ocsp_response, 'base64'), cert, chain[0], Date.now() / 1000);
1156
+ status = _verifyOwnershipOcsp(Buffer.from(ocsp.ocsp_response, 'base64'), cert, chain[0], Date.now() / 1000).status;
1113
1157
  }
1114
1158
  catch (error) {
1115
1159
  if (error instanceof ValidationError || error instanceof StateError)
@@ -1185,17 +1229,16 @@ export class AuthFlow {
1185
1229
  .filter((pem) => pem.length > 0);
1186
1230
  if (normalized.length === 0)
1187
1231
  return;
1188
- for (const pem of normalized)
1189
- _loadX509(pem);
1232
+ const chain = normalized.map(_loadX509);
1190
1233
  const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1191
- this._gatewayChainCache.set(cacheKey, normalized);
1192
- this._gatewayCaVerified.delete(cacheKey);
1234
+ const previous = this._gatewayChainCache.get(cacheKey);
1235
+ if (previous && JSON.stringify(previous.chain.map((pem) => _loadX509(pem).fingerprint256)) !== JSON.stringify(chain.map((cert) => cert.fingerprint256)))
1236
+ this.invalidateStrictPkiCache();
1237
+ this._gatewayChainCache.set(cacheKey, { chain: normalized, expiresAt: Math.min(Date.now() + 3600_000, ...chain.map((cert) => Date.parse(cert.validTo))) });
1193
1238
  }
1194
1239
  /** 丢弃预置或缓存的 Gateway CA 链材料。 */
1195
1240
  discardGatewayCaChain(gatewayUrl, chainAid = '') {
1196
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1197
- this._gatewayChainCache.delete(cacheKey);
1198
- this._gatewayCaVerified.delete(cacheKey);
1241
+ this.invalidateStrictPkiCache();
1199
1242
  }
1200
1243
  // ── 内部方法:短连接 RPC ────────────────────────────────────
1201
1244
  /**
@@ -1557,22 +1600,27 @@ export class AuthFlow {
1557
1600
  * 4. BasicConstraints 检查
1558
1601
  * 5. 根证书自签 + 受信根锚定
1559
1602
  */
1560
- async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, fresh = false, suppliedChain) {
1561
- const certSerial = _certSerialHex(authCert) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1562
- // 检查缓存
1563
- const cachedAt = this._chainVerifiedCache.get(certSerial);
1564
- if (!fresh && cachedAt && Date.now() - cachedAt < this._chainCacheTtl) {
1565
- return;
1566
- }
1603
+ async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, _fresh = false, suppliedChain) {
1604
+ const epoch = this._strictPkiEpoch;
1567
1605
  const nowMs = signedAt === undefined ? Date.now() : signedAt * 1000;
1568
1606
  _ensureCertTimeValid(authCert, 'auth certificate', nowMs);
1569
1607
  const chain = suppliedChain ?? await this._loadGatewayCaChain(gatewayUrl, chainAid);
1570
1608
  if (!chain.length) {
1571
1609
  throw new AuthError('unable to verify auth certificate chain: missing CA chain');
1572
1610
  }
1573
- const cacheKey = (chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1611
+ const trustedRoots = this._loadTrustedRoots();
1612
+ const cacheKey = JSON.stringify([gatewayUrl, chain.map((cert) => cert.fingerprint256), trustedRoots.map((cert) => cert.fingerprint256), signedAt === undefined ? 'current' : 'historical']);
1613
+ const certKey = JSON.stringify([cacheKey, chainAid, authCert.fingerprint256]);
1614
+ const now = Date.now();
1615
+ const expiresAt = Math.min(now + this._chainCacheTtl, Date.parse(authCert.validTo), ...chain.map((cert) => Date.parse(cert.validTo)));
1616
+ const chainExpiresAt = Math.min(now + this._chainCacheTtl, ...chain.map((cert) => Date.parse(cert.validTo)));
1617
+ for (const caCert of chain)
1618
+ _ensureCertTimeValid(caCert, 'CA certificate', nowMs);
1619
+ // 检查缓存
1620
+ if ((this._chainVerifiedCache.get(certKey) ?? 0) > now)
1621
+ return;
1574
1622
  // 快速路径:CA 链已通过完整验证
1575
- if (!fresh && this._gatewayCaVerified.get(cacheKey)) {
1623
+ if ((this._gatewayCaVerified.get(cacheKey) ?? 0) > now) {
1576
1624
  const issuer = chain[0];
1577
1625
  _ensureCertTimeValid(issuer, 'Issuer CA', nowMs);
1578
1626
  if (!_isCaCert(issuer)) {
@@ -1584,7 +1632,8 @@ export class AuthFlow {
1584
1632
  if (!_verifyCertSignedBy(authCert, issuer)) {
1585
1633
  throw new AuthError('auth certificate signature verification failed');
1586
1634
  }
1587
- this._chainVerifiedCache.set(certSerial, Date.now());
1635
+ if (epoch === this._strictPkiEpoch)
1636
+ this._chainVerifiedCache.set(certKey, Math.min(expiresAt, this._gatewayCaVerified.get(cacheKey)));
1588
1637
  return;
1589
1638
  }
1590
1639
  // 首次验证:完整验证流程
@@ -1612,25 +1661,60 @@ export class AuthFlow {
1612
1661
  throw new AuthError('auth certificate chain root self-signature verification failed');
1613
1662
  }
1614
1663
  // 验证根证书在受信列表中
1615
- const trustedRoots = this._loadTrustedRoots();
1616
1664
  const rootDer = _certDer(root);
1617
1665
  const isTrusted = trustedRoots.some((tr) => _certDer(tr).equals(rootDer));
1618
1666
  if (!isTrusted) {
1619
1667
  throw new AuthError('auth certificate chain is not anchored by a trusted root');
1620
1668
  }
1621
1669
  // 验证成功,更新缓存
1622
- this._chainVerifiedCache.set(certSerial, Date.now());
1623
- this._gatewayCaVerified.set(cacheKey, true);
1670
+ if (epoch === this._strictPkiEpoch) {
1671
+ this._chainVerifiedCache.set(certKey, expiresAt);
1672
+ this._gatewayCaVerified.set(cacheKey, chainExpiresAt);
1673
+ }
1624
1674
  }
1625
1675
  /** 加载 Gateway CA 链(带缓存) */
1626
- async _loadGatewayCaChain(gatewayUrl, chainAid = '') {
1627
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1628
- let cached = this._gatewayChainCache.get(cacheKey);
1629
- if (!cached) {
1630
- cached = await this._fetchGatewayCaChain(gatewayUrl, chainAid);
1631
- this._gatewayChainCache.set(cacheKey, cached);
1676
+ async _loadGatewayCaChain(gatewayUrl, chainAid = '', ownership = false, fresh = false) {
1677
+ const cacheKey = (ownership ? 'ownership:' : '') + (chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl);
1678
+ const cached = this._gatewayChainCache.get(cacheKey);
1679
+ if (cached && cached.expiresAt > Date.now())
1680
+ return cached.chain.map(_loadX509);
1681
+ const pending = this._gatewayChainLoads.get(cacheKey);
1682
+ if (pending)
1683
+ return pending;
1684
+ const epoch = this._strictPkiEpoch;
1685
+ const load = (async () => {
1686
+ let pems;
1687
+ if (ownership) {
1688
+ const response = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/chain/${encodeURIComponent(chainAid)}`));
1689
+ if (response.status !== 200 || !response.body.trim())
1690
+ throw new StateError('ownership certificate chain is unavailable', { stringCode: 'unavailable', retryable: true });
1691
+ pems = _splitPemBundle(response.body);
1692
+ if (!pems.length)
1693
+ throw new ValidationError('ownership certificate chain PEM is invalid', { stringCode: 'invalid' });
1694
+ }
1695
+ else
1696
+ pems = await this._fetchGatewayCaChain(gatewayUrl, chainAid, fresh);
1697
+ let chain;
1698
+ try {
1699
+ chain = pems.map(_loadX509);
1700
+ }
1701
+ catch (error) {
1702
+ if (ownership)
1703
+ throw new ValidationError('ownership certificate chain PEM is invalid', { stringCode: 'invalid' });
1704
+ throw error;
1705
+ }
1706
+ if (epoch === this._strictPkiEpoch && chain.length)
1707
+ this._gatewayChainCache.set(cacheKey, { chain: pems, expiresAt: Math.min(Date.now() + 3600_000, ...chain.map((cert) => Date.parse(cert.validTo))) });
1708
+ return chain;
1709
+ })();
1710
+ this._gatewayChainLoads.set(cacheKey, load);
1711
+ try {
1712
+ return await load;
1713
+ }
1714
+ finally {
1715
+ if (this._gatewayChainLoads.get(cacheKey) === load)
1716
+ this._gatewayChainLoads.delete(cacheKey);
1632
1717
  }
1633
- return cached.map((pem) => _loadX509(pem));
1634
1718
  }
1635
1719
  /** 从 Gateway PKI 端点获取 CA 链 */
1636
1720
  async _fetchFreshPkiJson(url) {
@@ -1683,8 +1767,10 @@ export class AuthFlow {
1683
1767
  if (cached && cached.nextRefreshAt > now) {
1684
1768
  return cached.revokedSerials;
1685
1769
  }
1770
+ const epoch = this._strictPkiEpoch;
1686
1771
  const entry = await this._fetchGatewayCrl(gatewayUrl, issuerCert);
1687
- this._gatewayCrlCache.set(gatewayUrl, entry);
1772
+ if (epoch === this._strictPkiEpoch)
1773
+ this._gatewayCrlCache.set(gatewayUrl, entry);
1688
1774
  return entry.revokedSerials;
1689
1775
  }
1690
1776
  /**
@@ -1706,14 +1792,15 @@ export class AuthFlow {
1706
1792
  throw new AuthError('gateway CRL endpoint returned no signed CRL');
1707
1793
  }
1708
1794
  if (fresh)
1709
- return { revokedSerials: _strictCrlRevokedSerials(crlPem, _issuerCert, Date.now() / 1000), nextRefreshAt: Date.now() };
1795
+ return _strictCrlRevokedSerials(crlPem, _issuerCert, Date.now() / 1000);
1710
1796
  // 从 JSON 响应中提取吊销序列号列表
1711
1797
  // Gateway 的 crl.json 同时包含 crl_pem(签名 CRL)和 revoked_serials(方便客户端解析)
1712
1798
  let revokedSerials = new Set();
1799
+ const bounds = { nextRefreshAt: Date.now() + 300_000 };
1713
1800
  // 尝试解析 CRL PEM 以提取吊销序列号
1714
1801
  // Node.js 不直接支持 CRL 解析,尝试从 DER 中手动提取
1715
1802
  try {
1716
- revokedSerials = this._parseCrlRevokedSerials(crlPem, _issuerCert);
1803
+ revokedSerials = this._parseCrlRevokedSerials(crlPem, _issuerCert, bounds);
1717
1804
  }
1718
1805
  catch {
1719
1806
  // CRL 解析失败时,降级使用 JSON 响应中的 revoked_serials(如果提供)
@@ -1728,7 +1815,7 @@ export class AuthFlow {
1728
1815
  const now = Date.now();
1729
1816
  let nextRefreshAt = now + 300_000; // 5 分钟
1730
1817
  const maxRefreshAt = now + 86400_000; // 24 小时
1731
- nextRefreshAt = Math.min(nextRefreshAt, maxRefreshAt);
1818
+ nextRefreshAt = Math.min(nextRefreshAt, maxRefreshAt, bounds.nextRefreshAt);
1732
1819
  return { revokedSerials, nextRefreshAt };
1733
1820
  }
1734
1821
  /**
@@ -1752,7 +1839,7 @@ export class AuthFlow {
1752
1839
  * ...
1753
1840
  * }
1754
1841
  */
1755
- _parseCrlRevokedSerials(crlPem, issuerCert) {
1842
+ _parseCrlRevokedSerials(crlPem, issuerCert, bounds) {
1756
1843
  // 提取 DER 数据
1757
1844
  const b64 = crlPem
1758
1845
  .replace(/-----BEGIN X509 CRL-----/g, '')
@@ -1790,6 +1877,17 @@ export class AuthFlow {
1790
1877
  throw new AuthError('gateway CRL signature verification failed');
1791
1878
  }
1792
1879
  }
1880
+ if (bounds) {
1881
+ const fields = _ownershipDerChildren(_ownershipDer(der.subarray(tbsStart, tbsStart + tbs.totalLength)), 0x30);
1882
+ const version = fields[0]?.tag === 2 ? 1 : 0;
1883
+ let nextUpdate = fields[version + 3];
1884
+ if (nextUpdate?.tag === 23) {
1885
+ const text = nextUpdate.content.toString('ascii');
1886
+ nextUpdate = { ...nextUpdate, tag: 24, content: Buffer.from(`${Number(text.slice(0, 2)) >= 50 ? '19' : '20'}${text}`) };
1887
+ }
1888
+ if (nextUpdate && nextUpdate.tag === 24)
1889
+ bounds.nextRefreshAt = _ownershipOcspTime(nextUpdate) * 1000;
1890
+ }
1793
1891
  // 解析 TBSCertList 内部字段以找到 revokedCertificates
1794
1892
  const revokedSerials = new Set();
1795
1893
  let offset = tbs.contentOffset;
@@ -1886,20 +1984,21 @@ export class AuthFlow {
1886
1984
  if (typeof payload.ocsp_response !== 'string' || !isStrictBase64(ocspB64))
1887
1985
  throw new AuthError('service event signed current OCSP is unavailable or invalid');
1888
1986
  const verified = _verifyOwnershipOcsp(Buffer.from(ocspB64, 'base64'), authCert, issuerCert, Date.now() / 1000);
1889
- if (payload.status !== undefined && payload.status !== verified)
1987
+ if (payload.status !== undefined && payload.status !== verified.status)
1890
1988
  throw new AuthError('service event OCSP status mismatch');
1891
- if (verified !== 'good')
1892
- throw new AuthError(`service event certificate OCSP status is ${verified}`);
1893
- return { status: verified, nextRefreshAt: Date.now() };
1989
+ if (verified.status !== 'good')
1990
+ throw new AuthError(`service event certificate OCSP status is ${verified.status}`);
1991
+ return verified;
1894
1992
  }
1895
1993
  if (!ocspB64) {
1896
1994
  throw new AuthError('gateway OCSP endpoint returned no ocsp_response');
1897
1995
  }
1898
1996
  let effectiveStatus;
1997
+ const bounds = { nextRefreshAt: Date.now() + 300_000 };
1899
1998
  try {
1900
1999
  // 尝试解析 DER OCSP 响应
1901
2000
  const ocspDer = Buffer.from(ocspB64, 'base64');
1902
- effectiveStatus = this._parseOcspResponse(ocspDer, authCert, issuerCert);
2001
+ effectiveStatus = this._parseOcspResponse(ocspDer, authCert, issuerCert, bounds);
1903
2002
  }
1904
2003
  catch (e) {
1905
2004
  // OCSP DER 解析失败时,降级信赖 JSON status 字段
@@ -1917,7 +2016,7 @@ export class AuthFlow {
1917
2016
  }
1918
2017
  // 缓存 TTL:默认 5 分钟,最大 24 小时
1919
2018
  const now = Date.now();
1920
- const nextRefreshAt = Math.min(now + 300_000, now + 86400_000);
2019
+ const nextRefreshAt = Math.min(now + 300_000, now + 86400_000, bounds.nextRefreshAt);
1921
2020
  return { status: effectiveStatus, nextRefreshAt };
1922
2021
  }
1923
2022
  /**
@@ -1956,7 +2055,7 @@ export class AuthFlow {
1956
2055
  * 这里只做关键字段提取:responseStatus、certStatus。
1957
2056
  * 完整签名验证依赖更全面的 ASN.1 库。
1958
2057
  */
1959
- _parseOcspResponse(ocspDer, _authCert, _issuerCert) {
2058
+ _parseOcspResponse(ocspDer, _authCert, _issuerCert, bounds) {
1960
2059
  // 外层 SEQUENCE
1961
2060
  const outer = this._readAsn1Sequence(ocspDer, 0);
1962
2061
  if (!outer)
@@ -2055,6 +2154,15 @@ export class AuthFlow {
2055
2154
  const certStatusByte = basicDer[srOffset];
2056
2155
  // context-specific tag class (bits 7-6 = 10), constructed bit (bit 5)
2057
2156
  const tagNumber = certStatusByte & 0x1f;
2157
+ if (bounds) {
2158
+ const status = this._readAsn1Tag(basicDer, srOffset);
2159
+ const thisUpdate = status && this._readAsn1Tag(basicDer, srOffset + status.totalLength);
2160
+ const nextOffset = status && thisUpdate ? srOffset + status.totalLength + thisUpdate.totalLength : singleSeq.contentOffset + singleSeq.contentLength;
2161
+ if (nextOffset < singleSeq.contentOffset + singleSeq.contentLength && basicDer[nextOffset] === 0xa0) {
2162
+ const nextUpdate = _ownershipDerChildren(_ownershipDer(basicDer.subarray(nextOffset)))[0];
2163
+ bounds.nextRefreshAt = _ownershipOcspTime(nextUpdate) * 1000;
2164
+ }
2165
+ }
2058
2166
  if (tagNumber === 0)
2059
2167
  return 'good';
2060
2168
  if (tagNumber === 1)
@@ -2493,6 +2601,7 @@ export class AuthFlow {
2493
2601
  // ── 内部方法:根证书管理 ───────────────────────────────────
2494
2602
  /** 重新从磁盘加载信任根证书,供 meta.import_trust_roots 后刷新使用。 */
2495
2603
  reloadTrustedRoots() {
2604
+ this.invalidateStrictPkiCache();
2496
2605
  this._rootCerts = this._loadRootCerts(this._rootCaPath);
2497
2606
  this._gatewayCaVerified.clear();
2498
2607
  this._chainVerifiedCache.clear();
@@ -2574,9 +2683,28 @@ export class AuthFlow {
2574
2683
  }
2575
2684
  return certs;
2576
2685
  }
2686
+ invalidateStrictPkiCache() {
2687
+ this._strictPkiEpoch++;
2688
+ this._strictPkiCache.clear();
2689
+ this._peerPkiCache.clear();
2690
+ this._chainVerifiedCache.clear();
2691
+ this._gatewayCaVerified.clear();
2692
+ this._gatewayChainCache.clear();
2693
+ this._gatewayChainLoads.clear();
2694
+ this._gatewayCrlCache.clear();
2695
+ this._gatewayOcspCache.clear();
2696
+ }
2577
2697
  /** 清理过期的 gateway 缓存条目(供外部定时调用) */
2578
2698
  cleanExpiredCaches() {
2579
2699
  const now = Date.now();
2700
+ for (const [k, v] of this._strictPkiCache) {
2701
+ if (v.epoch !== this._strictPkiEpoch || v.expiresAt <= now)
2702
+ this._strictPkiCache.delete(k);
2703
+ }
2704
+ for (const [k, v] of this._peerPkiCache) {
2705
+ if (v.epoch !== this._strictPkiEpoch || v.expiresAt <= now)
2706
+ this._peerPkiCache.delete(k);
2707
+ }
2580
2708
  for (const [k, v] of this._gatewayCrlCache) {
2581
2709
  if (v.nextRefreshAt <= now)
2582
2710
  this._gatewayCrlCache.delete(k);
@@ -2590,9 +2718,17 @@ export class AuthFlow {
2590
2718
  this._gatewayOcspCache.delete(k);
2591
2719
  }
2592
2720
  for (const [k, v] of this._chainVerifiedCache) {
2593
- if (now - v >= this._chainCacheTtl)
2721
+ if (v <= now)
2594
2722
  this._chainVerifiedCache.delete(k);
2595
2723
  }
2724
+ for (const [k, v] of this._gatewayCaVerified) {
2725
+ if (v <= now)
2726
+ this._gatewayCaVerified.delete(k);
2727
+ }
2728
+ for (const [k, v] of this._gatewayChainCache) {
2729
+ if (v.expiresAt <= now)
2730
+ this._gatewayChainCache.delete(k);
2731
+ }
2596
2732
  }
2597
2733
  }
2598
2734
  //# sourceMappingURL=auth.js.map