@agentunion/fastaun-browser 0.5.18 → 0.5.20

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 (149) hide show
  1. package/CHANGELOG.md +172 -79
  2. package/_packed_docs/CHANGELOG.md +172 -79
  3. package/_packed_docs/INDEX.md +244 -116
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  7. 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 +42 -15
  8. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  9. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
  10. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  11. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  12. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  18. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
  21. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  22. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
  23. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
  26. package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
  27. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
  28. package/_packed_docs/sdk/INDEX.md +105 -34
  29. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  30. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  31. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  32. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  33. 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 +300 -0
  34. 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 +382 -0
  35. 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 +708 -0
  36. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  37. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  38. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  39. 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 +173 -0
  40. 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 +211 -0
  41. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  42. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  43. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  44. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  45. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  46. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  47. package/dist/agent-md-schema.d.ts +17 -3
  48. package/dist/agent-md-schema.d.ts.map +1 -1
  49. package/dist/agent-md-schema.js +94 -26
  50. package/dist/agent-md-schema.js.map +1 -1
  51. package/dist/agent-md.d.ts +54 -3
  52. package/dist/agent-md.d.ts.map +1 -1
  53. package/dist/agent-md.js +711 -65
  54. package/dist/agent-md.js.map +1 -1
  55. package/dist/aid-store.d.ts +58 -46
  56. package/dist/aid-store.d.ts.map +1 -1
  57. package/dist/aid-store.js +254 -248
  58. package/dist/aid-store.js.map +1 -1
  59. package/dist/aid.d.ts +4 -1
  60. package/dist/aid.d.ts.map +1 -1
  61. package/dist/aid.js +27 -33
  62. package/dist/aid.js.map +1 -1
  63. package/dist/auth.d.ts +4 -1
  64. package/dist/auth.d.ts.map +1 -1
  65. package/dist/auth.js +426 -39
  66. package/dist/auth.js.map +1 -1
  67. package/dist/bundle.js +38179 -32054
  68. package/dist/cert-utils.d.ts +50 -2
  69. package/dist/cert-utils.d.ts.map +1 -1
  70. package/dist/cert-utils.js +105 -47
  71. package/dist/cert-utils.js.map +1 -1
  72. package/dist/client/delivery.d.ts +22 -1
  73. package/dist/client/delivery.d.ts.map +1 -1
  74. package/dist/client/delivery.js +389 -78
  75. package/dist/client/delivery.js.map +1 -1
  76. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  77. package/dist/client/rpc-pipeline.js +103 -77
  78. package/dist/client/rpc-pipeline.js.map +1 -1
  79. package/dist/client/v2-e2ee.d.ts +1 -0
  80. package/dist/client/v2-e2ee.d.ts.map +1 -1
  81. package/dist/client/v2-e2ee.js +182 -89
  82. package/dist/client/v2-e2ee.js.map +1 -1
  83. package/dist/client.d.ts +100 -1
  84. package/dist/client.d.ts.map +1 -1
  85. package/dist/client.js +1220 -25
  86. package/dist/client.js.map +1 -1
  87. package/dist/crypto.d.ts.map +1 -1
  88. package/dist/crypto.js +8 -6
  89. package/dist/crypto.js.map +1 -1
  90. package/dist/errors.d.ts.map +1 -1
  91. package/dist/errors.js +3 -2
  92. package/dist/errors.js.map +1 -1
  93. package/dist/events.d.ts +9 -2
  94. package/dist/events.d.ts.map +1 -1
  95. package/dist/events.js +72 -13
  96. package/dist/events.js.map +1 -1
  97. package/dist/facades.d.ts +82 -2
  98. package/dist/facades.d.ts.map +1 -1
  99. package/dist/facades.js +100 -6
  100. package/dist/facades.js.map +1 -1
  101. package/dist/index.d.ts +7 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/keystore/index.d.ts +17 -0
  106. package/dist/keystore/index.d.ts.map +1 -1
  107. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  108. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  109. package/dist/keystore/indexeddb-shared.js +111 -0
  110. package/dist/keystore/indexeddb-shared.js.map +1 -1
  111. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  112. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  113. package/dist/keystore/indexeddb-token-store.js +30 -1
  114. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  115. package/dist/org-protocol.d.ts +98 -0
  116. package/dist/org-protocol.d.ts.map +1 -0
  117. package/dist/org-protocol.js +1802 -0
  118. package/dist/org-protocol.js.map +1 -0
  119. package/dist/ownership-proof.d.ts +93 -5
  120. package/dist/ownership-proof.d.ts.map +1 -1
  121. package/dist/ownership-proof.js +239 -53
  122. package/dist/ownership-proof.js.map +1 -1
  123. package/dist/ownership-v21.d.ts +194 -0
  124. package/dist/ownership-v21.d.ts.map +1 -0
  125. package/dist/ownership-v21.js +678 -0
  126. package/dist/ownership-v21.js.map +1 -0
  127. package/dist/ownership-workflow.d.ts +151 -0
  128. package/dist/ownership-workflow.d.ts.map +1 -0
  129. package/dist/ownership-workflow.js +1229 -0
  130. package/dist/ownership-workflow.js.map +1 -0
  131. package/dist/seq-tracker.d.ts +1 -0
  132. package/dist/seq-tracker.d.ts.map +1 -1
  133. package/dist/seq-tracker.js +10 -2
  134. package/dist/seq-tracker.js.map +1 -1
  135. package/dist/tools/cross-sdk-agent.d.ts +83 -0
  136. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  137. package/dist/tools/cross-sdk-agent.js +415 -7
  138. package/dist/tools/cross-sdk-agent.js.map +1 -1
  139. package/dist/transport.d.ts +7 -0
  140. package/dist/transport.d.ts.map +1 -1
  141. package/dist/transport.js +90 -2
  142. package/dist/transport.js.map +1 -1
  143. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  144. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  145. package/dist/v2/e2ee/decrypt.js +9 -1
  146. package/dist/v2/e2ee/decrypt.js.map +1 -1
  147. package/dist/version.d.ts +1 -1
  148. package/dist/version.js +1 -1
  149. package/package.json +1 -1
package/dist/auth.js CHANGED
@@ -6,6 +6,7 @@ import { AuthError, StateError, ValidationError, mapRemoteError } from './errors
6
6
  import { ROOT_CA_PEM } from './certs/root.js';
7
7
  import { VERSION as AUN_SDK_VERSION } from './version.js';
8
8
  import { createTransportWebSocket } from './transport.js';
9
+ import { certFingerprint, isStrictBase64 } from './cert-utils.js';
9
10
  const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
10
11
  const AUN_SDK_LANG = 'javascript';
11
12
  const SENSITIVE_RPC_LOG_KEYS = new Set([
@@ -209,7 +210,7 @@ function parseCertDer(pem) {
209
210
  const serialBytes = der.slice(serialStart, serialStart + serialLen);
210
211
  const serialHex = Array.from(serialBytes)
211
212
  .map((b) => b.toString(16).padStart(2, '0'))
212
- .join('');
213
+ .join('').replace(/^0+/, '') || '0';
213
214
  pos = serialStart + serialLen;
214
215
  // 签名算法(SEQUENCE)
215
216
  const [, sigAlgStart, sigAlgLen] = readTlv(der, pos);
@@ -237,24 +238,86 @@ function parseCertDer(pem) {
237
238
  const [spkiFullStart, spkiFullEnd] = getTlvFullRange(der, pos);
238
239
  const spkiBytes = der.slice(spkiFullStart, spkiFullEnd);
239
240
  pos = spkiFullEnd;
240
- // 检查 BasicConstraints(在 extensions 中)
241
241
  let isCA = false;
242
- // extensions 在 [3] EXPLICIT 中
243
- const extensionsSearchStart = pos;
244
- const extensionsSearchEnd = tbsEnd;
245
- // 搜索 BasicConstraints OID: 2.5.29.19 → 55 1D 13
246
- const bcOidBytes = new Uint8Array([0x55, 0x1d, 0x13]);
247
- for (let i = extensionsSearchStart; i < extensionsSearchEnd - 3; i++) {
248
- if (der[i] === bcOidBytes[0] && der[i + 1] === bcOidBytes[1] && der[i + 2] === bcOidBytes[2]) {
249
- // 找到 BasicConstraints OID,在后续字节中搜索 BOOLEAN TRUE (01 01 FF)
250
- const searchEnd = Math.min(i + 20, extensionsSearchEnd);
251
- for (let j = i + 3; j < searchEnd - 2; j++) {
252
- if (der[j] === 0x01 && der[j + 1] === 0x01 && der[j + 2] === 0xff) {
242
+ let pathLenConstraint;
243
+ let keyCertSign;
244
+ const extensionTlv = (offset, end, tag) => {
245
+ if (offset + 2 > end || der[offset + 1] === 0x80 || der[offset + 1] > 0x84)
246
+ throw new AuthError('invalid certificate extension DER');
247
+ const value = readTlv(der, offset);
248
+ if (value[1] + value[2] > end || value[2] < 0 || tag !== undefined && value[0] !== tag)
249
+ throw new AuthError('invalid certificate extension DER');
250
+ return value;
251
+ };
252
+ const seenExtensions = new Set();
253
+ while (pos < tbsEnd) {
254
+ const [tag, start, length] = extensionTlv(pos, tbsEnd);
255
+ pos = start + length;
256
+ if (tag === 0x81 || tag === 0x82)
257
+ continue;
258
+ if (tag !== 0xa3)
259
+ throw new AuthError('invalid certificate extensions');
260
+ const [, sequenceStart, sequenceLength] = extensionTlv(start, pos, 0x30);
261
+ if (sequenceStart + sequenceLength !== pos)
262
+ throw new AuthError('invalid certificate extensions');
263
+ let extensionPos = sequenceStart;
264
+ while (extensionPos < pos) {
265
+ const [, extensionStart, extensionLength] = extensionTlv(extensionPos, pos, 0x30);
266
+ const extensionEnd = extensionStart + extensionLength;
267
+ const [, oidStart, oidLength] = extensionTlv(extensionStart, extensionEnd, 0x06);
268
+ const oid = parseOid(der.slice(oidStart, oidStart + oidLength));
269
+ if (seenExtensions.has(oid))
270
+ throw new AuthError('duplicate certificate extension');
271
+ seenExtensions.add(oid);
272
+ let valuePos = oidStart + oidLength;
273
+ let critical = false;
274
+ if (der[valuePos] === 0x01) {
275
+ const [, boolStart, boolLength] = extensionTlv(valuePos, extensionEnd, 0x01);
276
+ if (boolLength !== 1 || der[boolStart] !== 0xff)
277
+ throw new AuthError('invalid certificate extension critical flag');
278
+ critical = true;
279
+ valuePos = boolStart + boolLength;
280
+ }
281
+ const [, valueStart, valueLength] = extensionTlv(valuePos, extensionEnd, 0x04);
282
+ if (valueStart + valueLength !== extensionEnd)
283
+ throw new AuthError('invalid certificate extension value');
284
+ if (oid === '2.5.29.19') {
285
+ const [, constraintStart, constraintLength] = extensionTlv(valueStart, extensionEnd, 0x30);
286
+ const constraintEnd = constraintStart + constraintLength;
287
+ if (constraintEnd !== extensionEnd)
288
+ throw new AuthError('invalid BasicConstraints');
289
+ let constraintPos = constraintStart;
290
+ if (der[constraintPos] === 0x01) {
291
+ const [, boolStart, boolLength] = extensionTlv(constraintPos, constraintEnd, 0x01);
292
+ if (boolLength !== 1 || der[boolStart] !== 0xff)
293
+ throw new AuthError('invalid BasicConstraints CA flag');
253
294
  isCA = true;
254
- break;
295
+ constraintPos = boolStart + boolLength;
296
+ }
297
+ if (constraintPos < constraintEnd) {
298
+ const [, integerStart, integerLength] = extensionTlv(constraintPos, constraintEnd, 0x02);
299
+ if (!isCA || !integerLength || der[integerStart] & 0x80 || integerLength > 1 && der[integerStart] === 0 && !(der[integerStart + 1] & 0x80))
300
+ throw new AuthError('invalid BasicConstraints path length');
301
+ pathLenConstraint = 0;
302
+ for (let index = integerStart; index < integerStart + integerLength; index++)
303
+ pathLenConstraint = pathLenConstraint * 256 + der[index];
304
+ if (!Number.isSafeInteger(pathLenConstraint))
305
+ throw new AuthError('invalid BasicConstraints path length');
306
+ constraintPos = integerStart + integerLength;
255
307
  }
308
+ if (constraintPos !== constraintEnd)
309
+ throw new AuthError('invalid BasicConstraints');
256
310
  }
257
- break;
311
+ else if (oid === '2.5.29.15') {
312
+ const [, bitsStart, bitsLength] = extensionTlv(valueStart, extensionEnd, 0x03);
313
+ if (bitsStart + bitsLength !== extensionEnd || bitsLength < 2 || der[bitsStart] > 7 || der[bitsStart + bitsLength - 1] & ((1 << der[bitsStart]) - 1))
314
+ throw new AuthError('invalid KeyUsage');
315
+ keyCertSign = Boolean(der[bitsStart + 1] & 0x04);
316
+ }
317
+ else if (critical) {
318
+ throw new AuthError(`unsupported critical certificate extension: ${oid}`);
319
+ }
320
+ extensionPos = extensionEnd;
258
321
  }
259
322
  }
260
323
  // 签名算法(第二个,在 TBS 之后)
@@ -277,6 +340,8 @@ function parseCertDer(pem) {
277
340
  notBefore,
278
341
  notAfter,
279
342
  isCA,
343
+ pathLenConstraint,
344
+ keyCertSign,
280
345
  spkiBytes,
281
346
  };
282
347
  }
@@ -333,6 +398,112 @@ async function verifyEcdsaSignature(spkiDer, signatureDer, data) {
333
398
  const p1363Sig = derToP1363(signatureDer, curveLen);
334
399
  return crypto.subtle.verify({ name: 'ECDSA', hash }, pubKey, toBufferSource(p1363Sig), toBufferSource(data));
335
400
  }
401
+ function ownershipDerNode(data, offset, limit = data.length) {
402
+ if (offset + 2 > limit || data[offset + 1] === 0x80 || data[offset + 1] > 0x84)
403
+ throw new AuthError('invalid OCSP DER length');
404
+ const lengthBytes = data[offset + 1] & 0x80 ? data[offset + 1] & 0x7f : 0;
405
+ if (offset + 2 + lengthBytes > limit || lengthBytes && data[offset + 2] === 0)
406
+ throw new AuthError('invalid OCSP DER length');
407
+ const [tag, valueStart, length] = readTlv(data, offset);
408
+ if (length < 0 || valueStart + length > limit || lengthBytes && length < 128)
409
+ throw new AuthError('invalid OCSP DER bounds');
410
+ return { tag, start: offset, valueStart, end: valueStart + length };
411
+ }
412
+ function ownershipDerChildren(data, node) {
413
+ const children = [];
414
+ for (let offset = node.valueStart; offset < node.end;) {
415
+ const child = ownershipDerNode(data, offset, node.end);
416
+ children.push(child);
417
+ offset = child.end;
418
+ }
419
+ return children;
420
+ }
421
+ async function verifyOwnershipOcsp(raw, cert, issuer) {
422
+ const outer = ownershipDerNode(raw, 0);
423
+ if (outer.tag !== 0x30 || outer.end !== raw.length)
424
+ throw new AuthError('invalid OCSP response container');
425
+ const fields = ownershipDerChildren(raw, outer);
426
+ if (!fields[0])
427
+ throw new StateError('OCSP response status is missing', { stringCode: 'unavailable', retryable: true });
428
+ if (fields[0].tag !== 0x0a || fields[0].end - fields[0].valueStart !== 1)
429
+ throw new AuthError('invalid OCSP response status');
430
+ if (raw[fields[0].valueStart] !== 0 || !fields[1])
431
+ throw new StateError('OCSP current signed status unavailable', { stringCode: 'unavailable', retryable: true });
432
+ if (fields.length !== 2 || fields[1].tag !== 0xa0)
433
+ throw new AuthError('invalid OCSP response fields');
434
+ const response = ownershipDerChildren(raw, ownershipDerChildren(raw, fields[1])[0]);
435
+ if (response.length !== 2 || response[0].tag !== 0x06 || parseOid(raw.slice(response[0].valueStart, response[0].end)) !== '1.3.6.1.5.5.7.48.1.1' || response[1].tag !== 0x04)
436
+ throw new AuthError('invalid OCSP basic response');
437
+ const basic = raw.slice(response[1].valueStart, response[1].end);
438
+ const basicNode = ownershipDerNode(basic, 0);
439
+ const items = ownershipDerChildren(basic, basicNode);
440
+ if (basicNode.tag !== 0x30 || basicNode.end !== basic.length || (items.length !== 3 && items.length !== 4) || items[0].tag !== 0x30 || items[1].tag !== 0x30 || items[2].tag !== 0x03 || basic[items[2].valueStart] !== 0)
441
+ throw new AuthError('invalid OCSP signed response');
442
+ if (items[3]) {
443
+ if (items[3].tag !== 0xa0)
444
+ throw new AuthError('invalid OCSP embedded certificates');
445
+ const sequences = ownershipDerChildren(basic, items[3]);
446
+ if (sequences.length !== 1 || sequences[0].tag !== 0x30)
447
+ throw new AuthError('invalid OCSP embedded certificates');
448
+ const certificates = ownershipDerChildren(basic, sequences[0]);
449
+ const issuerDer = new Uint8Array(pemToArrayBuffer(issuer.pem));
450
+ if (certificates.length !== 1 || certificates[0].tag !== 0x30 || !arraysEqual(basic.slice(certificates[0].start, certificates[0].end), issuerDer))
451
+ throw new AuthError('OCSP embedded certificate is not the verified issuer');
452
+ }
453
+ const algorithm = ownershipDerChildren(basic, items[1])[0];
454
+ await verifyCertSignature(issuer.spkiBytes, { ...cert,
455
+ tbsBytes: basic.slice(items[0].start, items[0].end), signatureBytes: basic.slice(items[2].valueStart + 1, items[2].end),
456
+ signatureAlgorithmOid: parseOid(basic.slice(algorithm.valueStart, algorithm.end)),
457
+ });
458
+ const data = ownershipDerChildren(basic, items[0]);
459
+ if (data[0]?.tag === 0xa0)
460
+ data.shift();
461
+ if (data.length < 3 || ![0xa1, 0xa2, 0x82].includes(data[0].tag) || data[1].tag !== 0x18 || data[2].tag !== 0x30)
462
+ throw new AuthError('invalid OCSP response data');
463
+ const publicBits = ownershipDerChildren(issuer.spkiBytes, ownershipDerNode(issuer.spkiBytes, 0))[1];
464
+ const responder = data[0], keyHash = new Uint8Array(await crypto.subtle.digest('SHA-1', toBufferSource(issuer.spkiBytes.slice(publicBits.valueStart + 1, publicBits.end))));
465
+ const responderKey = responder.tag === 0xa2 ? ownershipDerChildren(basic, responder) : [];
466
+ const responderValid = responder.tag === 0xa1 ? arraysEqual(basic.slice(responder.valueStart, responder.end), issuer.subjectRaw)
467
+ : responder.tag === 0x82 ? arraysEqual(basic.slice(responder.valueStart, responder.end), keyHash)
468
+ : responderKey.length === 1 && responderKey[0].tag === 0x04 && arraysEqual(basic.slice(responderKey[0].valueStart, responderKey[0].end), keyHash);
469
+ if (!responderValid)
470
+ throw new AuthError('OCSP responder is not the verified issuer');
471
+ const responses = ownershipDerChildren(basic, data[2]);
472
+ if (responses.length !== 1)
473
+ throw new AuthError('OCSP response must identify one certificate');
474
+ const single = ownershipDerChildren(basic, responses[0]);
475
+ if (single.length < 4 || single[3].tag !== 0xa0)
476
+ throw new StateError('OCSP current time evidence unavailable', { stringCode: 'unavailable', retryable: true });
477
+ if (single[0].tag !== 0x30 || single[2].tag !== 0x18)
478
+ throw new AuthError('invalid OCSP current time evidence');
479
+ const certId = ownershipDerChildren(basic, single[0]);
480
+ if (certId.length !== 4 || certId[1].tag !== 0x04 || certId[2].tag !== 0x04 || certId[3].tag !== 0x02)
481
+ throw new AuthError('invalid OCSP certificate identity');
482
+ const hashAlgorithm = ownershipDerChildren(basic, certId[0])[0];
483
+ const hashOid = parseOid(basic.slice(hashAlgorithm.valueStart, hashAlgorithm.end));
484
+ const hashName = hashOid === '1.3.14.3.2.26' ? 'SHA-1' : hashOid === '2.16.840.1.101.3.4.2.1' ? 'SHA-256' : '';
485
+ if (!hashName)
486
+ throw new StateError('unsupported OCSP certificate identity hash', { stringCode: 'unavailable', retryable: true });
487
+ const digest = async (bytes) => new Uint8Array(await crypto.subtle.digest(hashName, toBufferSource(bytes)));
488
+ if (!arraysEqual(await digest(issuer.subjectRaw), basic.slice(certId[1].valueStart, certId[1].end)) || !arraysEqual(await digest(issuer.spkiBytes.slice(publicBits.valueStart + 1, publicBits.end)), basic.slice(certId[2].valueStart, certId[2].end)))
489
+ throw new AuthError('OCSP issuer identity mismatch');
490
+ const serial = Array.from(basic.slice(certId[3].valueStart, certId[3].end), (byte) => byte.toString(16).padStart(2, '0')).join('').replace(/^0+/, '') || '0';
491
+ if (serial !== (cert.serialHex.replace(/^0+/, '') || '0'))
492
+ throw new AuthError('OCSP certificate serial mismatch');
493
+ const status = single[1].tag === 0xa1 ? 'revoked' : single[1].tag === 0x80 && single[1].end === single[1].valueStart ? 'good' : single[1].tag === 0x82 && single[1].end === single[1].valueStart ? 'unknown' : '';
494
+ if (!status)
495
+ throw new AuthError('invalid OCSP certificate status');
496
+ const next = ownershipDerChildren(basic, single[3])[0];
497
+ const time = (node) => {
498
+ if (node.tag !== 0x18 || !/^\d{14}Z$/.test(new TextDecoder().decode(basic.slice(node.valueStart, node.end))))
499
+ throw new AuthError('invalid OCSP UTC time');
500
+ return parseAsn1Time(basic, node.start);
501
+ };
502
+ const now = Date.now() / 1000, producedAt = time(data[1]), thisUpdate = time(single[2]), nextUpdate = time(next);
503
+ if (producedAt > now + 5 || producedAt < now - 90 || thisUpdate > now + 5 || thisUpdate < now - 30 || nextUpdate <= now || nextUpdate <= thisUpdate)
504
+ throw new StateError('OCSP current status evidence is stale', { stringCode: 'unavailable', retryable: true });
505
+ return status;
506
+ }
336
507
  // ── Gateway URL 工具 ────────────────────────────────────
337
508
  /** 将 WebSocket URL 转为对应的 HTTP URL */
338
509
  function gatewayHttpUrl(gatewayUrl, path) {
@@ -754,13 +925,136 @@ export class AuthFlow {
754
925
  /**
755
926
  * 统一的对端证书验证入口:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
756
927
  */
757
- async verifyPeerCertificate(gatewayUrl, certPem, expectedAid) {
928
+ async resolveCurrentEntityCertificate(gatewayUrl, aid, type) {
929
+ const url = new URL(gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}`));
930
+ url.searchParams.set('consistency', 'authoritative');
931
+ if (type === 'group')
932
+ url.searchParams.set('key_purpose', 'group_identity');
933
+ const controller = new AbortController();
934
+ const timeout = setTimeout(() => controller.abort(), 5000);
935
+ try {
936
+ const response = await fetch(url.toString(), { signal: controller.signal, cache: 'no-store', redirect: 'error' });
937
+ if (!response.ok)
938
+ throw new StateError(`entity current certificate unavailable: HTTP ${response.status}`, { stringCode: 'unavailable', retryable: true });
939
+ const certPem = await response.text();
940
+ if (!certPem.trim())
941
+ throw new StateError('entity current certificate is unavailable', { stringCode: 'unavailable', retryable: true });
942
+ let fingerprint;
943
+ try {
944
+ parseCertDer(certPem);
945
+ fingerprint = await certFingerprint(certPem);
946
+ }
947
+ catch {
948
+ throw new ValidationError('entity current certificate is invalid', { stringCode: 'invalid' });
949
+ }
950
+ return await this.resolveOwnershipCertificate(gatewayUrl, aid, fingerprint, type === 'group' ? 'group_identity' : undefined);
951
+ }
952
+ catch (error) {
953
+ if (error instanceof StateError || error instanceof ValidationError)
954
+ throw error;
955
+ throw new StateError(`entity current certificate unavailable: ${String(error)}`, { stringCode: 'unavailable', retryable: true });
956
+ }
957
+ finally {
958
+ clearTimeout(timeout);
959
+ }
960
+ }
961
+ async resolveOwnershipCertificate(gatewayUrl, expectedAid, fingerprint, keyPurpose) {
962
+ if (!/^sha256:[0-9a-f]{64}$/.test(fingerprint))
963
+ throw new ValidationError('ownership certificate fingerprint is invalid', { stringCode: 'invalid' });
964
+ const began = Date.now();
965
+ const url = new URL(gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(expectedAid)}`));
966
+ url.searchParams.set('cert_fingerprint', fingerprint);
967
+ url.searchParams.set('consistency', 'authoritative');
968
+ if (keyPurpose)
969
+ url.searchParams.set('key_purpose', keyPurpose);
970
+ const controller = new AbortController();
971
+ const timeout = setTimeout(() => controller.abort(), 5000);
972
+ try {
973
+ const response = await fetch(url.toString(), { signal: controller.signal, cache: 'no-store', redirect: 'error' });
974
+ if (!response.ok)
975
+ throw new StateError(`ownership certificate unavailable: HTTP ${response.status}`, { stringCode: 'unavailable', retryable: true });
976
+ const state = response.headers.get('X-AUN-Cert-Lifecycle-State') ?? '';
977
+ const observed = response.headers.get('X-AUN-Cert-Observed-At') ?? '';
978
+ const observedAt = Math.trunc(Date.parse(observed) / 1000);
979
+ const now = Math.trunc(Date.now() / 1000);
980
+ if (response.headers.get('X-AUN-Cert-Consistency') !== 'authoritative' || !state || !response.headers.get('X-AUN-Cert-Fingerprint') || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/.test(observed) || !Number.isSafeInteger(observedAt) || observedAt < now - 30 || observedAt > now + 5)
981
+ throw new StateError('ownership current certificate metadata unavailable', { stringCode: 'unavailable', retryable: true });
982
+ if (state !== 'active_signing' || response.headers.get('X-AUN-Cert-Fingerprint') !== fingerprint)
983
+ throw new ValidationError('ownership certificate lifecycle or fingerprint is invalid', { stringCode: 'invalid' });
984
+ const certPem = await response.text();
985
+ if (!certPem.trim())
986
+ throw new StateError('ownership certificate is missing', { stringCode: 'unavailable', retryable: true });
987
+ let cert;
988
+ try {
989
+ cert = parseCertDer(certPem);
990
+ }
991
+ catch {
992
+ throw new ValidationError('ownership certificate PEM is invalid', { stringCode: 'invalid' });
993
+ }
994
+ if (cert.subjectCN !== expectedAid || await certFingerprint(certPem) !== fingerprint || cert.notBefore > now || cert.notAfter <= now)
995
+ throw new ValidationError('ownership current certificate identity or validity mismatch', { stringCode: 'invalid' });
996
+ const chainPems = await this._fetchGatewayCaChain(gatewayUrl, true);
997
+ let chain;
998
+ try {
999
+ chain = chainPems.map((pem) => parseCertDer(pem));
1000
+ }
1001
+ catch {
1002
+ throw new ValidationError('ownership certificate chain PEM is invalid', { stringCode: 'invalid' });
1003
+ }
1004
+ if (!chain.length)
1005
+ throw new StateError('ownership certificate chain unavailable', { stringCode: 'unavailable', retryable: true });
1006
+ if (chain.some((ca) => ca.notBefore > now || ca.notAfter <= now))
1007
+ throw new ValidationError('ownership current CA certificate validity mismatch', { stringCode: 'invalid' });
1008
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid, undefined, true, chain);
1009
+ await this._fetchGatewayOcspStatus(gatewayUrl, cert, chain[0], true);
1010
+ const finalNow = Date.now();
1011
+ if ([cert, ...chain].some((item) => finalNow / 1000 < item.notBefore || finalNow / 1000 >= item.notAfter))
1012
+ throw new ValidationError('ownership current certificate chain validity mismatch', { stringCode: 'invalid' });
1013
+ if (finalNow - began > 30_000 || finalNow / 1000 - observedAt > 30)
1014
+ throw new StateError('ownership current certificate evidence became stale', { stringCode: 'unavailable', retryable: true });
1015
+ return { cert_pem: certPem, chain_valid: true, state, revoked: false, revocation_checked: true, observed_at: observedAt };
1016
+ }
1017
+ catch (error) {
1018
+ if (error instanceof StateError || error instanceof ValidationError)
1019
+ throw error;
1020
+ if (error instanceof AuthError)
1021
+ throw new ValidationError(error.message, { stringCode: 'invalid' });
1022
+ throw new StateError(`ownership certificate unavailable: ${error instanceof Error ? error.message : String(error)}`, { stringCode: 'unavailable', retryable: true });
1023
+ }
1024
+ finally {
1025
+ clearTimeout(timeout);
1026
+ }
1027
+ }
1028
+ async verifyPeerCertificate(gatewayUrl, certPem, expectedAid, signedAt, strictRevocation = false, agentMdRead = false) {
758
1029
  const tStart = Date.now();
759
1030
  this._log.debug(`verifyPeerCertificate enter: aid=${expectedAid} gateway=${gatewayUrl}`);
760
1031
  try {
761
1032
  const cert = parseCertDer(certPem);
1033
+ if (signedAt !== undefined) {
1034
+ ensureCertTimeValid(cert, 'peer certificate', signedAt);
1035
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid, signedAt);
1036
+ if (agentMdRead ? cert.subjectCN.trim().toLowerCase() !== expectedAid.trim().toLowerCase() : cert.subjectCN !== expectedAid)
1037
+ throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}`);
1038
+ return;
1039
+ }
762
1040
  const now = Date.now() / 1000;
763
1041
  ensureCertTimeValid(cert, 'peer certificate', now);
1042
+ if (strictRevocation) {
1043
+ const chain = (await this._fetchGatewayCaChain(gatewayUrl, true)).map((pem) => parseCertDer(pem));
1044
+ if (!chain.length)
1045
+ throw new AuthError('service event certificate chain is unavailable');
1046
+ const current = Math.trunc(Date.now() / 1000);
1047
+ if ([cert, ...chain].some((item) => current < item.notBefore || current > item.notAfter))
1048
+ throw new AuthError('service event certificate is outside its validity window');
1049
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid, undefined, true, chain);
1050
+ const crl = await this._fetchGatewayCrl(gatewayUrl, chain[0], true);
1051
+ if (crl.revokedSerials.has(cert.serialHex.toLowerCase()))
1052
+ throw new AuthError('service event certificate has been revoked');
1053
+ await this._fetchGatewayOcspStatus(gatewayUrl, cert, chain[0], true);
1054
+ if (cert.subjectCN !== expectedAid)
1055
+ throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}`);
1056
+ return;
1057
+ }
764
1058
  await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
765
1059
  try {
766
1060
  await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
@@ -902,17 +1196,19 @@ export class AuthFlow {
902
1196
  }
903
1197
  // ── 内部方法:HTTP 请求 ───────────────────────────
904
1198
  /** fetch GET 返回文本 */
905
- async _fetchText(url) {
1199
+ async _fetchText(url, fresh = false) {
906
1200
  // 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
907
1201
  const controller = new AbortController();
908
1202
  const timeoutId = setTimeout(() => controller.abort(), 5000);
909
1203
  try {
910
- const resp = await fetch(url, { signal: controller.signal });
1204
+ const resp = await fetch(url, { signal: controller.signal, ...(fresh ? { cache: 'no-store', redirect: 'error' } : {}) });
911
1205
  if (!resp.ok)
912
1206
  throw new Error(`HTTP ${resp.status}`);
913
1207
  return await resp.text();
914
1208
  }
915
1209
  catch (e) {
1210
+ if (fresh)
1211
+ throw new StateError(`ownership certificate material unavailable: ${e}`, { stringCode: 'unavailable', retryable: true });
916
1212
  throw new AuthError(`failed to fetch ${url}: ${e}`);
917
1213
  }
918
1214
  finally {
@@ -920,12 +1216,27 @@ export class AuthFlow {
920
1216
  }
921
1217
  }
922
1218
  /** fetch GET 返回 JSON */
923
- async _fetchJson(url) {
1219
+ async _fetchJson(url, fresh = false) {
1220
+ if (fresh) {
1221
+ const text = await this._fetchText(url, true);
1222
+ if (!text.trim())
1223
+ throw new StateError('ownership certificate material is missing', { stringCode: 'unavailable', retryable: true });
1224
+ let payload;
1225
+ try {
1226
+ payload = JSON.parse(text);
1227
+ }
1228
+ catch {
1229
+ throw new ValidationError('ownership certificate JSON is invalid', { stringCode: 'invalid' });
1230
+ }
1231
+ if (!isJsonObject(payload))
1232
+ throw new ValidationError('ownership certificate JSON is invalid', { stringCode: 'invalid' });
1233
+ return payload;
1234
+ }
924
1235
  // 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
925
1236
  const controller = new AbortController();
926
1237
  const timeoutId = setTimeout(() => controller.abort(), 5000);
927
1238
  try {
928
- const resp = await fetch(url, { signal: controller.signal });
1239
+ const resp = await fetch(url, { signal: controller.signal, ...(fresh ? { cache: 'no-store', redirect: 'error' } : {}) });
929
1240
  if (!resp.ok)
930
1241
  throw new Error(`HTTP ${resp.status}`);
931
1242
  const payload = await resp.json();
@@ -935,6 +1246,8 @@ export class AuthFlow {
935
1246
  return payload;
936
1247
  }
937
1248
  catch (e) {
1249
+ if (fresh)
1250
+ throw new StateError(`ownership certificate material unavailable: ${e}`, { stringCode: 'unavailable', retryable: true });
938
1251
  if (e instanceof AuthError)
939
1252
  throw e;
940
1253
  throw new AuthError(`failed to fetch ${url}: ${e}`);
@@ -1165,29 +1478,30 @@ export class AuthFlow {
1165
1478
  }
1166
1479
  }
1167
1480
  // ── 内部方法:证书链验证 ──────────────────────────
1168
- async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '') {
1481
+ async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, fresh = false, suppliedChain) {
1169
1482
  // 检查缓存
1170
- const cachedAt = this._chainVerifiedCache.get(authCert.serialHex);
1171
- const now = Date.now() / 1000;
1172
- if (cachedAt && now - cachedAt < this._chainCacheTtl)
1483
+ const certKey = authCert.serialHex + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1484
+ const cachedAt = this._chainVerifiedCache.get(certKey);
1485
+ const now = signedAt === undefined ? Date.now() / 1000 : signedAt;
1486
+ if (!fresh && cachedAt && Date.now() / 1000 - cachedAt < this._chainCacheTtl)
1173
1487
  return;
1174
1488
  ensureCertTimeValid(authCert, 'auth certificate', now);
1175
- const chain = await this._loadGatewayCaChain(gatewayUrl, chainAid);
1489
+ const chain = suppliedChain ?? await this._loadGatewayCaChain(gatewayUrl, chainAid);
1176
1490
  if (!chain.length) {
1177
1491
  throw new AuthError('unable to verify auth certificate chain: missing CA chain');
1178
1492
  }
1179
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1493
+ const cacheKey = (chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1180
1494
  // 快速路径:CA 链已预验证过
1181
- if (this._gatewayCaVerified.get(cacheKey)) {
1495
+ if (!fresh && this._gatewayCaVerified.get(cacheKey)) {
1182
1496
  const issuer = chain[0];
1183
1497
  ensureCertTimeValid(issuer, 'Issuer CA', now);
1184
- if (!issuer.isCA)
1498
+ if (!issuer.isCA || issuer.keyCertSign === false)
1185
1499
  throw new AuthError('Issuer CA is not marked as CA (fast path)');
1186
1500
  if (!arraysEqual(authCert.issuerRaw, issuer.subjectRaw)) {
1187
1501
  throw new AuthError('auth certificate issuer mismatch');
1188
1502
  }
1189
1503
  await verifyCertSignature(issuer.spkiBytes, authCert);
1190
- this._chainVerifiedCache.set(authCert.serialHex, Date.now() / 1000);
1504
+ this._chainVerifiedCache.set(certKey, Date.now() / 1000);
1191
1505
  return;
1192
1506
  }
1193
1507
  // 完整验证路径
@@ -1198,9 +1512,12 @@ export class AuthFlow {
1198
1512
  if (!arraysEqual(current.issuerRaw, caCert.subjectRaw)) {
1199
1513
  throw new AuthError(`auth certificate issuer mismatch at chain level ${i}`);
1200
1514
  }
1201
- if (!caCert.isCA) {
1515
+ if (!caCert.isCA || caCert.keyCertSign === false) {
1202
1516
  throw new AuthError(`CA certificate[${i}] is not marked as CA`);
1203
1517
  }
1518
+ const subordinateCas = chain.slice(0, i).filter((cert) => cert.isCA).length;
1519
+ if (caCert.pathLenConstraint !== undefined && subordinateCas > caCert.pathLenConstraint)
1520
+ throw new AuthError(`CA certificate[${i}] path length constraint exceeded`);
1204
1521
  await verifyCertSignature(caCert.spkiBytes, current);
1205
1522
  current = caCert;
1206
1523
  }
@@ -1211,15 +1528,25 @@ export class AuthFlow {
1211
1528
  }
1212
1529
  await verifyCertSignature(root.spkiBytes, root);
1213
1530
  // 验证根证书在受信列表中
1214
- const trustedRoots = this._loadTrustedRoots();
1531
+ let trustedRoots;
1532
+ try {
1533
+ trustedRoots = this._loadTrustedRoots();
1534
+ }
1535
+ catch (error) {
1536
+ if (fresh)
1537
+ throw new StateError('ownership trust anchors are unavailable', { stringCode: 'unavailable', retryable: true });
1538
+ throw error;
1539
+ }
1215
1540
  const rootSpkiB64 = uint8ToBase64(root.spkiBytes);
1216
1541
  const isTrusted = trustedRoots.some((trusted) => uint8ToBase64(trusted.spkiBytes) === rootSpkiB64
1217
1542
  && trusted.subjectCN === root.subjectCN);
1218
1543
  if (!isTrusted) {
1219
1544
  throw new AuthError('auth certificate chain is not anchored by a trusted root');
1220
1545
  }
1221
- this._chainVerifiedCache.set(authCert.serialHex, Date.now() / 1000);
1222
- this._gatewayCaVerified.set(cacheKey, true);
1546
+ if (!fresh) {
1547
+ this._chainVerifiedCache.set(certKey, Date.now() / 1000);
1548
+ this._gatewayCaVerified.set(cacheKey, true);
1549
+ }
1223
1550
  }
1224
1551
  /** 加载 Gateway CA 链(带缓存) */
1225
1552
  async _loadGatewayCaChain(gatewayUrl, chainAid = '') {
@@ -1232,9 +1559,9 @@ export class AuthFlow {
1232
1559
  return cached.map((pem) => parseCertDer(pem));
1233
1560
  }
1234
1561
  /** 从 Gateway PKI 端点下载 CA 链 */
1235
- async _fetchGatewayCaChain(gatewayUrl) {
1562
+ async _fetchGatewayCaChain(gatewayUrl, fresh = false) {
1236
1563
  const url = gatewayHttpUrl(gatewayUrl, '/pki/chain');
1237
- const text = await this._fetchText(url);
1564
+ const text = await this._fetchText(url, fresh);
1238
1565
  return splitPemBundle(text);
1239
1566
  }
1240
1567
  // ── 内部方法:CRL 验证 ────────────────────────────
@@ -1270,9 +1597,47 @@ export class AuthFlow {
1270
1597
  return fresh.revokedSerials;
1271
1598
  }
1272
1599
  /** 从 Gateway PKI 端点获取并验证 CRL */
1273
- async _fetchGatewayCrl(gatewayUrl, issuerCert) {
1600
+ async _fetchGatewayCrl(gatewayUrl, issuerCert, fresh = false) {
1274
1601
  const url = gatewayHttpUrl(gatewayUrl, '/pki/crl.json');
1275
- const payload = await this._fetchJson(url);
1602
+ const payload = await this._fetchJson(url, fresh);
1603
+ if (fresh) {
1604
+ const pem = payload.crl_pem;
1605
+ if (typeof pem !== 'string' || !/^-----BEGIN X509 CRL-----\s+([A-Za-z0-9+/=\s]+)-----END X509 CRL-----\s*$/.test(pem))
1606
+ throw new AuthError('signed current CRL is unavailable');
1607
+ const encoded = pem.replace(/-----BEGIN X509 CRL-----|-----END X509 CRL-----|\s/g, '');
1608
+ if (encoded.length % 4 !== 0 || btoa(atob(encoded)) !== encoded)
1609
+ throw new AuthError('signed current CRL encoding is invalid');
1610
+ const der = base64ToUint8(encoded);
1611
+ const outer = ownershipDerNode(der, 0);
1612
+ if (outer.tag !== 0x30 || outer.end !== der.length)
1613
+ throw new AuthError('signed current CRL is invalid');
1614
+ const fields = ownershipDerChildren(der, outer);
1615
+ if (fields.length !== 3 || fields[0].tag !== 0x30 || fields[1].tag !== 0x30 || fields[2].tag !== 0x03 || der[fields[2].valueStart] !== 0)
1616
+ throw new AuthError('signed current CRL is invalid');
1617
+ const algorithm = ownershipDerChildren(der, fields[1]);
1618
+ if (algorithm[0]?.tag !== 0x06)
1619
+ throw new AuthError('signed current CRL algorithm is invalid');
1620
+ await verifyCertSignature(issuerCert.spkiBytes, { ...issuerCert, tbsBytes: der.slice(fields[0].start, fields[0].end), signatureBytes: der.slice(fields[2].valueStart + 1, fields[2].end), signatureAlgorithmOid: parseOid(der.slice(algorithm[0].valueStart, algorithm[0].end)) });
1621
+ const body = ownershipDerChildren(der, fields[0]);
1622
+ let index = body[0]?.tag === 0x02 ? 1 : 0;
1623
+ if (body[index++]?.tag !== 0x30 || body[index]?.tag !== 0x30 || !arraysEqual(der.slice(body[index].start, body[index].end), issuerCert.subjectRaw))
1624
+ throw new AuthError('signed current CRL issuer is invalid');
1625
+ index++;
1626
+ const thisUpdate = body[index++], nextUpdate = body[index++];
1627
+ const now = Date.now() / 1000;
1628
+ if (!thisUpdate || !nextUpdate || ![0x17, 0x18].includes(thisUpdate.tag) || ![0x17, 0x18].includes(nextUpdate.tag) || !Number.isFinite(parseAsn1Time(der, thisUpdate.start)) || parseAsn1Time(der, thisUpdate.start) > now || !Number.isFinite(parseAsn1Time(der, nextUpdate.start)) || now >= parseAsn1Time(der, nextUpdate.start))
1629
+ throw new AuthError('signed current CRL time window is invalid');
1630
+ const revokedSerials = new Set();
1631
+ const revoked = body[index];
1632
+ if (revoked?.tag === 0x30)
1633
+ for (const entry of ownershipDerChildren(der, revoked)) {
1634
+ const serial = ownershipDerChildren(der, entry)[0];
1635
+ if (entry.tag !== 0x30 || serial?.tag !== 0x02)
1636
+ throw new AuthError('signed current CRL revoked entry is invalid');
1637
+ revokedSerials.add(Array.from(der.slice(serial.valueStart, serial.end), (byte) => byte.toString(16).padStart(2, '0')).join('').replace(/^0+/, '') || '0');
1638
+ }
1639
+ return { revokedSerials, nextRefreshAt: parseAsn1Time(der, nextUpdate.start) };
1640
+ }
1276
1641
  // 简化 CRL 验证:JSON 格式 CRL
1277
1642
  // payload 包含 revoked_serials 数组和签名
1278
1643
  const revokedList = payload.revoked_serials;
@@ -1335,12 +1700,34 @@ export class AuthFlow {
1335
1700
  return fresh.status;
1336
1701
  }
1337
1702
  /** 从 Gateway PKI 端点获取并验证 OCSP 状态 */
1338
- async _fetchGatewayOcspStatus(gatewayUrl, authCert, issuerCert) {
1703
+ async _fetchGatewayOcspStatus(gatewayUrl, authCert, issuerCert, strictCurrent = false) {
1339
1704
  const serialHex = authCert.serialHex.toLowerCase();
1340
1705
  const url = gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${serialHex}`);
1341
- const payload = await this._fetchJson(url);
1706
+ const payload = await this._fetchJson(url, strictCurrent);
1342
1707
  const status = String(payload.status ?? '');
1343
1708
  const ocspB64 = String(payload.ocsp_response ?? '');
1709
+ if (strictCurrent) {
1710
+ if (payload.ocsp_response === undefined || payload.ocsp_response === '')
1711
+ throw new StateError('OCSP signed current evidence unavailable', { stringCode: 'unavailable', retryable: true });
1712
+ if (typeof payload.ocsp_response !== 'string' || !isStrictBase64(ocspB64))
1713
+ throw new ValidationError('ownership OCSP Base64 is invalid', { stringCode: 'invalid' });
1714
+ let verifiedStatus;
1715
+ try {
1716
+ verifiedStatus = await verifyOwnershipOcsp(base64ToUint8(ocspB64), authCert, issuerCert);
1717
+ }
1718
+ catch (error) {
1719
+ if (error instanceof StateError || error instanceof ValidationError)
1720
+ throw error;
1721
+ throw new ValidationError(error instanceof AuthError ? error.message : 'ownership OCSP evidence is invalid', { stringCode: 'invalid' });
1722
+ }
1723
+ if (payload.status !== undefined && payload.status !== verifiedStatus)
1724
+ throw new ValidationError('ownership OCSP status mismatch', { stringCode: 'invalid' });
1725
+ if (verifiedStatus === 'revoked')
1726
+ throw new ValidationError('ownership certificate is revoked', { stringCode: 'invalid' });
1727
+ if (verifiedStatus !== 'good')
1728
+ throw new StateError('ownership certificate revocation status is unknown', { stringCode: 'unavailable', retryable: true });
1729
+ return { status: verifiedStatus, nextRefreshAt: Date.now() / 1000 };
1730
+ }
1344
1731
  if (!ocspB64)
1345
1732
  throw new AuthError('gateway OCSP endpoint returned no ocsp_response');
1346
1733
  // 简化 OCSP 验证:检查 JSON 响应中的签名