@agentunion/fastaun 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 (122) 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 +15 -1
  48. package/dist/agent-md-schema.js +87 -24
  49. package/dist/agent-md-schema.js.map +1 -1
  50. package/dist/agent-md.d.ts +54 -2
  51. package/dist/agent-md.js +637 -53
  52. package/dist/agent-md.js.map +1 -1
  53. package/dist/aid-store.d.ts +38 -59
  54. package/dist/aid-store.js +300 -265
  55. package/dist/aid-store.js.map +1 -1
  56. package/dist/aid.js +30 -11
  57. package/dist/aid.js.map +1 -1
  58. package/dist/auth.d.ts +6 -1
  59. package/dist/auth.js +500 -15
  60. package/dist/auth.js.map +1 -1
  61. package/dist/cert-utils.d.ts +44 -2
  62. package/dist/cert-utils.js +149 -49
  63. package/dist/cert-utils.js.map +1 -1
  64. package/dist/client/delivery.d.ts +11 -1
  65. package/dist/client/delivery.js +321 -62
  66. package/dist/client/delivery.js.map +1 -1
  67. package/dist/client/rpc-pipeline.d.ts +1 -0
  68. package/dist/client/rpc-pipeline.js +142 -82
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +1 -0
  71. package/dist/client/v2-e2ee.js +166 -96
  72. package/dist/client/v2-e2ee.js.map +1 -1
  73. package/dist/client.d.ts +84 -1
  74. package/dist/client.js +780 -30
  75. package/dist/client.js.map +1 -1
  76. package/dist/errors.js +4 -3
  77. package/dist/errors.js.map +1 -1
  78. package/dist/events.d.ts +9 -3
  79. package/dist/events.js +69 -11
  80. package/dist/events.js.map +1 -1
  81. package/dist/facades.d.ts +83 -3
  82. package/dist/facades.js +100 -6
  83. package/dist/facades.js.map +1 -1
  84. package/dist/index.d.ts +9 -4
  85. package/dist/index.js +3 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/keystore/aid-db.d.ts +5 -0
  88. package/dist/keystore/aid-db.js +57 -0
  89. package/dist/keystore/aid-db.js.map +1 -1
  90. package/dist/keystore/index.d.ts +8 -0
  91. package/dist/keystore/local-identity-store.js +10 -3
  92. package/dist/keystore/local-identity-store.js.map +1 -1
  93. package/dist/keystore/local-token-store.d.ts +6 -1
  94. package/dist/keystore/local-token-store.js +24 -1
  95. package/dist/keystore/local-token-store.js.map +1 -1
  96. package/dist/org-protocol.d.ts +98 -0
  97. package/dist/org-protocol.js +1753 -0
  98. package/dist/org-protocol.js.map +1 -0
  99. package/dist/ownership-proof.d.ts +82 -5
  100. package/dist/ownership-proof.js +327 -31
  101. package/dist/ownership-proof.js.map +1 -1
  102. package/dist/ownership-v21.d.ts +196 -0
  103. package/dist/ownership-v21.js +712 -0
  104. package/dist/ownership-v21.js.map +1 -0
  105. package/dist/ownership-workflow.d.ts +87 -0
  106. package/dist/ownership-workflow.js +1182 -0
  107. package/dist/ownership-workflow.js.map +1 -0
  108. package/dist/seq-tracker.d.ts +1 -0
  109. package/dist/seq-tracker.js +10 -1
  110. package/dist/seq-tracker.js.map +1 -1
  111. package/dist/tools/cross-sdk-agent.d.ts +90 -1
  112. package/dist/tools/cross-sdk-agent.js +441 -7
  113. package/dist/tools/cross-sdk-agent.js.map +1 -1
  114. package/dist/transport.d.ts +8 -0
  115. package/dist/transport.js +118 -4
  116. package/dist/transport.js.map +1 -1
  117. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  118. package/dist/v2/e2ee/decrypt.js +9 -1
  119. package/dist/v2/e2ee/decrypt.js.map +1 -1
  120. package/dist/version.d.ts +1 -1
  121. package/dist/version.js +1 -1
  122. package/package.json +1 -1
package/dist/auth.js CHANGED
@@ -16,10 +16,12 @@ import * as http from 'node:http';
16
16
  import * as https from 'node:https';
17
17
  import * as path from 'node:path';
18
18
  import { URL, fileURLToPath } from 'node:url';
19
- import { AuthError, StateError, ValidationError, mapRemoteError } from './errors.js';
19
+ import { AuthError, StateError, ValidationError, AUNError, mapRemoteError } from './errors.js';
20
20
  import { shortRpcViaNetworkActor } from './transport.js';
21
21
  import { isJsonObject, } from './types.js';
22
22
  import { VERSION as AUN_SDK_VERSION } from './version.js';
23
+ import { certFingerprint, isStrictBase64 } from './cert-utils.js';
24
+ import { validateAgentMdCertificate } from './agent-md-schema.js';
23
25
  const _noopLogger = {
24
26
  error: () => { },
25
27
  warn: () => { },
@@ -182,6 +184,262 @@ function _certSubjectCN(cert) {
182
184
  function _certDer(cert) {
183
185
  return Buffer.from(cert.raw);
184
186
  }
187
+ function _ownershipDer(buffer, offset = 0) {
188
+ let cursor = offset + 2;
189
+ if (cursor > buffer.length || (buffer[offset] & 31) === 31)
190
+ throw new ValidationError('invalid OCSP DER', { stringCode: 'invalid' });
191
+ let length = buffer[offset + 1];
192
+ if (length & 128) {
193
+ const count = length & 127;
194
+ length = 0;
195
+ if (!count || count > 4 || cursor + count > buffer.length || buffer[cursor] === 0)
196
+ throw new ValidationError('invalid OCSP DER length', { stringCode: 'invalid' });
197
+ for (let i = 0; i < count; i++)
198
+ length = length * 256 + buffer[cursor++];
199
+ if (length < 128)
200
+ throw new ValidationError('non-canonical OCSP DER length', { stringCode: 'invalid' });
201
+ }
202
+ if (cursor + length > buffer.length)
203
+ throw new ValidationError('truncated OCSP DER', { stringCode: 'invalid' });
204
+ return { tag: buffer[offset], raw: buffer.subarray(offset, cursor + length), content: buffer.subarray(cursor, cursor + length) };
205
+ }
206
+ function _ownershipDerChildren(value, tag) {
207
+ if (!value)
208
+ throw new StateError('OCSP field is missing', { stringCode: 'unavailable', retryable: true });
209
+ if (tag !== undefined && value.tag !== tag)
210
+ throw new ValidationError('unexpected OCSP DER field', { stringCode: 'invalid' });
211
+ const children = [];
212
+ for (let offset = 0; offset < value.content.length;) {
213
+ const child = _ownershipDer(value.content, offset);
214
+ children.push(child);
215
+ offset += child.raw.length;
216
+ }
217
+ return children;
218
+ }
219
+ function _ownershipCertParts(cert) {
220
+ const tbs = _ownershipDerChildren(_ownershipDerChildren(_ownershipDer(cert.raw), 0x30)[0], 0x30);
221
+ const version = tbs[0]?.tag === 0xa0 ? 1 : 0;
222
+ const spki = _ownershipDerChildren(tbs[version + 5], 0x30);
223
+ if (spki[1]?.tag !== 3 || spki[1].content[0] !== 0)
224
+ throw new ValidationError('invalid issuer public key', { stringCode: 'invalid' });
225
+ return { subject: tbs[version + 4].raw, key: spki[1].content.subarray(1) };
226
+ }
227
+ function _ownershipCertConstraints(cert) {
228
+ const tbs = _ownershipDerChildren(_ownershipDerChildren(_ownershipDer(cert.raw), 0x30)[0], 0x30);
229
+ const extensions = tbs.find((node) => node.tag === 0xa3);
230
+ if (!extensions)
231
+ return { basicPresent: false, ca: false, keyUsagePresent: false, keyCertSign: false, nameConstraints: false };
232
+ const extensionSequence = _ownershipDerChildren(extensions)[0];
233
+ if (!extensionSequence || extensionSequence.tag !== 0x30)
234
+ throw new ValidationError('ownership certificate extensions are invalid', { stringCode: 'invalid' });
235
+ const values = _ownershipDerChildren(extensionSequence, 0x30);
236
+ let ca = false;
237
+ let basicPresent = false;
238
+ let keyCertSign = false;
239
+ let keyUsagePresent = false;
240
+ let pathLen;
241
+ let nameConstraints = false;
242
+ const seen = new Set();
243
+ for (const extension of values) {
244
+ const fields = _ownershipDerChildren(extension, 0x30);
245
+ const oid = fields[0]?.tag === 6 ? fields[0].content.toString('hex') : '';
246
+ if (!oid || seen.has(oid) || ![2, 3].includes(fields.length) || fields[fields.length - 1].tag !== 4)
247
+ throw new ValidationError('ownership certificate extension is invalid', { stringCode: 'invalid' });
248
+ seen.add(oid);
249
+ const critical = fields.length === 3 && fields[1].tag === 1 && fields[1].content.equals(Buffer.from([0xff]));
250
+ if (fields.length === 3 && (fields[1].tag !== 1 || fields[1].content.length !== 1 || ![0, 0xff].includes(fields[1].content[0])))
251
+ throw new ValidationError('ownership certificate extension critical flag is invalid', { stringCode: 'invalid' });
252
+ const value = _ownershipDer(fields[fields.length - 1].content);
253
+ if (value.raw.length !== fields[fields.length - 1].content.length)
254
+ throw new ValidationError('ownership certificate extension value has trailing data', { stringCode: 'invalid' });
255
+ if (oid === '551d13') {
256
+ basicPresent = true;
257
+ const basic = _ownershipDerChildren(value, 0x30);
258
+ if (basic.length > 2)
259
+ throw new ValidationError('ownership BasicConstraints is invalid', { stringCode: 'invalid' });
260
+ let offset = 0;
261
+ if (basic[offset]?.tag === 1) {
262
+ if (basic[offset].content.length !== 1)
263
+ throw new ValidationError('ownership BasicConstraints is invalid', { stringCode: 'invalid' });
264
+ ca = basic[offset].content[0] === 0xff;
265
+ offset++;
266
+ }
267
+ if (basic[offset]) {
268
+ const bytes = basic[offset].content;
269
+ if (basic[offset].tag !== 2 || !bytes.length || bytes[0] & 0x80 || bytes.length > 1 && bytes[0] === 0 && !(bytes[1] & 0x80))
270
+ throw new ValidationError('ownership BasicConstraints pathLen is invalid', { stringCode: 'invalid' });
271
+ const parsed = BigInt(`0x${bytes.toString('hex')}`);
272
+ if (parsed > BigInt(Number.MAX_SAFE_INTEGER) || !ca)
273
+ throw new ValidationError('ownership BasicConstraints pathLen is invalid', { stringCode: 'invalid' });
274
+ pathLen = Number(parsed);
275
+ }
276
+ }
277
+ else if (oid === '551d0f') {
278
+ keyUsagePresent = true;
279
+ if (value.tag !== 3 || value.content.length < 2 || value.content[0] > 7)
280
+ throw new ValidationError('ownership keyUsage is invalid', { stringCode: 'invalid' });
281
+ keyCertSign = Boolean(value.content[1] & 0x04);
282
+ }
283
+ else if (oid === '551d1e') {
284
+ nameConstraints = true;
285
+ }
286
+ else if (critical) {
287
+ throw new ValidationError('ownership certificate has an unsupported critical extension', { stringCode: 'invalid' });
288
+ }
289
+ }
290
+ if (nameConstraints)
291
+ throw new ValidationError('ownership certificate NameConstraints are unsupported', { stringCode: 'invalid' });
292
+ return { basicPresent, ca, pathLen, keyUsagePresent, keyCertSign, nameConstraints };
293
+ }
294
+ function _ownershipOcspTime(node) {
295
+ if (!node)
296
+ throw new StateError('OCSP time is missing', { stringCode: 'unavailable', retryable: true });
297
+ const text = node.content.toString('ascii');
298
+ const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/.exec(text);
299
+ if (node.tag !== 24 || !match)
300
+ throw new ValidationError('invalid OCSP time', { stringCode: 'invalid' });
301
+ const date = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6])));
302
+ if (date.toISOString().replace(/[-:T]/g, '').replace('.000', '') !== text)
303
+ throw new ValidationError('invalid OCSP calendar time', { stringCode: 'invalid' });
304
+ return date.getTime() / 1000;
305
+ }
306
+ function _strictCrlRevokedSerials(pem, issuer, now) {
307
+ const match = /^-----BEGIN X509 CRL-----\s+([A-Za-z0-9+/=\s]+)-----END X509 CRL-----\s*$/.exec(pem);
308
+ if (!match || !isStrictBase64(match[1].replace(/\s/g, '')))
309
+ throw new AuthError('service event signed CRL is invalid');
310
+ const der = Buffer.from(match[1].replace(/\s/g, ''), 'base64');
311
+ const outer = _ownershipDer(der);
312
+ if (outer.raw.length !== der.length)
313
+ throw new AuthError('service event signed CRL has trailing data');
314
+ const fields = _ownershipDerChildren(outer, 0x30);
315
+ if (fields.length !== 3 || fields[2].tag !== 3 || fields[2].content[0] !== 0)
316
+ throw new AuthError('service event signed CRL is incomplete');
317
+ const tbs = _ownershipDerChildren(fields[0], 0x30);
318
+ const offset = tbs[0]?.tag === 2 ? 1 : 0;
319
+ const algorithm = _ownershipDerChildren(fields[1], 0x30)[0];
320
+ const oid = algorithm?.tag === 6 ? algorithm.content.toString('hex') : '';
321
+ const hash = { '2a8648ce3d040302': 'sha256', '2a8648ce3d040303': 'sha384', '2a864886f70d01010b': 'sha256', '2a864886f70d01010c': 'sha384', '2b6570': null }[oid];
322
+ if (hash === undefined || !tbs[offset]?.raw.equals(fields[1].raw) || !crypto.verify(hash, fields[0].raw, issuer.publicKey, fields[2].content.subarray(1)))
323
+ throw new AuthError('service event CRL signature is invalid');
324
+ if (!tbs[offset + 1]?.raw.equals(_ownershipCertParts(issuer).subject))
325
+ throw new AuthError('service event CRL issuer is invalid');
326
+ const crlTime = (node) => {
327
+ if (!node || ![23, 24].includes(node.tag))
328
+ throw new AuthError('service event CRL time is invalid');
329
+ const value = node.content.toString('ascii');
330
+ const digits = node.tag === 23 ? /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/.exec(value) : /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/.exec(value);
331
+ if (!digits)
332
+ throw new AuthError('service event CRL time is invalid');
333
+ const year = node.tag === 23 ? (Number(digits[1]) >= 50 ? 1900 : 2000) + Number(digits[1]) : Number(digits[1]);
334
+ const date = new Date(Date.UTC(year, Number(digits[2]) - 1, Number(digits[3]), Number(digits[4]), Number(digits[5]), Number(digits[6])));
335
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() + 1 !== Number(digits[2]) || date.getUTCDate() !== Number(digits[3]) || date.getUTCHours() !== Number(digits[4]) || date.getUTCMinutes() !== Number(digits[5]) || date.getUTCSeconds() !== Number(digits[6]))
336
+ throw new AuthError('service event CRL time is invalid');
337
+ return date.getTime() / 1000;
338
+ };
339
+ const thisUpdate = crlTime(tbs[offset + 2]);
340
+ const nextUpdate = crlTime(tbs[offset + 3]);
341
+ if (thisUpdate > now + 5 || nextUpdate <= now || nextUpdate <= thisUpdate)
342
+ throw new AuthError('service event CRL is not current');
343
+ const revoked = new Set();
344
+ if (tbs[offset + 4]?.tag === 0x30)
345
+ for (const entry of _ownershipDerChildren(tbs[offset + 4], 0x30)) {
346
+ const serial = _ownershipDerChildren(entry, 0x30)[0];
347
+ if (!serial || serial.tag !== 2)
348
+ throw new AuthError('service event CRL entry is invalid');
349
+ revoked.add(serial.content.toString('hex').replace(/^0+/, '') || '0');
350
+ }
351
+ return revoked;
352
+ }
353
+ function _verifyOwnershipOcsp(der, certificate, issuer, now) {
354
+ const outer = _ownershipDer(der);
355
+ if (outer.raw.length !== der.length)
356
+ throw new ValidationError('trailing OCSP data', { stringCode: 'invalid' });
357
+ const fields = _ownershipDerChildren(outer, 0x30);
358
+ if (!fields[0])
359
+ throw new StateError('OCSP response status is missing', { stringCode: 'unavailable', retryable: true });
360
+ if (fields[0].tag !== 10 || fields[0].content.length !== 1)
361
+ throw new ValidationError('invalid OCSP response status', { stringCode: 'invalid' });
362
+ if (!fields[0].content.equals(Buffer.from([0])) || !fields[1])
363
+ throw new StateError('OCSP response is not successful', { stringCode: 'unavailable', retryable: true });
364
+ if (fields.length !== 2 || fields[1].tag !== 0xa0)
365
+ throw new ValidationError('invalid OCSP response fields', { stringCode: 'invalid' });
366
+ const response = _ownershipDerChildren(_ownershipDerChildren(fields[1])[0], 0x30);
367
+ if (response.length < 2)
368
+ throw new StateError('OCSP Basic response is unavailable', { stringCode: 'unavailable', retryable: true });
369
+ if (response.length !== 2 || response[0].tag !== 6 || response[1].tag !== 4)
370
+ throw new ValidationError('invalid OCSP Basic response', { stringCode: 'invalid' });
371
+ if (response[0].content.toString('hex') !== '2b0601050507300101')
372
+ throw new StateError('unsupported OCSP response type', { stringCode: 'unavailable', retryable: true });
373
+ const basicRoot = _ownershipDer(response[1].content);
374
+ if (basicRoot.raw.length !== response[1].content.length)
375
+ throw new ValidationError('invalid OCSP Basic response length', { stringCode: 'invalid' });
376
+ const basic = _ownershipDerChildren(basicRoot, 0x30);
377
+ if (basic.length < 3 || basic[2].content.length <= 1)
378
+ throw new StateError('OCSP signature is missing', { stringCode: 'unavailable', retryable: true });
379
+ if (basic.length > 4 || basic[2].tag !== 3 || basic[2].content[0] !== 0)
380
+ throw new ValidationError('invalid OCSP signature field', { stringCode: 'invalid' });
381
+ if (basic.length === 4) {
382
+ const certs = _ownershipDerChildren(basic[3], 0xa0);
383
+ if (certs.length !== 1 || certs[0].tag !== 0x30)
384
+ throw new ValidationError('invalid OCSP embedded certificates', { stringCode: 'invalid' });
385
+ const embedded = _ownershipDerChildren(certs[0], 0x30);
386
+ if (embedded.length !== 1 || !embedded[0].raw.equals(issuer.raw))
387
+ throw new ValidationError('OCSP embedded certificate is not the verified issuer', { stringCode: 'invalid' });
388
+ }
389
+ const algorithms = { '2a8648ce3d040302': 'sha256', '2a8648ce3d040303': 'sha384', '2a864886f70d01010b': 'sha256', '2a864886f70d01010c': 'sha384', '2b6570': null };
390
+ const algorithm = _ownershipDerChildren(basic[1], 0x30)[0];
391
+ const oid = algorithm?.tag === 6 ? algorithm.content.toString('hex') : '';
392
+ if (!(oid in algorithms))
393
+ throw new StateError('unsupported OCSP signature algorithm', { stringCode: 'unavailable', retryable: true });
394
+ if (!crypto.verify(algorithms[oid], basic[0].raw, issuer.publicKey, basic[2].content.subarray(1)))
395
+ throw new ValidationError('OCSP signature verification failed', { stringCode: 'invalid' });
396
+ const tbs = _ownershipDerChildren(basic[0], 0x30);
397
+ let offset = tbs[0]?.tag === 0xa0 ? 1 : 0;
398
+ const responder = tbs[offset++];
399
+ const issuerParts = _ownershipCertParts(issuer);
400
+ const responderValid = responder?.tag === 0xa1 ? responder.content.equals(issuerParts.subject) : responder?.tag === 0xa2 && _ownershipDerChildren(responder)[0]?.tag === 4 && _ownershipDerChildren(responder)[0].content.equals(crypto.createHash('sha1').update(issuerParts.key).digest());
401
+ if (!responderValid)
402
+ throw new ValidationError('OCSP responder is not the verified issuer', { stringCode: 'invalid' });
403
+ const producedAt = _ownershipOcspTime(tbs[offset++]);
404
+ if (producedAt > now + 5 || producedAt < now - 90)
405
+ throw new StateError('OCSP response is not current', { stringCode: 'unavailable', retryable: true });
406
+ const responses = _ownershipDerChildren(tbs[offset++], 0x30);
407
+ let status;
408
+ for (const entry of responses) {
409
+ const single = _ownershipDerChildren(entry, 0x30);
410
+ if (single.length < 4)
411
+ throw new StateError('OCSP update bounds are missing', { stringCode: 'unavailable', retryable: true });
412
+ const id = _ownershipDerChildren(single[0], 0x30);
413
+ if (id.length !== 4 || id[1].tag !== 4 || id[2].tag !== 4 || id[3].tag !== 2)
414
+ throw new ValidationError('invalid OCSP CertID', { stringCode: 'invalid' });
415
+ const hashOid = _ownershipDerChildren(id[0], 0x30)[0];
416
+ const hashName = hashOid?.tag === 6 ? { '2b0e03021a': 'sha1', '608648016503040201': 'sha256' }[hashOid.content.toString('hex')] : undefined;
417
+ if (!hashName)
418
+ throw new StateError('unsupported OCSP issuer hash', { stringCode: 'unavailable', retryable: true });
419
+ const serial = id[3].content.toString('hex').replace(/^0+/, '') || '0';
420
+ if (serial !== certificate.serialNumber.toLowerCase().replace(/^0+/, ''))
421
+ continue;
422
+ if (status !== undefined || !id[1].content.equals(crypto.createHash(hashName).update(issuerParts.subject).digest()) || !id[2].content.equals(crypto.createHash(hashName).update(issuerParts.key).digest()))
423
+ throw new ValidationError('OCSP CertID issuer mismatch', { stringCode: 'invalid' });
424
+ const thisUpdate = _ownershipOcspTime(single[2]);
425
+ if (single[3].tag !== 0xa0)
426
+ throw new StateError('OCSP nextUpdate is missing', { stringCode: 'unavailable', retryable: true });
427
+ const nextUpdate = _ownershipOcspTime(_ownershipDerChildren(single[3])[0]);
428
+ if (thisUpdate > now + 5 || thisUpdate < now - 30 || nextUpdate <= now || nextUpdate <= thisUpdate)
429
+ throw new StateError('OCSP validity window is not current', { stringCode: 'unavailable', retryable: true });
430
+ if (single[1].tag === 0x80 && single[1].content.length === 0)
431
+ status = 'good';
432
+ else if (single[1].tag === 0xa1)
433
+ status = 'revoked';
434
+ else if (single[1].tag === 0x82 && single[1].content.length === 0)
435
+ status = 'unknown';
436
+ else
437
+ throw new ValidationError('invalid OCSP certificate status', { stringCode: 'invalid' });
438
+ }
439
+ if (!status)
440
+ throw new ValidationError('OCSP does not match the requested certificate', { stringCode: 'invalid' });
441
+ return status;
442
+ }
185
443
  // ── HTTP 辅助 ─────────────────────────────────────────────────
186
444
  /** 将 WebSocket URL 转换为 HTTP URL + 路径 */
187
445
  function _gatewayHttpUrl(gatewayUrl, urlPath) {
@@ -663,13 +921,36 @@ export class AuthFlow {
663
921
  * 验证对端证书:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
664
922
  * 用于 E2EE 握手中验证通信对端的身份证书。
665
923
  */
666
- async verifyPeerCertificate(gatewayUrl, certPem, expectedAid) {
924
+ async verifyPeerCertificate(gatewayUrl, certPem, expectedAid, signedAt, strictRevocation = false) {
667
925
  const tStart = Date.now();
668
926
  this._logger.debug(`verifyPeerCertificate enter: aid=${expectedAid}, gateway=${gatewayUrl}`);
669
927
  try {
670
928
  const cert = _loadX509(certPem);
929
+ if (signedAt !== undefined) {
930
+ _ensureCertTimeValid(cert, 'peer certificate', signedAt * 1000);
931
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid, signedAt);
932
+ if (_certSubjectCN(cert) !== expectedAid)
933
+ throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}`);
934
+ return;
935
+ }
671
936
  const nowMs = Date.now();
672
937
  _ensureCertTimeValid(cert, 'peer certificate', nowMs);
938
+ if (strictRevocation) {
939
+ const chain = (await this._fetchGatewayCaChain(gatewayUrl, expectedAid, true)).map(_loadX509);
940
+ if (!chain.length)
941
+ throw new AuthError('service event certificate chain is unavailable');
942
+ const current = Math.trunc(Date.now() / 1000);
943
+ if ([cert, ...chain].some((item) => current < Math.trunc(Date.parse(item.validFrom) / 1000) || current > Math.trunc(Date.parse(item.validTo) / 1000)))
944
+ throw new AuthError('service event certificate is outside its validity window');
945
+ await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid, undefined, true, chain);
946
+ const crl = await this._fetchGatewayCrl(gatewayUrl, chain[0], true);
947
+ if (crl.revokedSerials.has(_certSerialHex(cert)))
948
+ throw new AuthError('service event certificate has been revoked');
949
+ await this._fetchGatewayOcspStatus(gatewayUrl, cert, chain[0], true);
950
+ if (_certSubjectCN(cert) !== expectedAid)
951
+ throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}`);
952
+ return;
953
+ }
673
954
  await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
674
955
  try {
675
956
  await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
@@ -701,6 +982,186 @@ export class AuthFlow {
701
982
  throw err;
702
983
  }
703
984
  }
985
+ async resolveCurrentEntityCertificate(gatewayUrl, aid, type) {
986
+ try {
987
+ const query = new URLSearchParams({ consistency: 'authoritative', ...(type === 'group' ? { key_purpose: 'group_identity' } : {}) });
988
+ const response = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}?${query}`));
989
+ if (response.status !== 200 || !response.body.trim())
990
+ throw new StateError('entity current certificate is unavailable', { stringCode: 'unavailable', retryable: true });
991
+ let fingerprint;
992
+ try {
993
+ new crypto.X509Certificate(response.body);
994
+ fingerprint = certFingerprint(response.body);
995
+ }
996
+ catch {
997
+ throw new ValidationError('entity current certificate is invalid', { stringCode: 'invalid' });
998
+ }
999
+ return this.resolveOwnershipCertificate(gatewayUrl, aid, fingerprint, type === 'group' ? 'group_identity' : 'signing');
1000
+ }
1001
+ catch (error) {
1002
+ if (error instanceof AUNError && error.stringCode)
1003
+ throw error;
1004
+ throw new StateError(error instanceof Error ? error.message : String(error), { stringCode: 'unavailable', retryable: true });
1005
+ }
1006
+ }
1007
+ async resolveOwnershipCertificate(gatewayUrl, aid, fingerprint, purpose) {
1008
+ const began = Date.now();
1009
+ const unavailable = (reason) => new StateError(reason, { stringCode: 'unavailable', retryable: true });
1010
+ try {
1011
+ if (!/^[a-z0-9_][a-z0-9_-]*(?:\.[a-z0-9_][a-z0-9_-]*)+$/.test(aid) || !/^sha256:[0-9a-f]{64}$/.test(fingerprint))
1012
+ throw new ValidationError('ownership certificate query requires exact AID and full fingerprint', { stringCode: 'invalid' });
1013
+ const query = new URLSearchParams({ cert_fingerprint: fingerprint, consistency: 'authoritative' });
1014
+ if (purpose === 'group_identity')
1015
+ query.set('key_purpose', purpose);
1016
+ const response = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}?${query}`));
1017
+ if (response.status !== 200)
1018
+ throw unavailable(`ownership certificate HTTP ${response.status}`);
1019
+ const headers = response.headers;
1020
+ const state = headers['x-aun-cert-lifecycle-state'];
1021
+ const observedText = headers['x-aun-cert-observed-at'] ?? '';
1022
+ const observed = Date.parse(observedText) / 1000;
1023
+ const now = Date.now() / 1000;
1024
+ if (headers['x-aun-cert-consistency'] !== 'authoritative' || typeof state !== 'string' || !state || !headers['x-aun-cert-fingerprint'] || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/.test(observedText) || !Number.isFinite(observed) || observed > now + 5 || observed < now - 30)
1025
+ throw unavailable('ownership certificate authoritative current metadata is unavailable');
1026
+ if (state !== 'active_signing' || headers['x-aun-cert-fingerprint'] !== fingerprint)
1027
+ throw new ValidationError('ownership certificate lifecycle or fingerprint is invalid', { stringCode: 'invalid' });
1028
+ if (!response.body.trim())
1029
+ throw unavailable('ownership certificate is missing');
1030
+ let cert;
1031
+ try {
1032
+ cert = new crypto.X509Certificate(response.body);
1033
+ }
1034
+ catch {
1035
+ throw new ValidationError('ownership certificate PEM is invalid', { stringCode: 'invalid' });
1036
+ }
1037
+ if (cert.subject.match(/(?:^|\n)CN=([^\n]+)/)?.[1] !== aid)
1038
+ throw new ValidationError('ownership certificate AID mismatch', { stringCode: 'invalid' });
1039
+ _ownershipCertConstraints(cert);
1040
+ validateAgentMdCertificate(response.body, aid, Math.trunc(now), { now: Math.trunc(now), requireCurrentValidity: true });
1041
+ if (certFingerprint(response.body) !== fingerprint)
1042
+ 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
+ }
1053
+ if (chain[0] && !chain[0].ca && _certSubjectCN(chain[0]) === aid)
1054
+ chain.shift();
1055
+ if (!chain.length)
1056
+ 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
+ let roots;
1068
+ try {
1069
+ roots = this._loadTrustedRoots();
1070
+ }
1071
+ catch {
1072
+ throw unavailable('ownership trust anchors are unavailable');
1073
+ }
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' });
1076
+ const ocspResponse = await this._fetchOwnershipPki(_gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${cert.serialNumber.toLowerCase()}`));
1077
+ if (ocspResponse.status !== 200)
1078
+ throw unavailable('ownership OCSP is unavailable');
1079
+ let ocsp;
1080
+ if (!ocspResponse.body.trim())
1081
+ throw unavailable('ownership OCSP response is missing');
1082
+ try {
1083
+ ocsp = JSON.parse(ocspResponse.body);
1084
+ }
1085
+ catch {
1086
+ throw new ValidationError('ownership OCSP response is invalid', { stringCode: 'invalid' });
1087
+ }
1088
+ if (!ocsp || typeof ocsp !== 'object' || Array.isArray(ocsp))
1089
+ throw new ValidationError('ownership OCSP response must be an object', { stringCode: 'invalid' });
1090
+ if (ocsp.ocsp_response === undefined || ocsp.ocsp_response === '')
1091
+ throw unavailable('signed ownership OCSP is missing');
1092
+ if (typeof ocsp.ocsp_response !== 'string' || !isStrictBase64(ocsp.ocsp_response))
1093
+ throw new ValidationError('ownership OCSP Base64 is invalid', { stringCode: 'invalid' });
1094
+ let status;
1095
+ try {
1096
+ status = _verifyOwnershipOcsp(Buffer.from(ocsp.ocsp_response, 'base64'), cert, chain[0], Date.now() / 1000);
1097
+ }
1098
+ catch (error) {
1099
+ if (error instanceof ValidationError || error instanceof StateError)
1100
+ throw error;
1101
+ throw new ValidationError('ownership OCSP evidence is invalid', { stringCode: 'invalid' });
1102
+ }
1103
+ if (ocsp.status !== undefined && ocsp.status !== status)
1104
+ throw new ValidationError('ownership OCSP status mismatch', { stringCode: 'invalid' });
1105
+ if (status === 'revoked')
1106
+ throw new ValidationError('ownership certificate is revoked', { stringCode: 'invalid' });
1107
+ if (status !== 'good')
1108
+ throw unavailable('ownership certificate revocation status is unknown');
1109
+ const finalNow = Date.now();
1110
+ if ([cert, ...chain].some((item) => finalNow < Date.parse(item.validFrom) || finalNow > Date.parse(item.validTo)))
1111
+ throw new ValidationError('ownership certificate chain is invalid', { stringCode: 'invalid' });
1112
+ if (finalNow - began > 30_000 || finalNow / 1000 - observed > 30)
1113
+ throw unavailable('ownership certificate verification window elapsed');
1114
+ return { cert_pem: response.body, chain_valid: true, state, revoked: false, revocation_checked: true, observed_at: Math.trunc(observed) };
1115
+ }
1116
+ catch (error) {
1117
+ if (error instanceof ValidationError)
1118
+ throw new ValidationError(error.message, { stringCode: 'invalid' });
1119
+ if (error instanceof StateError && error.stringCode)
1120
+ throw error;
1121
+ throw unavailable(error instanceof Error ? error.message : String(error));
1122
+ }
1123
+ }
1124
+ async _fetchOwnershipPki(url) {
1125
+ const fetch = (target, originalHost) => new Promise((resolve, reject) => {
1126
+ const parsed = new URL(target);
1127
+ const module = parsed.protocol === 'https:' ? https : http;
1128
+ const request = module.get(target, { timeout: 5_000, rejectUnauthorized: this._verifySsl,
1129
+ servername: originalHost, headers: { 'Cache-Control': 'no-cache, no-store', ...(originalHost ? { Host: new URL(url).host } : {}) } }, (response) => {
1130
+ const chunks = [];
1131
+ response.on('data', (chunk) => chunks.push(chunk));
1132
+ response.on('error', reject);
1133
+ response.on('end', () => {
1134
+ try {
1135
+ const headers = {};
1136
+ for (const [key, value] of Object.entries(response.headers))
1137
+ if (typeof value === 'string')
1138
+ headers[key] = value;
1139
+ resolve({ status: response.statusCode ?? 0, headers, body: new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(Buffer.concat(chunks)) });
1140
+ }
1141
+ catch (error) {
1142
+ reject(error);
1143
+ }
1144
+ });
1145
+ });
1146
+ request.on('error', reject);
1147
+ request.on('timeout', () => request.destroy(new Error('ownership PKI request timed out')));
1148
+ });
1149
+ try {
1150
+ const result = await fetch(url);
1151
+ if (this._net)
1152
+ void this._net.refreshDnsCacheAfterSuccess(url);
1153
+ return result;
1154
+ }
1155
+ catch (error) {
1156
+ if (!this._net?.isDnsError(error))
1157
+ throw error;
1158
+ const original = new URL(url);
1159
+ const cached = this._net.loadDnsCache(original.hostname);
1160
+ if (!cached)
1161
+ throw error;
1162
+ return fetch(this._net.replaceHostWithIP(url, cached.ip), original.hostname);
1163
+ }
1164
+ }
704
1165
  /** 预置 Gateway CA 链材料,但不标记为已验证。 */
705
1166
  cacheGatewayCaChain(gatewayUrl, caChainPems, chainAid = '') {
706
1167
  const normalized = caChainPems
@@ -1076,22 +1537,22 @@ export class AuthFlow {
1076
1537
  * 4. BasicConstraints 检查
1077
1538
  * 5. 根证书自签 + 受信根锚定
1078
1539
  */
1079
- async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '') {
1080
- const certSerial = _certSerialHex(authCert);
1540
+ async _verifyAuthCertChain(gatewayUrl, authCert, chainAid = '', signedAt, fresh = false, suppliedChain) {
1541
+ const certSerial = _certSerialHex(authCert) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1081
1542
  // 检查缓存
1082
1543
  const cachedAt = this._chainVerifiedCache.get(certSerial);
1083
- if (cachedAt && Date.now() - cachedAt < this._chainCacheTtl) {
1544
+ if (!fresh && cachedAt && Date.now() - cachedAt < this._chainCacheTtl) {
1084
1545
  return;
1085
1546
  }
1086
- const nowMs = Date.now();
1547
+ const nowMs = signedAt === undefined ? Date.now() : signedAt * 1000;
1087
1548
  _ensureCertTimeValid(authCert, 'auth certificate', nowMs);
1088
- const chain = await this._loadGatewayCaChain(gatewayUrl, chainAid);
1549
+ const chain = suppliedChain ?? await this._loadGatewayCaChain(gatewayUrl, chainAid);
1089
1550
  if (!chain.length) {
1090
1551
  throw new AuthError('unable to verify auth certificate chain: missing CA chain');
1091
1552
  }
1092
- const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
1553
+ const cacheKey = (chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
1093
1554
  // 快速路径:CA 链已通过完整验证
1094
- if (this._gatewayCaVerified.get(cacheKey)) {
1555
+ if (!fresh && this._gatewayCaVerified.get(cacheKey)) {
1095
1556
  const issuer = chain[0];
1096
1557
  _ensureCertTimeValid(issuer, 'Issuer CA', nowMs);
1097
1558
  if (!_isCaCert(issuer)) {
@@ -1152,9 +1613,21 @@ export class AuthFlow {
1152
1613
  return cached.map((pem) => _loadX509(pem));
1153
1614
  }
1154
1615
  /** 从 Gateway PKI 端点获取 CA 链 */
1155
- async _fetchGatewayCaChain(gatewayUrl, _chainAid = '') {
1616
+ async _fetchFreshPkiJson(url) {
1617
+ const response = await this._fetchOwnershipPki(url);
1618
+ if (response.status !== 200)
1619
+ throw new AuthError('service event current PKI is unavailable');
1620
+ const payload = JSON.parse(response.body);
1621
+ if (!isJsonObject(payload))
1622
+ throw new AuthError('service event current PKI response is invalid');
1623
+ return payload;
1624
+ }
1625
+ async _fetchGatewayCaChain(gatewayUrl, _chainAid = '', fresh = false) {
1156
1626
  const url = _gatewayHttpUrl(gatewayUrl, '/pki/chain');
1157
- const text = await _fetchText(url, this._verifySsl, this._net);
1627
+ const response = fresh ? await this._fetchOwnershipPki(url) : undefined;
1628
+ if (response && response.status !== 200)
1629
+ throw new AuthError('service event certificate chain is unavailable');
1630
+ const text = response ? response.body : await _fetchText(url, this._verifySsl, this._net);
1158
1631
  return _splitPemBundle(text);
1159
1632
  }
1160
1633
  /**
@@ -1205,13 +1678,15 @@ export class AuthFlow {
1205
1678
  * 并验证 crl_pem 存在性。完整的 CRL 签名验证需要依赖
1206
1679
  * @peculiar/x509 或手动 ASN.1 解析。
1207
1680
  */
1208
- async _fetchGatewayCrl(gatewayUrl, _issuerCert) {
1681
+ async _fetchGatewayCrl(gatewayUrl, _issuerCert, fresh = false) {
1209
1682
  const url = _gatewayHttpUrl(gatewayUrl, '/pki/crl.json');
1210
- const payload = await _fetchJson(url, this._verifySsl, this._net);
1683
+ const payload = fresh ? await this._fetchFreshPkiJson(url) : await _fetchJson(url, this._verifySsl, this._net);
1211
1684
  const crlPem = String(payload.crl_pem || '');
1212
1685
  if (!crlPem) {
1213
1686
  throw new AuthError('gateway CRL endpoint returned no signed CRL');
1214
1687
  }
1688
+ if (fresh)
1689
+ return { revokedSerials: _strictCrlRevokedSerials(crlPem, _issuerCert, Date.now() / 1000), nextRefreshAt: Date.now() };
1215
1690
  // 从 JSON 响应中提取吊销序列号列表
1216
1691
  // Gateway 的 crl.json 同时包含 crl_pem(签名 CRL)和 revoked_serials(方便客户端解析)
1217
1692
  let revokedSerials = new Set();
@@ -1381,12 +1856,22 @@ export class AuthFlow {
1381
1856
  * 这里从 JSON 响应中提取 status 字段,并验证 ocsp_response 存在性。
1382
1857
  * 对于 ocsp_response 的签名验证,实现简化的 DER 解析以提取关键字段。
1383
1858
  */
1384
- async _fetchGatewayOcspStatus(gatewayUrl, authCert, issuerCert) {
1859
+ async _fetchGatewayOcspStatus(gatewayUrl, authCert, issuerCert, strictCurrent = false) {
1385
1860
  const serialHex = _certSerialHex(authCert);
1386
1861
  const url = _gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${serialHex}`);
1387
- const payload = await _fetchJson(url, this._verifySsl, this._net);
1862
+ const payload = strictCurrent ? await this._fetchFreshPkiJson(url) : await _fetchJson(url, this._verifySsl, this._net);
1388
1863
  const status = String(payload.status || '');
1389
1864
  const ocspB64 = String(payload.ocsp_response || '');
1865
+ if (strictCurrent) {
1866
+ if (typeof payload.ocsp_response !== 'string' || !isStrictBase64(ocspB64))
1867
+ throw new AuthError('service event signed current OCSP is unavailable or invalid');
1868
+ const verified = _verifyOwnershipOcsp(Buffer.from(ocspB64, 'base64'), authCert, issuerCert, Date.now() / 1000);
1869
+ if (payload.status !== undefined && payload.status !== verified)
1870
+ throw new AuthError('service event OCSP status mismatch');
1871
+ if (verified !== 'good')
1872
+ throw new AuthError(`service event certificate OCSP status is ${verified}`);
1873
+ return { status: verified, nextRefreshAt: Date.now() };
1874
+ }
1390
1875
  if (!ocspB64) {
1391
1876
  throw new AuthError('gateway OCSP endpoint returned no ocsp_response');
1392
1877
  }