@agentunion/fastaun 0.5.21 → 0.5.23

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 (75) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/_packed_docs/CHANGELOG.md +58 -1
  3. package/_packed_docs/INDEX.md +469 -453
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +169 -164
  5. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
  7. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +5 -5
  8. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  9. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +669 -666
  10. package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
  11. package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
  12. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +16 -0
  13. package/_packed_docs/sdk/09-group-rpc-manual.md +10 -0
  14. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  15. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +3 -1
  16. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +2 -0
  17. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +138 -134
  18. package/_packed_docs/sdk/INDEX.md +255 -239
  19. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +939 -930
  20. 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 +312 -363
  21. 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 +322 -553
  22. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +4 -4
  23. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +484 -480
  24. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +190 -97
  25. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +158 -127
  26. package/dist/agent-md.d.ts +16 -2
  27. package/dist/agent-md.js +59 -23
  28. package/dist/agent-md.js.map +1 -1
  29. package/dist/aid-store.d.ts +1 -0
  30. package/dist/aid-store.js +43 -8
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/auth.d.ts +7 -0
  33. package/dist/auth.js +222 -70
  34. package/dist/auth.js.map +1 -1
  35. package/dist/client/delivery.d.ts +28 -0
  36. package/dist/client/delivery.js +399 -44
  37. package/dist/client/delivery.js.map +1 -1
  38. package/dist/client/group-state.js +3 -0
  39. package/dist/client/group-state.js.map +1 -1
  40. package/dist/client/rpc-pipeline.js +28 -2
  41. package/dist/client/rpc-pipeline.js.map +1 -1
  42. package/dist/client/v2-e2ee.js +109 -39
  43. package/dist/client/v2-e2ee.js.map +1 -1
  44. package/dist/client.d.ts +3 -0
  45. package/dist/client.js +114 -26
  46. package/dist/client.js.map +1 -1
  47. package/dist/group-fs.d.ts +1 -0
  48. package/dist/group-fs.js +37 -19
  49. package/dist/group-fs.js.map +1 -1
  50. package/dist/org-protocol.d.ts +11 -0
  51. package/dist/org-protocol.js +154 -19
  52. package/dist/org-protocol.js.map +1 -1
  53. package/dist/ownership-v21.d.ts +21 -3
  54. package/dist/ownership-v21.js +135 -37
  55. package/dist/ownership-v21.js.map +1 -1
  56. package/dist/ownership-workflow.d.ts +7 -2
  57. package/dist/ownership-workflow.js +173 -17
  58. package/dist/ownership-workflow.js.map +1 -1
  59. package/dist/storage/lowlevel.d.ts +6 -0
  60. package/dist/storage/lowlevel.js +24 -7
  61. package/dist/storage/lowlevel.js.map +1 -1
  62. package/dist/storage/read-cache.d.ts +70 -0
  63. package/dist/storage/read-cache.js +625 -0
  64. package/dist/storage/read-cache.js.map +1 -0
  65. package/dist/storage/vfs.d.ts +2 -0
  66. package/dist/storage/vfs.js +50 -15
  67. package/dist/storage/vfs.js.map +1 -1
  68. package/dist/tools/cross-sdk-agent.d.ts +21 -1
  69. package/dist/tools/cross-sdk-agent.js +301 -19
  70. package/dist/tools/cross-sdk-agent.js.map +1 -1
  71. package/dist/transport.js +6 -2
  72. package/dist/transport.js.map +1 -1
  73. package/dist/version.d.ts +1 -1
  74. package/dist/version.js +1 -1
  75. 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) {
@@ -983,6 +1030,7 @@ export class AuthFlow {
983
1030
  }
984
1031
  }
985
1032
  async resolveCurrentEntityCertificate(gatewayUrl, aid, type) {
1033
+ const began = Date.now();
986
1034
  try {
987
1035
  const query = new URLSearchParams({ consistency: 'authoritative', ...(type === 'group' ? { key_purpose: 'group_identity' } : {}) });
988
1036
  const response = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}?${query}`));
@@ -996,7 +1044,7 @@ export class AuthFlow {
996
1044
  catch {
997
1045
  throw new ValidationError('entity current certificate is invalid', { stringCode: 'invalid' });
998
1046
  }
999
- return this.resolveOwnershipCertificate(gatewayUrl, aid, fingerprint, type === 'group' ? 'group_identity' : 'signing');
1047
+ return this._verifyOwnershipCertificate(gatewayUrl, aid, fingerprint, response, began);
1000
1048
  }
1001
1049
  catch (error) {
1002
1050
  if (error instanceof AUNError && error.stringCode)
@@ -1014,6 +1062,21 @@ export class AuthFlow {
1014
1062
  if (purpose === 'group_identity')
1015
1063
  query.set('key_purpose', purpose);
1016
1064
  const response = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}?${query}`));
1065
+ return await this._verifyOwnershipCertificate(gatewayUrl, aid, fingerprint, response, began);
1066
+ }
1067
+ catch (error) {
1068
+ if (error instanceof ValidationError)
1069
+ throw new ValidationError(error.message, { stringCode: 'invalid' });
1070
+ if (error instanceof StateError && error.stringCode)
1071
+ throw error;
1072
+ throw unavailable(error instanceof Error ? error.message : String(error));
1073
+ }
1074
+ }
1075
+ async _verifyOwnershipCertificate(gatewayUrl, aid, fingerprint, response, began) {
1076
+ const unavailable = (reason) => new StateError(reason, { stringCode: 'unavailable', retryable: true });
1077
+ try {
1078
+ if (!/^[a-z0-9_][a-z0-9_-]*(?:\.[a-z0-9_][a-z0-9_-]*)+$/.test(aid) || !/^sha256:[0-9a-f]{64}$/.test(fingerprint))
1079
+ throw new ValidationError('ownership certificate query requires exact AID and full fingerprint', { stringCode: 'invalid' });
1017
1080
  if (response.status !== 200)
1018
1081
  throw unavailable(`ownership certificate HTTP ${response.status}`);
1019
1082
  const headers = response.headers;
@@ -1040,30 +1103,12 @@ export class AuthFlow {
1040
1103
  validateAgentMdCertificate(response.body, aid, Math.trunc(now), { now: Math.trunc(now), requireCurrentValidity: true });
1041
1104
  if (certFingerprint(response.body) !== fingerprint)
1042
1105
  throw new ValidationError('ownership certificate DER fingerprint mismatch', { stringCode: 'invalid' });
1043
- const chainResponse = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/chain/${encodeURIComponent(aid)}`));
1044
- if (chainResponse.status !== 200 || !chainResponse.body.trim())
1045
- throw unavailable('ownership certificate chain is unavailable');
1046
- let chain;
1047
- try {
1048
- chain = _splitPemBundle(chainResponse.body).map(_loadX509);
1049
- }
1050
- catch {
1051
- throw new ValidationError('ownership certificate chain PEM is invalid', { stringCode: 'invalid' });
1052
- }
1106
+ const epoch = this._strictPkiEpoch;
1107
+ const chain = [...await this._loadGatewayCaChain(gatewayUrl, aid, true)];
1053
1108
  if (chain[0] && !chain[0].ca && _certSubjectCN(chain[0]) === aid)
1054
1109
  chain.shift();
1055
1110
  if (!chain.length)
1056
1111
  throw unavailable('ownership issuer chain is unavailable');
1057
- let current = cert;
1058
- for (let index = 0; index < chain.length; index++) {
1059
- const issuer = chain[index];
1060
- const constraints = _ownershipCertConstraints(issuer);
1061
- if (constraints.pathLen !== undefined && index > constraints.pathLen)
1062
- throw new ValidationError('ownership certificate path length is invalid', { stringCode: 'invalid' });
1063
- 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))
1064
- throw new ValidationError('ownership certificate chain is invalid', { stringCode: 'invalid' });
1065
- current = issuer;
1066
- }
1067
1112
  let roots;
1068
1113
  try {
1069
1114
  roots = this._loadTrustedRoots();
@@ -1071,8 +1116,23 @@ export class AuthFlow {
1071
1116
  catch {
1072
1117
  throw unavailable('ownership trust anchors are unavailable');
1073
1118
  }
1074
- if (!current.checkIssued(current) || !current.verify(current.publicKey) || !roots.some((root) => root.raw.equals(current.raw)))
1075
- 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
+ }
1076
1136
  const ocspResponse = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${cert.serialNumber.toLowerCase()}`));
1077
1137
  if (ocspResponse.status !== 200)
1078
1138
  throw unavailable('ownership OCSP is unavailable');
@@ -1093,7 +1153,7 @@ export class AuthFlow {
1093
1153
  throw new ValidationError('ownership OCSP Base64 is invalid', { stringCode: 'invalid' });
1094
1154
  let status;
1095
1155
  try {
1096
- 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;
1097
1157
  }
1098
1158
  catch (error) {
1099
1159
  if (error instanceof ValidationError || error instanceof StateError)
@@ -1169,17 +1229,16 @@ export class AuthFlow {
1169
1229
  .filter((pem) => pem.length > 0);
1170
1230
  if (normalized.length === 0)
1171
1231
  return;
1172
- for (const pem of normalized)
1173
- _loadX509(pem);
1232
+ const chain = normalized.map(_loadX509);
1174
1233
  const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1175
- this._gatewayChainCache.set(cacheKey, normalized);
1176
- 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))) });
1177
1238
  }
1178
1239
  /** 丢弃预置或缓存的 Gateway CA 链材料。 */
1179
1240
  discardGatewayCaChain(gatewayUrl, chainAid = '') {
1180
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1181
- this._gatewayChainCache.delete(cacheKey);
1182
- this._gatewayCaVerified.delete(cacheKey);
1241
+ this.invalidateStrictPkiCache();
1183
1242
  }
1184
1243
  // ── 内部方法:短连接 RPC ────────────────────────────────────
1185
1244
  /**
@@ -1541,22 +1600,27 @@ export class AuthFlow {
1541
1600
  * 4. BasicConstraints 检查
1542
1601
  * 5. 根证书自签 + 受信根锚定
1543
1602
  */
1544
- async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, fresh = false, suppliedChain) {
1545
- const certSerial = _certSerialHex(authCert) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1546
- // 检查缓存
1547
- const cachedAt = this._chainVerifiedCache.get(certSerial);
1548
- if (!fresh && cachedAt && Date.now() - cachedAt < this._chainCacheTtl) {
1549
- return;
1550
- }
1603
+ async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, _fresh = false, suppliedChain) {
1604
+ const epoch = this._strictPkiEpoch;
1551
1605
  const nowMs = signedAt === undefined ? Date.now() : signedAt * 1000;
1552
1606
  _ensureCertTimeValid(authCert, 'auth certificate', nowMs);
1553
1607
  const chain = suppliedChain ?? await this._loadGatewayCaChain(gatewayUrl, chainAid);
1554
1608
  if (!chain.length) {
1555
1609
  throw new AuthError('unable to verify auth certificate chain: missing CA chain');
1556
1610
  }
1557
- 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;
1558
1622
  // 快速路径:CA 链已通过完整验证
1559
- if (!fresh && this._gatewayCaVerified.get(cacheKey)) {
1623
+ if ((this._gatewayCaVerified.get(cacheKey) ?? 0) > now) {
1560
1624
  const issuer = chain[0];
1561
1625
  _ensureCertTimeValid(issuer, 'Issuer CA', nowMs);
1562
1626
  if (!_isCaCert(issuer)) {
@@ -1568,7 +1632,8 @@ export class AuthFlow {
1568
1632
  if (!_verifyCertSignedBy(authCert, issuer)) {
1569
1633
  throw new AuthError('auth certificate signature verification failed');
1570
1634
  }
1571
- this._chainVerifiedCache.set(certSerial, Date.now());
1635
+ if (epoch === this._strictPkiEpoch)
1636
+ this._chainVerifiedCache.set(certKey, Math.min(expiresAt, this._gatewayCaVerified.get(cacheKey)));
1572
1637
  return;
1573
1638
  }
1574
1639
  // 首次验证:完整验证流程
@@ -1596,25 +1661,60 @@ export class AuthFlow {
1596
1661
  throw new AuthError('auth certificate chain root self-signature verification failed');
1597
1662
  }
1598
1663
  // 验证根证书在受信列表中
1599
- const trustedRoots = this._loadTrustedRoots();
1600
1664
  const rootDer = _certDer(root);
1601
1665
  const isTrusted = trustedRoots.some((tr) => _certDer(tr).equals(rootDer));
1602
1666
  if (!isTrusted) {
1603
1667
  throw new AuthError('auth certificate chain is not anchored by a trusted root');
1604
1668
  }
1605
1669
  // 验证成功,更新缓存
1606
- this._chainVerifiedCache.set(certSerial, Date.now());
1607
- this._gatewayCaVerified.set(cacheKey, true);
1670
+ if (epoch === this._strictPkiEpoch) {
1671
+ this._chainVerifiedCache.set(certKey, expiresAt);
1672
+ this._gatewayCaVerified.set(cacheKey, chainExpiresAt);
1673
+ }
1608
1674
  }
1609
1675
  /** 加载 Gateway CA 链(带缓存) */
1610
- async _loadGatewayCaChain(gatewayUrl, chainAid = '') {
1611
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1612
- let cached = this._gatewayChainCache.get(cacheKey);
1613
- if (!cached) {
1614
- cached = await this._fetchGatewayCaChain(gatewayUrl, chainAid);
1615
- 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);
1616
1717
  }
1617
- return cached.map((pem) => _loadX509(pem));
1618
1718
  }
1619
1719
  /** 从 Gateway PKI 端点获取 CA 链 */
1620
1720
  async _fetchFreshPkiJson(url) {
@@ -1667,8 +1767,10 @@ export class AuthFlow {
1667
1767
  if (cached && cached.nextRefreshAt > now) {
1668
1768
  return cached.revokedSerials;
1669
1769
  }
1770
+ const epoch = this._strictPkiEpoch;
1670
1771
  const entry = await this._fetchGatewayCrl(gatewayUrl, issuerCert);
1671
- this._gatewayCrlCache.set(gatewayUrl, entry);
1772
+ if (epoch === this._strictPkiEpoch)
1773
+ this._gatewayCrlCache.set(gatewayUrl, entry);
1672
1774
  return entry.revokedSerials;
1673
1775
  }
1674
1776
  /**
@@ -1690,14 +1792,15 @@ export class AuthFlow {
1690
1792
  throw new AuthError('gateway CRL endpoint returned no signed CRL');
1691
1793
  }
1692
1794
  if (fresh)
1693
- return { revokedSerials: _strictCrlRevokedSerials(crlPem, _issuerCert, Date.now() / 1000), nextRefreshAt: Date.now() };
1795
+ return _strictCrlRevokedSerials(crlPem, _issuerCert, Date.now() / 1000);
1694
1796
  // 从 JSON 响应中提取吊销序列号列表
1695
1797
  // Gateway 的 crl.json 同时包含 crl_pem(签名 CRL)和 revoked_serials(方便客户端解析)
1696
1798
  let revokedSerials = new Set();
1799
+ const bounds = { nextRefreshAt: Date.now() + 300_000 };
1697
1800
  // 尝试解析 CRL PEM 以提取吊销序列号
1698
1801
  // Node.js 不直接支持 CRL 解析,尝试从 DER 中手动提取
1699
1802
  try {
1700
- revokedSerials = this._parseCrlRevokedSerials(crlPem, _issuerCert);
1803
+ revokedSerials = this._parseCrlRevokedSerials(crlPem, _issuerCert, bounds);
1701
1804
  }
1702
1805
  catch {
1703
1806
  // CRL 解析失败时,降级使用 JSON 响应中的 revoked_serials(如果提供)
@@ -1712,7 +1815,7 @@ export class AuthFlow {
1712
1815
  const now = Date.now();
1713
1816
  let nextRefreshAt = now + 300_000; // 5 分钟
1714
1817
  const maxRefreshAt = now + 86400_000; // 24 小时
1715
- nextRefreshAt = Math.min(nextRefreshAt, maxRefreshAt);
1818
+ nextRefreshAt = Math.min(nextRefreshAt, maxRefreshAt, bounds.nextRefreshAt);
1716
1819
  return { revokedSerials, nextRefreshAt };
1717
1820
  }
1718
1821
  /**
@@ -1736,7 +1839,7 @@ export class AuthFlow {
1736
1839
  * ...
1737
1840
  * }
1738
1841
  */
1739
- _parseCrlRevokedSerials(crlPem, issuerCert) {
1842
+ _parseCrlRevokedSerials(crlPem, issuerCert, bounds) {
1740
1843
  // 提取 DER 数据
1741
1844
  const b64 = crlPem
1742
1845
  .replace(/-----BEGIN X509 CRL-----/g, '')
@@ -1774,6 +1877,17 @@ export class AuthFlow {
1774
1877
  throw new AuthError('gateway CRL signature verification failed');
1775
1878
  }
1776
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
+ }
1777
1891
  // 解析 TBSCertList 内部字段以找到 revokedCertificates
1778
1892
  const revokedSerials = new Set();
1779
1893
  let offset = tbs.contentOffset;
@@ -1870,20 +1984,21 @@ export class AuthFlow {
1870
1984
  if (typeof payload.ocsp_response !== 'string' || !isStrictBase64(ocspB64))
1871
1985
  throw new AuthError('service event signed current OCSP is unavailable or invalid');
1872
1986
  const verified = _verifyOwnershipOcsp(Buffer.from(ocspB64, 'base64'), authCert, issuerCert, Date.now() / 1000);
1873
- if (payload.status !== undefined && payload.status !== verified)
1987
+ if (payload.status !== undefined && payload.status !== verified.status)
1874
1988
  throw new AuthError('service event OCSP status mismatch');
1875
- if (verified !== 'good')
1876
- throw new AuthError(`service event certificate OCSP status is ${verified}`);
1877
- 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;
1878
1992
  }
1879
1993
  if (!ocspB64) {
1880
1994
  throw new AuthError('gateway OCSP endpoint returned no ocsp_response');
1881
1995
  }
1882
1996
  let effectiveStatus;
1997
+ const bounds = { nextRefreshAt: Date.now() + 300_000 };
1883
1998
  try {
1884
1999
  // 尝试解析 DER OCSP 响应
1885
2000
  const ocspDer = Buffer.from(ocspB64, 'base64');
1886
- effectiveStatus = this._parseOcspResponse(ocspDer, authCert, issuerCert);
2001
+ effectiveStatus = this._parseOcspResponse(ocspDer, authCert, issuerCert, bounds);
1887
2002
  }
1888
2003
  catch (e) {
1889
2004
  // OCSP DER 解析失败时,降级信赖 JSON status 字段
@@ -1901,7 +2016,7 @@ export class AuthFlow {
1901
2016
  }
1902
2017
  // 缓存 TTL:默认 5 分钟,最大 24 小时
1903
2018
  const now = Date.now();
1904
- const nextRefreshAt = Math.min(now + 300_000, now + 86400_000);
2019
+ const nextRefreshAt = Math.min(now + 300_000, now + 86400_000, bounds.nextRefreshAt);
1905
2020
  return { status: effectiveStatus, nextRefreshAt };
1906
2021
  }
1907
2022
  /**
@@ -1940,7 +2055,7 @@ export class AuthFlow {
1940
2055
  * 这里只做关键字段提取:responseStatus、certStatus。
1941
2056
  * 完整签名验证依赖更全面的 ASN.1 库。
1942
2057
  */
1943
- _parseOcspResponse(ocspDer, _authCert, _issuerCert) {
2058
+ _parseOcspResponse(ocspDer, _authCert, _issuerCert, bounds) {
1944
2059
  // 外层 SEQUENCE
1945
2060
  const outer = this._readAsn1Sequence(ocspDer, 0);
1946
2061
  if (!outer)
@@ -2039,6 +2154,15 @@ export class AuthFlow {
2039
2154
  const certStatusByte = basicDer[srOffset];
2040
2155
  // context-specific tag class (bits 7-6 = 10), constructed bit (bit 5)
2041
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
+ }
2042
2166
  if (tagNumber === 0)
2043
2167
  return 'good';
2044
2168
  if (tagNumber === 1)
@@ -2477,6 +2601,7 @@ export class AuthFlow {
2477
2601
  // ── 内部方法:根证书管理 ───────────────────────────────────
2478
2602
  /** 重新从磁盘加载信任根证书,供 meta.import_trust_roots 后刷新使用。 */
2479
2603
  reloadTrustedRoots() {
2604
+ this.invalidateStrictPkiCache();
2480
2605
  this._rootCerts = this._loadRootCerts(this._rootCaPath);
2481
2606
  this._gatewayCaVerified.clear();
2482
2607
  this._chainVerifiedCache.clear();
@@ -2558,9 +2683,28 @@ export class AuthFlow {
2558
2683
  }
2559
2684
  return certs;
2560
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
+ }
2561
2697
  /** 清理过期的 gateway 缓存条目(供外部定时调用) */
2562
2698
  cleanExpiredCaches() {
2563
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
+ }
2564
2708
  for (const [k, v] of this._gatewayCrlCache) {
2565
2709
  if (v.nextRefreshAt <= now)
2566
2710
  this._gatewayCrlCache.delete(k);
@@ -2574,9 +2718,17 @@ export class AuthFlow {
2574
2718
  this._gatewayOcspCache.delete(k);
2575
2719
  }
2576
2720
  for (const [k, v] of this._chainVerifiedCache) {
2577
- if (now - v >= 300_000)
2721
+ if (v <= now)
2578
2722
  this._chainVerifiedCache.delete(k);
2579
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
+ }
2580
2732
  }
2581
2733
  }
2582
2734
  //# sourceMappingURL=auth.js.map