@blamejs/core 0.14.17 → 0.14.19
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/CHANGELOG.md +4 -0
- package/README.md +3 -3
- package/lib/agent-orchestrator.js +10 -4
- package/lib/ai-prompt.js +1 -1
- package/lib/app-shutdown.js +28 -0
- package/lib/archive-read.js +215 -16
- package/lib/archive.js +206 -52
- package/lib/auth/oauth.js +58 -0
- package/lib/auth/oid4vci.js +84 -27
- package/lib/breach-deadline.js +166 -1
- package/lib/cloud-events.js +3 -1
- package/lib/codepoint-class.js +21 -0
- package/lib/db-schema.js +120 -3
- package/lib/db.js +10 -3
- package/lib/error-page.js +88 -8
- package/lib/external-db.js +164 -13
- package/lib/guard-email.js +36 -3
- package/lib/mail-auth.js +554 -55
- package/lib/mail-send-deliver.js +15 -5
- package/lib/mail-sieve.js +2 -1
- package/lib/middleware/ai-act-disclosure.js +88 -19
- package/lib/middleware/asyncapi-serve.js +56 -4
- package/lib/middleware/attach-user.js +45 -10
- package/lib/middleware/body-parser.js +70 -14
- package/lib/middleware/csp-report.js +30 -2
- package/lib/middleware/deny-response.js +22 -7
- package/lib/middleware/openapi-serve.js +56 -4
- package/lib/middleware/scim-server.js +301 -14
- package/lib/openapi-paths-builder.js +105 -29
- package/lib/openapi.js +225 -100
- package/lib/queue-local.js +148 -38
- package/lib/queue.js +41 -11
- package/lib/render.js +21 -3
- package/lib/safe-buffer.js +55 -0
- package/lib/static.js +46 -17
- package/lib/uri-template.js +3 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/archive.js
CHANGED
|
@@ -33,16 +33,20 @@
|
|
|
33
33
|
* null bytes, and `..` segments throw `archive/bad-name`.
|
|
34
34
|
* - No symlink emission — only regular file entries are produced.
|
|
35
35
|
* - SHA3-512 fingerprint via `digest()` for operator integrity logs.
|
|
36
|
+
* - ZIP64 (APPNOTE 6.3.10 §4.3.14 / §4.3.15 / §4.4.8 / §4.5.3) is
|
|
37
|
+
* emitted automatically when an archive exceeds 65535 entries or
|
|
38
|
+
* any entry's compressed/uncompressed size or local-header offset
|
|
39
|
+
* exceeds 4 GiB: the classic field carries the 0xFFFF/0xFFFFFFFF
|
|
40
|
+
* sentinel, a ZIP64 extended-information extra field supplies the
|
|
41
|
+
* 64-bit value, and the ZIP64 EOCD record + locator precede the
|
|
42
|
+
* classic EOCD. Archives below those limits stay classic
|
|
43
|
+
* byte-for-byte. `b.archive.read.zip` reads the produced ZIP64
|
|
44
|
+
* form transparently.
|
|
36
45
|
*
|
|
37
46
|
* Out of scope (v1):
|
|
38
|
-
* - ZIP64 (>4 GiB archives, >65535 files) — `toBuffer` and
|
|
39
|
-
* `toStream` throw `archive/too-many-entries` past the limit;
|
|
40
|
-
* operators at that scale bring their own toolset.
|
|
41
47
|
* - ZIP-native password encryption (broken-by-design); operators
|
|
42
48
|
* wrap the produced bytes via `b.crypto.encryptPacked` for
|
|
43
49
|
* encryption-at-rest.
|
|
44
|
-
* - Reading / extraction — write-only; operators use yauzl or
|
|
45
|
-
* `unzip` for read paths.
|
|
46
50
|
*
|
|
47
51
|
* @card
|
|
48
52
|
* ZIP archive creation primitive.
|
|
@@ -61,6 +65,30 @@ var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
|
61
65
|
var SIG_LFH = 0x04034b50; // local file header
|
|
62
66
|
var SIG_CFH = 0x02014b50; // central directory file header
|
|
63
67
|
var SIG_EOCD = 0x06054b50; // end of central directory
|
|
68
|
+
var SIG_EOCD64 = 0x06064b50; // APPNOTE §4.3.14 ZIP64 EOCD record
|
|
69
|
+
var SIG_EOCD64_LOCATOR = 0x07064b50; // APPNOTE §4.3.15 ZIP64 EOCD locator
|
|
70
|
+
|
|
71
|
+
// ZIP64 sentinels (APPNOTE §4.4 + §4.5.3) — a classic field set to its
|
|
72
|
+
// all-ones value signals that the true value lives in the ZIP64 record /
|
|
73
|
+
// extended-information extra field. 16-bit fields use 0xFFFF, 32-bit
|
|
74
|
+
// fields use 0xFFFFFFFF.
|
|
75
|
+
var ZIP64_U16_SENTINEL = 0xffff;
|
|
76
|
+
var ZIP64_U32_SENTINEL = 0xffffffff;
|
|
77
|
+
// 0xFFFFFFFF as a value boundary: any size/offset > this overflows the
|
|
78
|
+
// classic 32-bit field and must be carried in the ZIP64 extra field.
|
|
79
|
+
var ZIP64_U32_MAX = 0xffffffff;
|
|
80
|
+
// Classic EOCD entry-count field is 16-bit (APPNOTE §4.4.21/§4.4.22);
|
|
81
|
+
// more than 65535 entries forces the ZIP64 EOCD record.
|
|
82
|
+
var ZIP64_MAX_CLASSIC_ENTRIES = 65535;
|
|
83
|
+
// ZIP64 "version needed to extract" — 4.5 (APPNOTE §4.4.3.2).
|
|
84
|
+
var ZIP64_VERSION_NEEDED = 45;
|
|
85
|
+
// ZIP64 extended-information extra field (§4.5.3): 4-byte header
|
|
86
|
+
// (id(2) + dataSize(2)) then up to four fields. Each 64-bit field is
|
|
87
|
+
// 8 bytes; diskStart is a 4-byte dword.
|
|
88
|
+
var ZIP64_EXTRA_HEADER_ID = 0x0001;
|
|
89
|
+
var ZIP64_EXTRA_FIELD_BYTES = 8; // one 64-bit field (uSize / cSize / lfhOffset)
|
|
90
|
+
var ZIP64_EOCD64_BYTES = 56; // §4.3.14 fixed-size record (no extensible-data tail)
|
|
91
|
+
var ZIP64_EOCD64_LOCATOR_BYTES = 20; // §4.3.15 fixed-size locator
|
|
64
92
|
|
|
65
93
|
// Compression methods (APPNOTE 4.4.5 — protocol-fixed method IDs)
|
|
66
94
|
var METHOD_STORE_ID = 0;
|
|
@@ -101,6 +129,52 @@ function _msdosDateTime(date) {
|
|
|
101
129
|
return { time: dosTime, date: dosDate };
|
|
102
130
|
}
|
|
103
131
|
|
|
132
|
+
// ZIP64 (APPNOTE 6.3.10 §4.3.14 / §4.3.15 / §4.4.8 / §4.5.3) — small
|
|
133
|
+
// archives stay classic byte-for-byte; the ZIP64 trailer + per-entry
|
|
134
|
+
// sentinels only appear once a size/offset overflows the classic 32-bit
|
|
135
|
+
// field (or the entry count exceeds the classic 16-bit cap). The reader
|
|
136
|
+
// in archive-read.js resolves these symmetrically.
|
|
137
|
+
|
|
138
|
+
// True when a single size/offset overflows the classic 32-bit field.
|
|
139
|
+
function _overflows32(n) { return n > ZIP64_U32_MAX; }
|
|
140
|
+
|
|
141
|
+
// Does this entry need a per-record ZIP64 extra block? An entry overflows
|
|
142
|
+
// when its compressed size, uncompressed size, or local-header offset
|
|
143
|
+
// exceeds the 32-bit limit. `lfhOffset` is only known at central-directory
|
|
144
|
+
// build time, so the local-header path passes `lfhOffset = 0` (the LFH
|
|
145
|
+
// extra never carries the offset — §4.5.3).
|
|
146
|
+
function _entryNeedsZip64(csize, usize, lfhOffset) {
|
|
147
|
+
return _overflows32(csize) || _overflows32(usize) || _overflows32(lfhOffset);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build the ZIP64 extended-information extra field (§4.5.3) carrying ONLY
|
|
151
|
+
// the fields whose classic value overflowed, in APPNOTE order:
|
|
152
|
+
// uncompressedSize, compressedSize, localHeaderOffset, diskStart. Returns
|
|
153
|
+
// an empty buffer when nothing overflowed. `includeOffset` controls
|
|
154
|
+
// whether localHeaderOffset is appended (the LFH variant omits it). The
|
|
155
|
+
// reader keys presence off the matching classic sentinel, so a field is
|
|
156
|
+
// emitted here iff the caller also writes the sentinel into the classic
|
|
157
|
+
// slot. diskStart is never emitted — single-disk archives only.
|
|
158
|
+
function _buildZip64Extra(csize, usize, lfhOffset, includeOffset) {
|
|
159
|
+
var needUsize = _overflows32(usize);
|
|
160
|
+
var needCsize = _overflows32(csize);
|
|
161
|
+
var needOffset = includeOffset && _overflows32(lfhOffset);
|
|
162
|
+
if (!needUsize && !needCsize && !needOffset) return Buffer.alloc(0);
|
|
163
|
+
var fields = 0;
|
|
164
|
+
if (needUsize) fields += 1;
|
|
165
|
+
if (needCsize) fields += 1;
|
|
166
|
+
if (needOffset) fields += 1;
|
|
167
|
+
var dataLen = fields * ZIP64_EXTRA_FIELD_BYTES;
|
|
168
|
+
var extra = Buffer.alloc(C.BYTES.bytes(4 + dataLen));
|
|
169
|
+
extra.writeUInt16LE(ZIP64_EXTRA_HEADER_ID, C.BYTES.bytes(0)); // §4.5.3 extra-field tag
|
|
170
|
+
extra.writeUInt16LE(dataLen, C.BYTES.bytes(2)); // §4.5.1 data size
|
|
171
|
+
var q = 4;
|
|
172
|
+
if (needUsize) { extra.writeBigUInt64LE(BigInt(usize), C.BYTES.bytes(q)); q += ZIP64_EXTRA_FIELD_BYTES; }
|
|
173
|
+
if (needCsize) { extra.writeBigUInt64LE(BigInt(csize), C.BYTES.bytes(q)); q += ZIP64_EXTRA_FIELD_BYTES; }
|
|
174
|
+
if (needOffset) { extra.writeBigUInt64LE(BigInt(lfhOffset), C.BYTES.bytes(q)); q += ZIP64_EXTRA_FIELD_BYTES; }
|
|
175
|
+
return extra;
|
|
176
|
+
}
|
|
177
|
+
|
|
104
178
|
/**
|
|
105
179
|
* @primitive b.archive.zip
|
|
106
180
|
* @signature b.archive.zip()
|
|
@@ -223,66 +297,156 @@ function zip() {
|
|
|
223
297
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
224
298
|
var dt = _msdosDateTime(entry.mtime);
|
|
225
299
|
var flags = FLAG_UTF8_NAME | (streaming ? FLAG_DATA_DESCRIPTOR : 0);
|
|
300
|
+
// ZIP64 (§4.3.7 + §4.5.3) applies to the buffer path only — the LFH
|
|
301
|
+
// sizes are written up-front there. Streaming entries carry zeros
|
|
302
|
+
// under the data-descriptor flag (sizes unknown at header time), so
|
|
303
|
+
// they never carry an LFH ZIP64 extra; their 64-bit values ride the
|
|
304
|
+
// data descriptor + central-directory ZIP64 extra. When either size
|
|
305
|
+
// overflows the 32-bit field, the LFH carries the sentinel and a
|
|
306
|
+
// ZIP64 extra block supplies uncompressedSize + compressedSize (the
|
|
307
|
+
// offset is never in the LFH extra — §4.5.3).
|
|
308
|
+
var csize = streaming ? 0 : entry.stored.length;
|
|
309
|
+
var usize = streaming ? 0 : entry.uncompressedSize;
|
|
310
|
+
var zip64 = !streaming && _entryNeedsZip64(csize, usize, 0);
|
|
311
|
+
var zip64Extra = zip64 ? _buildZip64Extra(csize, usize, 0, false) : Buffer.alloc(0);
|
|
226
312
|
// APPNOTE 4.3.7 — local file header. Offsets are byte positions
|
|
227
313
|
// within the 30-byte fixed header; each route through C.BYTES.bytes
|
|
228
314
|
// so the framework's byte-math discipline applies even to format-
|
|
229
315
|
// fixed offsets.
|
|
230
316
|
var hdr = Buffer.alloc(C.BYTES.bytes(30));
|
|
231
317
|
hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
|
|
232
|
-
hdr.writeUInt16LE(20, C.BYTES.bytes(4));
|
|
318
|
+
hdr.writeUInt16LE(zip64 ? ZIP64_VERSION_NEEDED : 20, C.BYTES.bytes(4)); // version needed
|
|
233
319
|
hdr.writeUInt16LE(flags, C.BYTES.bytes(6)); // flags: bit 11 UTF-8, bit 3 data-descriptor
|
|
234
320
|
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
|
|
235
321
|
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
|
|
236
322
|
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(12));
|
|
237
323
|
hdr.writeUInt32LE(streaming ? 0 : entry.crc, C.BYTES.bytes(14));
|
|
238
|
-
hdr.writeUInt32LE(
|
|
239
|
-
hdr.writeUInt32LE(
|
|
324
|
+
hdr.writeUInt32LE(_overflows32(csize) ? ZIP64_U32_SENTINEL : csize, C.BYTES.bytes(18));
|
|
325
|
+
hdr.writeUInt32LE(_overflows32(usize) ? ZIP64_U32_SENTINEL : usize, C.BYTES.bytes(22));
|
|
240
326
|
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
|
|
241
|
-
hdr.writeUInt16LE(
|
|
242
|
-
return Buffer.concat([hdr, nameBuf]);
|
|
327
|
+
hdr.writeUInt16LE(zip64Extra.length, C.BYTES.bytes(28)); // extra field length
|
|
328
|
+
return Buffer.concat([hdr, nameBuf, zip64Extra]);
|
|
243
329
|
}
|
|
244
330
|
|
|
245
331
|
function _buildDataDescriptor(crc, csize, usize) {
|
|
246
|
-
// APPNOTE 4.3.9 —
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
332
|
+
// APPNOTE 4.3.9 — data descriptor (with optional sig dword). The
|
|
333
|
+
// classic form carries 4-byte csize/usize; §4.3.9.2 widens both to
|
|
334
|
+
// 8 bytes when the entry is ZIP64 (either size overflows the 32-bit
|
|
335
|
+
// field). The central directory carries the authoritative sizes, so
|
|
336
|
+
// the wide form here is for external single-pass extractors.
|
|
337
|
+
var zip64 = _overflows32(csize) || _overflows32(usize);
|
|
338
|
+
if (!zip64) {
|
|
339
|
+
var dd = Buffer.alloc(C.BYTES.bytes(16));
|
|
340
|
+
dd.writeUInt32LE(SIG_DATA_DESCRIPTOR, C.BYTES.bytes(0));
|
|
341
|
+
dd.writeUInt32LE(crc, C.BYTES.bytes(4));
|
|
342
|
+
dd.writeUInt32LE(csize, C.BYTES.bytes(0x08));
|
|
343
|
+
dd.writeUInt32LE(usize, C.BYTES.bytes(12));
|
|
344
|
+
return dd;
|
|
345
|
+
}
|
|
346
|
+
var dd64 = Buffer.alloc(C.BYTES.bytes(24));
|
|
347
|
+
dd64.writeUInt32LE(SIG_DATA_DESCRIPTOR, C.BYTES.bytes(0));
|
|
348
|
+
dd64.writeUInt32LE(crc, C.BYTES.bytes(4));
|
|
349
|
+
dd64.writeBigUInt64LE(BigInt(csize), C.BYTES.bytes(0x08));
|
|
350
|
+
dd64.writeBigUInt64LE(BigInt(usize), C.BYTES.bytes(0x10));
|
|
351
|
+
return dd64;
|
|
253
352
|
}
|
|
254
353
|
|
|
255
354
|
function _buildCentralDirectoryEntry(entry, lfhOffset) {
|
|
256
355
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
257
356
|
var dt = _msdosDateTime(entry.mtime);
|
|
258
357
|
var flags = FLAG_UTF8_NAME | (entry.kind === "stream" ? FLAG_DATA_DESCRIPTOR : 0);
|
|
358
|
+
var csize = entry.stored.length;
|
|
359
|
+
var usize = entry.uncompressedSize;
|
|
360
|
+
// ZIP64 (§4.3.12 + §4.4.8 + §4.5.3): the central-directory entry
|
|
361
|
+
// carries the offset, so its ZIP64 trigger includes localHeaderOffset
|
|
362
|
+
// overflow. Each overflowed field becomes the classic sentinel and is
|
|
363
|
+
// supplied 64-bit in the extra block, in APPNOTE order.
|
|
364
|
+
var zip64 = _entryNeedsZip64(csize, usize, lfhOffset);
|
|
365
|
+
var zip64Extra = zip64 ? _buildZip64Extra(csize, usize, lfhOffset, true) : Buffer.alloc(0);
|
|
259
366
|
// APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
|
|
260
367
|
var hdr = Buffer.alloc(C.BYTES.bytes(46));
|
|
261
368
|
hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
|
|
262
369
|
hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
|
|
263
|
-
hdr.writeUInt16LE(20, C.BYTES.bytes(6));
|
|
370
|
+
hdr.writeUInt16LE(zip64 ? ZIP64_VERSION_NEEDED : 20, C.BYTES.bytes(6)); // version needed
|
|
264
371
|
hdr.writeUInt16LE(flags, C.BYTES.bytes(0x08)); // flags: bit 11 UTF-8, bit 3 data-descriptor (stream)
|
|
265
372
|
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
|
|
266
373
|
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
|
|
267
374
|
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
|
|
268
375
|
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(0x10));
|
|
269
|
-
hdr.writeUInt32LE(
|
|
270
|
-
hdr.writeUInt32LE(
|
|
376
|
+
hdr.writeUInt32LE(_overflows32(csize) ? ZIP64_U32_SENTINEL : csize, C.BYTES.bytes(20));
|
|
377
|
+
hdr.writeUInt32LE(_overflows32(usize) ? ZIP64_U32_SENTINEL : usize, C.BYTES.bytes(0x18));
|
|
271
378
|
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(28));
|
|
272
|
-
hdr.writeUInt16LE(
|
|
379
|
+
hdr.writeUInt16LE(zip64Extra.length, C.BYTES.bytes(30)); // extra field length
|
|
273
380
|
hdr.writeUInt16LE(0, C.BYTES.bytes(0x20)); // file comment length
|
|
274
381
|
hdr.writeUInt16LE(0, C.BYTES.bytes(34)); // disk number start
|
|
275
382
|
hdr.writeUInt16LE(0, C.BYTES.bytes(36)); // internal file attributes
|
|
276
383
|
hdr.writeUInt32LE(0, C.BYTES.bytes(38)); // external file attributes
|
|
277
|
-
hdr.writeUInt32LE(lfhOffset, C.BYTES.bytes(42));
|
|
278
|
-
return Buffer.concat([hdr, nameBuf]);
|
|
384
|
+
hdr.writeUInt32LE(_overflows32(lfhOffset) ? ZIP64_U32_SENTINEL : lfhOffset, C.BYTES.bytes(42));
|
|
385
|
+
return Buffer.concat([hdr, nameBuf, zip64Extra]);
|
|
279
386
|
}
|
|
280
387
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
388
|
+
// Build the end-of-central-directory trailer. Returns a Buffer that is
|
|
389
|
+
// just the classic 22-byte EOCD for archives within the classic limits,
|
|
390
|
+
// or the ZIP64 EOCD record (§4.3.14) + ZIP64 EOCD locator (§4.3.15) +
|
|
391
|
+
// the classic EOCD (with sentinels) when the entry count exceeds 65535
|
|
392
|
+
// or the central-directory size/offset exceeds the 32-bit field. The
|
|
393
|
+
// ZIP64 trailer precedes the classic EOCD exactly where the reader's
|
|
394
|
+
// locator-before-classic-EOCD walk expects it.
|
|
395
|
+
function _buildEndOfCentralDirectory(totalEntries, cdSize, cdStart) {
|
|
396
|
+
var needZip64 = totalEntries > ZIP64_MAX_CLASSIC_ENTRIES ||
|
|
397
|
+
_overflows32(cdSize) || _overflows32(cdStart);
|
|
398
|
+
if (!needZip64) {
|
|
399
|
+
// APPNOTE 4.3.16 — end of central directory record (22-byte fixed).
|
|
400
|
+
var eocdClassic = Buffer.alloc(C.BYTES.bytes(22));
|
|
401
|
+
eocdClassic.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
402
|
+
eocdClassic.writeUInt16LE(0, C.BYTES.bytes(4)); // disk number
|
|
403
|
+
eocdClassic.writeUInt16LE(0, C.BYTES.bytes(6)); // disk where CD starts
|
|
404
|
+
eocdClassic.writeUInt16LE(totalEntries, C.BYTES.bytes(0x08)); // entries on this disk
|
|
405
|
+
eocdClassic.writeUInt16LE(totalEntries, C.BYTES.bytes(10)); // total entries
|
|
406
|
+
eocdClassic.writeUInt32LE(cdSize, C.BYTES.bytes(12)); // size of central directory
|
|
407
|
+
eocdClassic.writeUInt32LE(cdStart, C.BYTES.bytes(0x10)); // offset of central directory
|
|
408
|
+
eocdClassic.writeUInt16LE(0, C.BYTES.bytes(20)); // comment length
|
|
409
|
+
return eocdClassic;
|
|
285
410
|
}
|
|
411
|
+
// ZIP64 EOCD record (§4.3.14) — fixed 56-byte form, no extensible
|
|
412
|
+
// data tail. The "size of ZIP64 EOCD record" field counts the bytes
|
|
413
|
+
// that FOLLOW it (record total minus the 12-byte sig+size prefix).
|
|
414
|
+
var eocd64 = Buffer.alloc(C.BYTES.bytes(ZIP64_EOCD64_BYTES));
|
|
415
|
+
eocd64.writeUInt32LE(SIG_EOCD64, C.BYTES.bytes(0));
|
|
416
|
+
eocd64.writeBigUInt64LE(BigInt(ZIP64_EOCD64_BYTES - 12), C.BYTES.bytes(4));
|
|
417
|
+
eocd64.writeUInt16LE(0x033f, C.BYTES.bytes(12)); // version made by (UNIX | 6.3)
|
|
418
|
+
eocd64.writeUInt16LE(ZIP64_VERSION_NEEDED, C.BYTES.bytes(14)); // version needed
|
|
419
|
+
eocd64.writeUInt32LE(0, C.BYTES.bytes(16)); // this disk number
|
|
420
|
+
eocd64.writeUInt32LE(0, C.BYTES.bytes(20)); // disk with CD start
|
|
421
|
+
eocd64.writeBigUInt64LE(BigInt(totalEntries), C.BYTES.bytes(24)); // entries on this disk
|
|
422
|
+
eocd64.writeBigUInt64LE(BigInt(totalEntries), C.BYTES.bytes(32)); // total entries
|
|
423
|
+
eocd64.writeBigUInt64LE(BigInt(cdSize), C.BYTES.bytes(40)); // central directory size
|
|
424
|
+
eocd64.writeBigUInt64LE(BigInt(cdStart), C.BYTES.bytes(48)); // central directory offset
|
|
425
|
+
var eocd64Offset = cdStart + cdSize;
|
|
426
|
+
// ZIP64 EOCD locator (§4.3.15) — fixed 20 bytes.
|
|
427
|
+
var locator = Buffer.alloc(C.BYTES.bytes(ZIP64_EOCD64_LOCATOR_BYTES));
|
|
428
|
+
locator.writeUInt32LE(SIG_EOCD64_LOCATOR, C.BYTES.bytes(0));
|
|
429
|
+
locator.writeUInt32LE(0, C.BYTES.bytes(4)); // disk with ZIP64 EOCD
|
|
430
|
+
locator.writeBigUInt64LE(BigInt(eocd64Offset), C.BYTES.bytes(0x08));
|
|
431
|
+
locator.writeUInt32LE(1, C.BYTES.bytes(16)); // total number of disks
|
|
432
|
+
// Classic EOCD (§4.3.16) with ZIP64 sentinels for any overflowed
|
|
433
|
+
// field — readers that don't grok ZIP64 see the sentinel, ZIP64-aware
|
|
434
|
+
// readers follow the locator.
|
|
435
|
+
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
436
|
+
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
437
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(4));
|
|
438
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(6));
|
|
439
|
+
eocd.writeUInt16LE(totalEntries > ZIP64_MAX_CLASSIC_ENTRIES
|
|
440
|
+
? ZIP64_U16_SENTINEL : totalEntries, C.BYTES.bytes(0x08));
|
|
441
|
+
eocd.writeUInt16LE(totalEntries > ZIP64_MAX_CLASSIC_ENTRIES
|
|
442
|
+
? ZIP64_U16_SENTINEL : totalEntries, C.BYTES.bytes(10));
|
|
443
|
+
eocd.writeUInt32LE(_overflows32(cdSize) ? ZIP64_U32_SENTINEL : cdSize, C.BYTES.bytes(12));
|
|
444
|
+
eocd.writeUInt32LE(_overflows32(cdStart) ? ZIP64_U32_SENTINEL : cdStart, C.BYTES.bytes(0x10));
|
|
445
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(20));
|
|
446
|
+
return Buffer.concat([eocd64, locator, eocd]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function toBuffer() {
|
|
286
450
|
for (var k = 0; k < entries.length; k++) {
|
|
287
451
|
if (entries[k].kind === "stream") {
|
|
288
452
|
throw new ArchiveError("archive/streaming-entry",
|
|
@@ -307,17 +471,7 @@ function zip() {
|
|
|
307
471
|
pieces.push(cdh);
|
|
308
472
|
cdSize += cdh.length;
|
|
309
473
|
}
|
|
310
|
-
|
|
311
|
-
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
312
|
-
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
313
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(4)); // disk number
|
|
314
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(6)); // disk where CD starts
|
|
315
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08)); // entries on this disk
|
|
316
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10)); // total entries
|
|
317
|
-
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12)); // size of central directory
|
|
318
|
-
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10)); // offset of central directory
|
|
319
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(20)); // comment length
|
|
320
|
-
pieces.push(eocd);
|
|
474
|
+
pieces.push(_buildEndOfCentralDirectory(entries.length, cdSize, cdStart));
|
|
321
475
|
return Buffer.concat(pieces);
|
|
322
476
|
}
|
|
323
477
|
|
|
@@ -451,11 +605,6 @@ function zip() {
|
|
|
451
605
|
"toStream: writable must be a Writable (or omit to receive a Readable)");
|
|
452
606
|
}
|
|
453
607
|
|
|
454
|
-
if (entries.length > 65535) {
|
|
455
|
-
throw new ArchiveError("archive/too-many-entries",
|
|
456
|
-
"ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
|
|
457
|
-
}
|
|
458
|
-
|
|
459
608
|
var run = (async function () {
|
|
460
609
|
var offsets = [];
|
|
461
610
|
var totalLocalBytes = 0;
|
|
@@ -480,15 +629,7 @@ function zip() {
|
|
|
480
629
|
await _writeChunk(dest, cdh);
|
|
481
630
|
cdSize += cdh.length;
|
|
482
631
|
}
|
|
483
|
-
var eocd =
|
|
484
|
-
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
485
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(4));
|
|
486
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(6));
|
|
487
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08));
|
|
488
|
-
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10));
|
|
489
|
-
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12));
|
|
490
|
-
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10));
|
|
491
|
-
eocd.writeUInt16LE(0, C.BYTES.bytes(20));
|
|
632
|
+
var eocd = _buildEndOfCentralDirectory(entries.length, cdSize, cdStart);
|
|
492
633
|
await _writeChunk(dest, eocd);
|
|
493
634
|
if (typeof dest.end === "function") dest.end();
|
|
494
635
|
_emitAudit(opts, "archive.zip.streamed.completed", "success", {
|
|
@@ -574,4 +715,17 @@ module.exports = {
|
|
|
574
715
|
// Test-only export — operators don't call this; it's here for unit-testing
|
|
575
716
|
// the CRC implementation against known vectors.
|
|
576
717
|
_crc32ForTest: _crc32,
|
|
718
|
+
// Test-only export — exercises the per-entry ZIP64 extended-information
|
|
719
|
+
// extra-field builder (§4.5.3) at logical sizes/offsets that exceed the
|
|
720
|
+
// 32-bit field, which the buffer path can only reach with multi-GiB
|
|
721
|
+
// payloads. The entry-count and EOCD64 paths are covered by full
|
|
722
|
+
// round-trips through the random-access reader.
|
|
723
|
+
_zip64ForTest: {
|
|
724
|
+
entryNeedsZip64: _entryNeedsZip64,
|
|
725
|
+
buildExtra: _buildZip64Extra,
|
|
726
|
+
U16_SENTINEL: ZIP64_U16_SENTINEL,
|
|
727
|
+
U32_SENTINEL: ZIP64_U32_SENTINEL,
|
|
728
|
+
U32_MAX: ZIP64_U32_MAX,
|
|
729
|
+
EXTRA_HEADER_ID: ZIP64_EXTRA_HEADER_ID,
|
|
730
|
+
},
|
|
577
731
|
};
|
package/lib/auth/oauth.js
CHANGED
|
@@ -497,6 +497,55 @@ function create(opts) {
|
|
|
497
497
|
});
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
// PKCE downgrade defense (RFC 9700 §4.13 / OAuth 2.1 §6.2.4 +
|
|
501
|
+
// RFC 7636). The client always sends code_challenge_method=S256 (the
|
|
502
|
+
// plain method and pkce:false are refused). A network attacker who
|
|
503
|
+
// can tamper with discovery metadata can advertise an OP that only
|
|
504
|
+
// supports the `plain` method (or omits S256), nudging a permissive
|
|
505
|
+
// client into a weaker exchange. We don't downgrade — but if the OP's
|
|
506
|
+
// published `code_challenge_methods_supported` is PRESENT and does not
|
|
507
|
+
// list "S256", the redirect we'd build sends an S256 challenge the OP
|
|
508
|
+
// claims it cannot verify, which is the signature of a stripped-S256
|
|
509
|
+
// MITM. Refuse rather than emit an authorization request the metadata
|
|
510
|
+
// says will fail.
|
|
511
|
+
//
|
|
512
|
+
// Back-compat: an OP that does not publish the field at all keeps
|
|
513
|
+
// today's behavior (S256 is still sent — RFC 7636 §4.2 lets the OP
|
|
514
|
+
// accept S256 without advertising it). The check is a non-fetching
|
|
515
|
+
// peek at the already-resolved discovery document: it never forces a
|
|
516
|
+
// network round-trip, so static-endpoint clients (no discovery) are
|
|
517
|
+
// unaffected. Config-time refusal — throw so the operator sees the
|
|
518
|
+
// mismatch instead of a silently-doomed redirect.
|
|
519
|
+
function _assertS256Supported(config) {
|
|
520
|
+
if (!config || typeof config !== "object") return;
|
|
521
|
+
var methods = config.code_challenge_methods_supported;
|
|
522
|
+
if (!Array.isArray(methods)) return; // field absent → keep behavior
|
|
523
|
+
var hasS256 = false;
|
|
524
|
+
for (var i = 0; i < methods.length; i++) {
|
|
525
|
+
if (methods[i] === "S256") { hasS256 = true; break; }
|
|
526
|
+
}
|
|
527
|
+
if (!hasS256) {
|
|
528
|
+
throw new OAuthError("auth-oauth/pkce-downgrade",
|
|
529
|
+
"OP discovery advertises code_challenge_methods_supported " +
|
|
530
|
+
JSON.stringify(methods) + " without 'S256'. The framework sends " +
|
|
531
|
+
"S256 (RFC 7636) and refuses to emit an authorization request the " +
|
|
532
|
+
"OP claims it cannot verify — a stripped-S256 / plain-only " +
|
|
533
|
+
"discovery is the signature of a PKCE downgrade (RFC 9700 §4.13). " +
|
|
534
|
+
"Fix the OP metadata or, on a genuinely S256-incapable IdP, " +
|
|
535
|
+
"front it with a conforming gateway.");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Peek the cached discovery document WITHOUT triggering a fetch, so
|
|
540
|
+
// the PKCE-downgrade gate only inspects metadata the client already
|
|
541
|
+
// resolved on the discovery path. Returns null when no discovery has
|
|
542
|
+
// occurred (static endpoints / non-OIDC) — back-compat preserved.
|
|
543
|
+
async function _peekDiscovery() {
|
|
544
|
+
if (!isOidc || !issuer) return null;
|
|
545
|
+
try { return (await _discoveryCache.get("config")) || null; }
|
|
546
|
+
catch (_e) { return null; }
|
|
547
|
+
}
|
|
548
|
+
|
|
500
549
|
async function _resolveEndpoint(name) {
|
|
501
550
|
if (staticEndpoints[name]) return staticEndpoints[name];
|
|
502
551
|
var config = await _discover();
|
|
@@ -530,6 +579,11 @@ function create(opts) {
|
|
|
530
579
|
async function authorizationUrl(uopts) {
|
|
531
580
|
uopts = uopts || {};
|
|
532
581
|
var endpoint = await _resolveEndpoint("authorizationEndpoint");
|
|
582
|
+
// RFC 9700 §4.13 — refuse an OP whose discovery metadata advertises
|
|
583
|
+
// code_challenge_methods_supported without S256 (PKCE downgrade /
|
|
584
|
+
// stripped-S256 MITM). _resolveEndpoint already populated the
|
|
585
|
+
// discovery cache on the OIDC path; this peek never fetches.
|
|
586
|
+
_assertS256Supported(await _peekDiscovery());
|
|
533
587
|
// CVE-2026-34511 — PKCE verifier leak via state. The state token is
|
|
534
588
|
// an opaque CSPRNG output; the PKCE verifier is generated separately
|
|
535
589
|
// and returned in its own field for the caller to store. The
|
|
@@ -1270,6 +1324,10 @@ function create(opts) {
|
|
|
1270
1324
|
"pushed_authorization_request_endpoint (set opts.pushedAuthorizationRequestEndpoint " +
|
|
1271
1325
|
"on create() if the IdP doesn't publish it)");
|
|
1272
1326
|
}
|
|
1327
|
+
// Same PKCE-downgrade gate as authorizationUrl (RFC 9700 §4.13):
|
|
1328
|
+
// PAR pushes the identical S256 challenge, so an OP advertising
|
|
1329
|
+
// code_challenge_methods_supported without S256 is refused here too.
|
|
1330
|
+
_assertS256Supported(await _peekDiscovery());
|
|
1273
1331
|
// Build the same param set authorizationUrl would emit, then POST
|
|
1274
1332
|
// it to PAR instead of putting it in the redirect URL.
|
|
1275
1333
|
var state = uopts.state || _generateRandomToken(STATE_NONCE_BYTES);
|
package/lib/auth/oid4vci.js
CHANGED
|
@@ -79,14 +79,15 @@ function _b64uDecodeStr(s) {
|
|
|
79
79
|
return Buffer.from(s, "base64url").toString("utf8");
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedClientId, supportedAlgs, proofMaxAgeMs) {
|
|
82
|
+
async function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedClientId, supportedAlgs, proofMaxAgeMs, resolveKid) {
|
|
83
83
|
// OID4VCI §7.2.1.1: the proof JWT MUST:
|
|
84
84
|
// - typ = "openid4vci-proof+jwt"
|
|
85
85
|
// - alg in supported list (issuer publishes these)
|
|
86
86
|
// - aud = credential issuer URL (this issuer's `credential_issuer`)
|
|
87
87
|
// - iat = recent
|
|
88
88
|
// - nonce = c_nonce previously issued to the wallet
|
|
89
|
-
// - jwk OR kid
|
|
89
|
+
// - jwk (inline) OR kid (resolved via resolveKid) in the header
|
|
90
|
+
// pointing at the holder key to bind cnf to (RFC 7515 §4.1.3/§4.1.4)
|
|
90
91
|
if (typeof proofJwt !== "string" || proofJwt.length === 0 || proofJwt.length > MAX_PROOF_BYTES) {
|
|
91
92
|
throw new AuthError("auth-oid4vci/bad-proof",
|
|
92
93
|
"credential issuance: proof JWT is empty or exceeds " + MAX_PROOF_BYTES + " bytes");
|
|
@@ -171,29 +172,78 @@ function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedClientId
|
|
|
171
172
|
"credential issuance: proof JWT iss does not match the access-token client_id");
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
//
|
|
175
|
+
// Resolve the holder key the proof is signed with. Two paths:
|
|
176
|
+
// - inline `jwk` (RFC 7515 §4.1.3) — the wallet ships the public
|
|
177
|
+
// key in the header; bind `cnf` to it directly.
|
|
178
|
+
// - `kid` (RFC 7515 §4.1.4) without inline `jwk` — the wallet
|
|
179
|
+
// references a key by identifier (EUDI-Wallet attested-key flow,
|
|
180
|
+
// OID4VCI §8.2.1.1 `key_attestation` proof). The operator
|
|
181
|
+
// supplies `resolveKid(kid, header)` to map the kid → public key.
|
|
182
|
+
// With no resolver configured the issuer keeps the clear refusal
|
|
183
|
+
// (back-compat): a kid-only proof can't be verified without one.
|
|
175
184
|
var holderKeyJwk = header.jwk || null;
|
|
176
|
-
if (!holderKeyJwk && header.kid) {
|
|
177
|
-
// Operators with kid-only proofs supply a resolver; until then,
|
|
178
|
-
// require jwk inline. Refuse rather than silently downgrade.
|
|
179
|
-
throw new AuthError("auth-oid4vci/kid-resolver-not-supported",
|
|
180
|
-
"credential issuance: proof JWT used `kid` without inline `jwk` — supply { jwk } in the header for inline binding (kid-resolver path is operator-side)");
|
|
181
|
-
}
|
|
182
|
-
if (!holderKeyJwk) {
|
|
183
|
-
throw new AuthError("auth-oid4vci/no-jwk-in-header",
|
|
184
|
-
"credential issuance: proof JWT must carry `jwk` for inline holder-key binding");
|
|
185
|
-
}
|
|
186
|
-
// CVE-2026-22817 — cross-check alg/kty before importing the holder
|
|
187
|
-
// JWK. Without this an attacker-controlled `alg: "HS256"` against an
|
|
188
|
-
// RSA holder JWK would have node:crypto.verify treat the RSA public
|
|
189
|
-
// key as an HMAC secret. Routed through the shared helper so every
|
|
190
|
-
// JWT verifier in the framework enforces the same check.
|
|
191
|
-
jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
|
|
192
185
|
var keyObj;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
186
|
+
if (!holderKeyJwk && header.kid) {
|
|
187
|
+
if (typeof resolveKid !== "function") {
|
|
188
|
+
throw new AuthError("auth-oid4vci/kid-resolver-not-supported",
|
|
189
|
+
"credential issuance: proof JWT used `kid` without inline `jwk` — supply { jwk } in the header for inline binding, or configure issuer.create({ resolveKid }) to resolve kid-referenced holder keys");
|
|
190
|
+
}
|
|
191
|
+
var resolved;
|
|
192
|
+
try {
|
|
193
|
+
resolved = await resolveKid(header.kid, header);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
// Wrap a resolver exception in a stable AuthError code so the
|
|
196
|
+
// /credential handler returns a typed refusal instead of an
|
|
197
|
+
// unhandled rejection. resolveKid is operator code, so its own
|
|
198
|
+
// message is allowed through for operator-side debugging.
|
|
199
|
+
throw new AuthError("auth-oid4vci/kid-resolver-failed",
|
|
200
|
+
"credential issuance: resolveKid threw while resolving the proof JWT kid: " + ((e && e.message) || String(e)));
|
|
201
|
+
}
|
|
202
|
+
if (!resolved) {
|
|
203
|
+
throw new AuthError("auth-oid4vci/kid-unresolved",
|
|
204
|
+
"credential issuance: resolveKid returned no key for the proof JWT kid — refused");
|
|
205
|
+
}
|
|
206
|
+
// Normalize to (verify KeyObject) + (cnf JWK). A KeyObject verifies
|
|
207
|
+
// the signature directly; the cnf binding sdJwtIssuer.issue expects
|
|
208
|
+
// a JWK, so a resolved KeyObject is exported to one. A resolved JWK
|
|
209
|
+
// is used for both.
|
|
210
|
+
if (resolved instanceof nodeCrypto.KeyObject) {
|
|
211
|
+
try { holderKeyJwk = resolved.export({ format: "jwk" }); }
|
|
212
|
+
catch (e) {
|
|
213
|
+
throw new AuthError("auth-oid4vci/bad-resolved-key",
|
|
214
|
+
"credential issuance: resolveKid returned a KeyObject that does not export to JWK: " + ((e && e.message) || String(e)));
|
|
215
|
+
}
|
|
216
|
+
} else if (typeof resolved === "object" && typeof resolved.kty === "string") {
|
|
217
|
+
holderKeyJwk = resolved;
|
|
218
|
+
} else {
|
|
219
|
+
throw new AuthError("auth-oid4vci/bad-resolved-key",
|
|
220
|
+
"credential issuance: resolveKid must return a JWK object (with kty) or a node:crypto KeyObject");
|
|
221
|
+
}
|
|
222
|
+
// CVE-2026-22817 — same alg/kty cross-check the inline path applies.
|
|
223
|
+
// A resolver that returns an RSA key for a proof declaring an HMAC
|
|
224
|
+
// alg would otherwise be verified as an HMAC secret.
|
|
225
|
+
jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
|
|
226
|
+
try { keyObj = nodeCrypto.createPublicKey({ key: holderKeyJwk, format: "jwk" }); }
|
|
227
|
+
catch (e) {
|
|
228
|
+
throw new AuthError("auth-oid4vci/bad-resolved-key",
|
|
229
|
+
"credential issuance: resolveKid-returned key is not importable as a public key: " + ((e && e.message) || String(e)));
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (!holderKeyJwk) {
|
|
233
|
+
throw new AuthError("auth-oid4vci/no-jwk-in-header",
|
|
234
|
+
"credential issuance: proof JWT must carry `jwk` for inline holder-key binding");
|
|
235
|
+
}
|
|
236
|
+
// CVE-2026-22817 — cross-check alg/kty before importing the holder
|
|
237
|
+
// JWK. Without this an attacker-controlled `alg: "HS256"` against an
|
|
238
|
+
// RSA holder JWK would have node:crypto.verify treat the RSA public
|
|
239
|
+
// key as an HMAC secret. Routed through the shared helper so every
|
|
240
|
+
// JWT verifier in the framework enforces the same check.
|
|
241
|
+
jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
|
|
242
|
+
try { keyObj = nodeCrypto.createPublicKey({ key: holderKeyJwk, format: "jwk" }); }
|
|
243
|
+
catch (e) {
|
|
244
|
+
throw new AuthError("auth-oid4vci/bad-jwk",
|
|
245
|
+
"credential issuance: proof JWT jwk is not parseable: " + ((e && e.message) || String(e)));
|
|
246
|
+
}
|
|
197
247
|
}
|
|
198
248
|
|
|
199
249
|
var signingInput = parts[0] + "." + parts[1];
|
|
@@ -241,6 +291,7 @@ function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedClientId
|
|
|
241
291
|
* sdJwtIssuer: <b.auth.sdJwtVc.issuer instance>, // mints the SD-JWT VC
|
|
242
292
|
* supportedCredentials: { [id]: { format, vct, claims, ... } },
|
|
243
293
|
* proofAlgorithms: string[], // default ["ES256", "ES384", "EdDSA"]
|
|
294
|
+
* resolveKid?: function(kid, header), // resolve a kid-only proof's holder key (JWK | KeyObject); without it, kid-only proofs are refused
|
|
244
295
|
* preAuthCodeTtlMs?: number, // default 5m
|
|
245
296
|
* accessTokenTtlMs?: number, // default 15m
|
|
246
297
|
* cNonceTtlMs?: number, // default 5m
|
|
@@ -301,6 +352,12 @@ function create(opts) {
|
|
|
301
352
|
var proofAlgs = Array.isArray(opts.proofAlgorithms) && opts.proofAlgorithms.length > 0
|
|
302
353
|
? opts.proofAlgorithms : ["ES256", "ES384", "EdDSA"];
|
|
303
354
|
|
|
355
|
+
// Optional kid-resolver for kid-only proofs (EUDI-Wallet attested-key
|
|
356
|
+
// flow). Config-time throw if supplied but not a function. Absent →
|
|
357
|
+
// kid-only proofs keep the clear refusal (back-compat).
|
|
358
|
+
var resolveKid = validateOpts.optionalFunction(opts.resolveKid,
|
|
359
|
+
"issuer.create: resolveKid", AuthError, "auth-oid4vci/bad-resolve-kid");
|
|
360
|
+
|
|
304
361
|
var preAuthTtl = opts.preAuthCodeTtlMs || DEFAULT_PRE_AUTH_TTL_MS;
|
|
305
362
|
var accessTokenTtl = opts.accessTokenTtlMs || DEFAULT_ACCESS_TOKEN_TTL;
|
|
306
363
|
var cNonceTtl = opts.cNonceTtlMs || DEFAULT_C_NONCE_TTL_MS;
|
|
@@ -466,7 +523,7 @@ function create(opts) {
|
|
|
466
523
|
"exchangePreAuthorizedCode: tx_code does not match");
|
|
467
524
|
}
|
|
468
525
|
}
|
|
469
|
-
await codeStore.
|
|
526
|
+
await codeStore.del(eopts.preAuthCode);
|
|
470
527
|
var accessToken = generateToken(32); // 256-bit access token
|
|
471
528
|
var cNonce = generateToken(16); // 128-bit c_nonce
|
|
472
529
|
var record = {
|
|
@@ -555,7 +612,7 @@ function create(opts) {
|
|
|
555
612
|
}
|
|
556
613
|
|
|
557
614
|
var expectedCNonce = await cNonceStore.get(iopts.accessToken);
|
|
558
|
-
var verified = _verifyProofJwt(iopts.proof, opts.credentialIssuerUrl, expectedCNonce, null, proofAlgs, proofMaxAgeMs);
|
|
615
|
+
var verified = await _verifyProofJwt(iopts.proof, opts.credentialIssuerUrl, expectedCNonce, null, proofAlgs, proofMaxAgeMs, resolveKid);
|
|
559
616
|
|
|
560
617
|
if (!iopts.claims || typeof iopts.claims !== "object") {
|
|
561
618
|
throw new AuthError("auth-oid4vci/no-claims",
|
|
@@ -584,8 +641,8 @@ function create(opts) {
|
|
|
584
641
|
// explicitly tightens cleanup.
|
|
585
642
|
if (accessTokenSingleUse) {
|
|
586
643
|
try {
|
|
587
|
-
await atStore.
|
|
588
|
-
await cNonceStore.
|
|
644
|
+
await atStore.del(iopts.accessToken);
|
|
645
|
+
await cNonceStore.del(iopts.accessToken);
|
|
589
646
|
} catch (_e) { /* drop-silent — cleanup is best-effort */ }
|
|
590
647
|
}
|
|
591
648
|
|