@agentunion/fastaun 0.5.22 → 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.
package/dist/client.js CHANGED
@@ -48,7 +48,7 @@ import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
48
48
  import { AID } from './aid.js';
49
49
  import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
50
  import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
- import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure } from './ownership-v21.js';
51
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure } from './ownership-v21.js';
52
52
  import { OwnershipWorkflow } from './ownership-workflow.js';
53
53
  import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
54
54
  function isPromiseLike(value) {
@@ -489,12 +489,15 @@ export class AUNClient {
489
489
  return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
490
490
  }
491
491
  _getOwnershipFiles() {
492
- return this._ownershipFiles ??= new OwnershipFiles({
492
+ return this._ownershipFiles ??= this._createOwnershipFiles((subAid) => this._agentMdManager.readOwnershipFresh(subAid));
493
+ }
494
+ _createOwnershipFiles(readAgentMdFresh) {
495
+ return new OwnershipFiles({
493
496
  currentAid: () => { if (!this._currentAid)
494
497
  throw ownershipFailure('forbidden', 'ownership requires a loaded identity'); return this._currentAid; },
495
498
  currentType: () => this.getType(),
496
499
  now: () => Math.trunc(Date.now() / 1000),
497
- readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
500
+ readAgentMdFresh,
498
501
  submitAgentMdRaw: (subAid, raw, condition, assertActive, stage) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive, stage),
499
502
  readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
500
503
  writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
@@ -554,9 +557,11 @@ export class AUNClient {
554
557
  _credentialRenewalTimer = null;
555
558
  _credentialRenewalAbort = null;
556
559
  _credentialRenewalScan = Promise.resolve();
560
+ _nextCredentialRevocationSyncAt = 0;
557
561
  _getCredentialRenewal() {
558
562
  return this._credentialRenewal ??= new CredentialRenewal({
559
563
  currentAid: () => this._aid ?? '',
564
+ queryContext: () => JSON.stringify([this._serviceEventIdentityVersion, this._gatewayUrl, this._transport?._connectionGeneration, this._transport?._workerGeneration]),
560
565
  atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
561
566
  load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
562
567
  ...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
@@ -630,6 +635,7 @@ export class AUNClient {
630
635
  if (aid === this._aid && this._identity) {
631
636
  this._identity.cert = identity.certPem;
632
637
  this._currentAid = identity;
638
+ this._auth.setIdentity(this._identity);
633
639
  }
634
640
  }
635
641
  return identity;
@@ -665,14 +671,14 @@ export class AUNClient {
665
671
  this._credentialRenewalAbort = abort;
666
672
  const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
667
673
  const guard = { current, signal: abort.signal };
668
- let nextRevocationSync = Date.now() + 3_600_000;
674
+ this._nextCredentialRevocationSyncAt ||= Date.now() + 3_600_000;
669
675
  const scan = async () => {
670
676
  const task = this._credentialRenewalScan.then(async () => {
671
677
  if (!current())
672
678
  return;
673
- const syncRevocations = Date.now() >= nextRevocationSync;
679
+ const syncRevocations = Date.now() >= this._nextCredentialRevocationSyncAt;
674
680
  if (syncRevocations)
675
- nextRevocationSync = Date.now() + 3_600_000;
681
+ this._nextCredentialRevocationSyncAt = Date.now() + 3_600_000;
676
682
  await this._resumeCredentialRenewalAfterConnect(guard, syncRevocations);
677
683
  });
678
684
  this._credentialRenewalScan = task.catch(() => { });
@@ -710,6 +716,10 @@ export class AUNClient {
710
716
  const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
711
717
  if (!current())
712
718
  throw new ValidationError('service event receiver identity changed');
719
+ const eventName = event.replace(/^event\//, '');
720
+ if (eventName.startsWith('org.') || ['credential.revoked', 'credential.revocationList'].includes(eventName)) {
721
+ this._getCredentialRenewal().invalidateQueries?.();
722
+ }
713
723
  if (['credential.revoked', 'credential.revocationList'].includes(event.replace(/^event\//, ''))) {
714
724
  await this._getCredentialRenewal()._receiveRevocations(payload, { current, signal });
715
725
  }
@@ -1187,7 +1197,12 @@ export class AUNClient {
1187
1197
  async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
1188
1198
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1189
1199
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1190
- async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
1200
+ async checkOwnershipConsensus(subAid) {
1201
+ return this._createOwnershipFiles((aid) => this._agentMdManager._readOwnershipQuery(aid)).checkConsensus(ownershipAid(subAid ?? this._aid, 'sub_aid'));
1202
+ }
1203
+ async _checkOwnershipConsensusFresh(subAid) {
1204
+ return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid);
1205
+ }
1191
1206
  async bindAid(subjectAid, options = {}) {
1192
1207
  throw ownershipFailure('unsupported_protocol', 'legacy bindAid is unsupported; use bindSubaid');
1193
1208
  }
@@ -1394,6 +1409,9 @@ export class AUNClient {
1394
1409
  this._identityRuntime.loadIdentity(aid);
1395
1410
  this._stopCredentialRenewal();
1396
1411
  this._serviceEventIdentityVersion++;
1412
+ this._auth.invalidateStrictPkiCache();
1413
+ this._certCache.clear();
1414
+ this._certFetchInFlight.clear();
1397
1415
  this._serviceEventIdentityAbort.abort();
1398
1416
  this._serviceEventIdentityAbort = new AbortController();
1399
1417
  this._ensureReadStateIdentity();
@@ -3339,6 +3357,8 @@ export class AUNClient {
3339
3357
  this._clientLog.debug(`bootstrap CA chain cache skipped: peer=${aid}, source=${opts.source ?? 'unknown'}, err=${formatCaughtError(exc)}`);
3340
3358
  }
3341
3359
  }
3360
+ const auth = this._auth;
3361
+ const pkiEpoch = auth._strictPkiEpoch;
3342
3362
  try {
3343
3363
  if (opts.signedAt === undefined)
3344
3364
  await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
@@ -3356,20 +3376,21 @@ export class AUNClient {
3356
3376
  }
3357
3377
  throw new ValidationError(`peer cert verification failed for ${aid}: ${exc instanceof Error ? exc.message : String(exc)}`);
3358
3378
  }
3379
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3380
+ throw new ValidationError('peer certificate validation context changed');
3359
3381
  const nowSec = Date.now() / 1000;
3360
3382
  const entry = {
3361
3383
  certPem,
3362
3384
  validatedAt: nowSec,
3363
- refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
3385
+ refreshAfter: Math.min(nowSec + PEER_CERT_CACHE_TTL, Date.parse(x509Cert.validTo) / 1000, auth._gatewayChainCache.get(`${peerGatewayUrl}:${aid}`)?.expiresAt / 1000 || Infinity),
3386
+ auth,
3387
+ pkiEpoch,
3364
3388
  };
3365
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
3389
+ const scope = opts.signedAt === undefined ? '' : `:historical${opts.agentMdRead ? ':agentmd' : ''}`;
3390
+ const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3391
+ const cacheKey = AUNClient._certCacheKey(aid, actualFp) + scope;
3366
3392
  this._certCache.set(cacheKey, entry);
3367
- if (!certFingerprint && opts.signedAt === undefined) {
3368
- const bareKey = AUNClient._certCacheKey(aid);
3369
- this._certCache.set(bareKey, entry);
3370
- const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3371
- this._certCache.set(AUNClient._certCacheKey(aid, actualFp), entry);
3372
- }
3393
+ this._certCache.set(AUNClient._certCacheKey(aid, certFingerprint) + scope, entry);
3373
3394
  try {
3374
3395
  // peer 证书只存版本目录,不覆盖 cert.pem
3375
3396
  this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
@@ -3391,14 +3412,46 @@ export class AUNClient {
3391
3412
  const tStart = Date.now();
3392
3413
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
3393
3414
  try {
3394
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
3415
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:historical${agentMdRead ? ':agentmd' : ''}`);
3395
3416
  const cached = this._certCache.get(cacheKey);
3396
3417
  const now = Date.now() / 1000;
3397
- if (cached && now < cached.refreshAfter) {
3418
+ const auth = this._auth, pkiEpoch = auth._strictPkiEpoch;
3419
+ if (cached && cached.auth === auth && cached.pkiEpoch === pkiEpoch && now < cached.refreshAfter) {
3420
+ if (signedAt === undefined) {
3421
+ const statusKey = `${cacheKey}:status:${pkiEpoch}`;
3422
+ let pending = this._certFetchInFlight.get(statusKey);
3423
+ if (!pending) {
3424
+ pending = (async () => {
3425
+ await auth.verifyPeerCertificate(AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid), cached.certPem, aid);
3426
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3427
+ throw new ValidationError('peer certificate status context changed');
3428
+ return cached.certPem;
3429
+ })();
3430
+ this._certFetchInFlight.set(statusKey, pending);
3431
+ }
3432
+ try {
3433
+ await pending;
3434
+ }
3435
+ finally {
3436
+ if (this._certFetchInFlight.get(statusKey) === pending)
3437
+ this._certFetchInFlight.delete(statusKey);
3438
+ }
3439
+ }
3440
+ else {
3441
+ const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid);
3442
+ if (agentMdRead) {
3443
+ validateAgentMdCertificate(cached.certPem, aid, signedAt, { requireCurrentValidity: false, readCompatibility: true });
3444
+ await auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(cached.certPem), aid, signedAt);
3445
+ }
3446
+ else
3447
+ await auth.verifyPeerCertificate(peerGatewayUrl, cached.certPem, aid, signedAt);
3448
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3449
+ throw new ValidationError('peer certificate validation context changed');
3450
+ }
3398
3451
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
3399
3452
  return cached.certPem;
3400
3453
  }
3401
- const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3454
+ const inFlightKey = `${cacheKey}:signed:${signedAt ?? 'current'}:timeout:${timeoutMs}:pki:${pkiEpoch}`;
3402
3455
  const pending = this._certFetchInFlight.get(inFlightKey);
3403
3456
  if (pending)
3404
3457
  return await pending;
@@ -3409,6 +3462,8 @@ export class AUNClient {
3409
3462
  }
3410
3463
  const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3411
3464
  const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3465
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3466
+ throw new ValidationError('peer certificate fetch context changed');
3412
3467
  const validated = await this._validateAndCachePeerCert({
3413
3468
  aid,
3414
3469
  certPem,
@@ -3829,7 +3884,9 @@ export class AUNClient {
3829
3884
  }
3830
3885
  /** 构建证书下载 URL */
3831
3886
  static _certCacheKey(aid, certFingerprint) {
3832
- const normalized = String(certFingerprint ?? '').trim().toLowerCase();
3887
+ const raw = String(certFingerprint ?? '').trim().toLowerCase();
3888
+ const hex = normalizeFingerprintHex(raw);
3889
+ const normalized = hex ? `sha256:${hex}` : raw;
3833
3890
  return normalized ? `${aid}#${normalized}` : aid;
3834
3891
  }
3835
3892
  static _buildCertUrl(gatewayUrl, aid, certFingerprint) {
@@ -4874,6 +4931,9 @@ export class AUNClient {
4874
4931
  /** 停止所有后台任务 */
4875
4932
  _stopBackgroundTasks() {
4876
4933
  this._stopCredentialRenewal();
4934
+ this._auth.invalidateStrictPkiCache();
4935
+ this._certCache.clear();
4936
+ this._certFetchInFlight.clear();
4877
4937
  if (this._heartbeatTimer !== null) {
4878
4938
  clearInterval(this._heartbeatTimer);
4879
4939
  this._heartbeatTimer = null;