@file-viewer/renderer-signature 3.0.0

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 (45) hide show
  1. package/LICENSE +160 -0
  2. package/README.en.md +87 -0
  3. package/README.md +89 -0
  4. package/THIRD_PARTY_LICENSES.json +3867 -0
  5. package/THIRD_PARTY_NOTICES.md +10 -0
  6. package/dist/container.worker.d.ts +1 -0
  7. package/dist/container.worker.js +39 -0
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.js +53 -0
  10. package/dist/inspect.d.ts +7 -0
  11. package/dist/inspect.js +4 -0
  12. package/dist/openpgp/client.d.ts +16 -0
  13. package/dist/openpgp/client.js +148 -0
  14. package/dist/openpgp/formatDetection.d.ts +3 -0
  15. package/dist/openpgp/formatDetection.js +45 -0
  16. package/dist/openpgp/types.d.ts +72 -0
  17. package/dist/openpgp/types.js +1 -0
  18. package/dist/rpgp-wasm/rpgp_wrapper.d.ts +43 -0
  19. package/dist/rpgp-wasm/rpgp_wrapper.js +578 -0
  20. package/dist/rpgp-wasm/rpgp_wrapper_bg.wasm +0 -0
  21. package/dist/rpgp-wasm/rpgp_wrapper_bg.wasm.d.ts +10 -0
  22. package/dist/signature.d.ts +24 -0
  23. package/dist/signature.js +1040 -0
  24. package/dist/signature.worker.d.ts +1 -0
  25. package/dist/signature.worker.js +108 -0
  26. package/dist/signatureAsn1.d.ts +150 -0
  27. package/dist/signatureAsn1.js +1486 -0
  28. package/dist/structured/asic.d.ts +3 -0
  29. package/dist/structured/asic.js +188 -0
  30. package/dist/structured/containerClient.d.ts +12 -0
  31. package/dist/structured/containerClient.js +71 -0
  32. package/dist/structured/containerProtocol.d.ts +20 -0
  33. package/dist/structured/containerProtocol.js +1 -0
  34. package/dist/structured/jws.d.ts +3 -0
  35. package/dist/structured/jws.js +382 -0
  36. package/dist/structured/limits.d.ts +20 -0
  37. package/dist/structured/limits.js +41 -0
  38. package/dist/structured/types.d.ts +53 -0
  39. package/dist/structured/types.js +1 -0
  40. package/dist/structured/zipPreflight.d.ts +14 -0
  41. package/dist/structured/zipPreflight.js +173 -0
  42. package/dist/workerProtocol.d.ts +40 -0
  43. package/dist/workerProtocol.js +1 -0
  44. package/file-viewer.capability.json +56 -0
  45. package/package.json +106 -0
@@ -0,0 +1,1486 @@
1
+ const UNIVERSAL = 0;
2
+ const CONTEXT = 2;
3
+ const TAG = {
4
+ boolean: 1,
5
+ integer: 2,
6
+ bitString: 3,
7
+ octetString: 4,
8
+ null: 5,
9
+ oid: 6,
10
+ utf8String: 12,
11
+ sequence: 16,
12
+ set: 17,
13
+ printableString: 19,
14
+ teletexString: 20,
15
+ ia5String: 22,
16
+ utcTime: 23,
17
+ generalizedTime: 24,
18
+ universalString: 28,
19
+ bmpString: 30
20
+ };
21
+ const OID = {
22
+ data: '1.2.840.113549.1.7.1',
23
+ signedData: '1.2.840.113549.1.7.2',
24
+ envelopedData: '1.2.840.113549.1.7.3',
25
+ digestedData: '1.2.840.113549.1.7.5',
26
+ encryptedData: '1.2.840.113549.1.7.6',
27
+ tstInfo: '1.2.840.113549.1.9.16.1.4',
28
+ timestampedData: '1.2.840.113549.1.9.16.1.31',
29
+ contentTypeAttribute: '1.2.840.113549.1.9.3',
30
+ messageDigestAttribute: '1.2.840.113549.1.9.4',
31
+ signingTimeAttribute: '1.2.840.113549.1.9.5',
32
+ signatureTimestampTokenAttribute: '1.2.840.113549.1.9.16.2.14',
33
+ signingCertificateV2Attribute: '1.2.840.113549.1.9.16.2.47',
34
+ subjectKeyIdentifier: '2.5.29.14',
35
+ sha1: '1.3.14.3.2.26',
36
+ sha256: '2.16.840.1.101.3.4.2.1',
37
+ sha384: '2.16.840.1.101.3.4.2.2',
38
+ sha512: '2.16.840.1.101.3.4.2.3',
39
+ rsaEncryption: '1.2.840.113549.1.1.1',
40
+ sha1WithRsa: '1.2.840.113549.1.1.5',
41
+ sha256WithRsa: '1.2.840.113549.1.1.11',
42
+ sha384WithRsa: '1.2.840.113549.1.1.12',
43
+ sha512WithRsa: '1.2.840.113549.1.1.13',
44
+ rsaPss: '1.2.840.113549.1.1.10',
45
+ ecPublicKey: '1.2.840.10045.2.1',
46
+ ecdsaSha256: '1.2.840.10045.4.3.2',
47
+ ecdsaSha384: '1.2.840.10045.4.3.3',
48
+ ecdsaSha512: '1.2.840.10045.4.3.4',
49
+ ed25519: '1.3.101.112',
50
+ prime256v1: '1.2.840.10045.3.1.7',
51
+ secp384r1: '1.3.132.0.34',
52
+ secp521r1: '1.3.132.0.35'
53
+ };
54
+ const OID_LABELS = {
55
+ [OID.data]: 'CMS data',
56
+ [OID.signedData]: 'CMS SignedData',
57
+ [OID.envelopedData]: 'CMS EnvelopedData',
58
+ [OID.digestedData]: 'CMS DigestedData',
59
+ [OID.encryptedData]: 'CMS EncryptedData',
60
+ [OID.tstInfo]: 'RFC 3161 TSTInfo',
61
+ [OID.timestampedData]: 'RFC 5544 TimeStampedData',
62
+ [OID.sha1]: 'SHA-1',
63
+ [OID.sha256]: 'SHA-256',
64
+ [OID.sha384]: 'SHA-384',
65
+ [OID.sha512]: 'SHA-512',
66
+ [OID.rsaEncryption]: 'RSA',
67
+ [OID.sha1WithRsa]: 'SHA-1 with RSA',
68
+ [OID.sha256WithRsa]: 'SHA-256 with RSA',
69
+ [OID.sha384WithRsa]: 'SHA-384 with RSA',
70
+ [OID.sha512WithRsa]: 'SHA-512 with RSA',
71
+ [OID.rsaPss]: 'RSA-PSS',
72
+ [OID.ecPublicKey]: 'EC public key',
73
+ [OID.ecdsaSha256]: 'ECDSA with SHA-256',
74
+ [OID.ecdsaSha384]: 'ECDSA with SHA-384',
75
+ [OID.ecdsaSha512]: 'ECDSA with SHA-512',
76
+ [OID.ed25519]: 'Ed25519'
77
+ };
78
+ const NAME_LABELS = {
79
+ '2.5.4.3': 'CN',
80
+ '2.5.4.4': 'SN',
81
+ '2.5.4.5': 'serialNumber',
82
+ '2.5.4.6': 'C',
83
+ '2.5.4.7': 'L',
84
+ '2.5.4.8': 'ST',
85
+ '2.5.4.10': 'O',
86
+ '2.5.4.11': 'OU',
87
+ '2.5.4.12': 'title',
88
+ '2.5.4.42': 'GN',
89
+ '1.2.840.113549.1.9.1': 'emailAddress'
90
+ };
91
+ export const DEFAULT_SIGNATURE_ASN1_LIMITS = Object.freeze({
92
+ maxInputBytes: 64 * 1024 * 1024,
93
+ maxOriginalContentBytes: 64 * 1024 * 1024,
94
+ maxAsn1Nodes: 25000,
95
+ maxAsn1NestingDepth: 96,
96
+ maxDigestAlgorithms: 64,
97
+ maxCertificates: 512,
98
+ maxCrls: 256,
99
+ maxSigners: 256,
100
+ maxAttributesPerSigner: 256,
101
+ maxEmbeddedContentBytes: 32 * 1024 * 1024,
102
+ maxTimestampEvidenceEntries: 256,
103
+ maxEvidenceChains: 128,
104
+ maxEvidenceTimestampsPerChain: 256,
105
+ maxEvidenceTimestamps: 1024,
106
+ maxReducedHashTreeNodes: 4096
107
+ });
108
+ const normalizeSignatureAsn1Limits = (supplied) => {
109
+ var _a;
110
+ const normalized = { ...DEFAULT_SIGNATURE_ASN1_LIMITS };
111
+ for (const key of Object.keys(normalized)) {
112
+ const value = (_a = supplied === null || supplied === void 0 ? void 0 : supplied[key]) !== null && _a !== void 0 ? _a : normalized[key];
113
+ assertBounds(Number.isSafeInteger(value) && value >= 1 && value <= DEFAULT_SIGNATURE_ASN1_LIMITS[key], `${key} must be an integer between 1 and ${DEFAULT_SIGNATURE_ASN1_LIMITS[key]}.`);
114
+ normalized[key] = value;
115
+ }
116
+ return normalized;
117
+ };
118
+ const toUint8Array = (value) => value instanceof Uint8Array ? value : new Uint8Array(value);
119
+ const bytesToHex = (bytes, separator = '') => Array.from(bytes, (value) => value.toString(16).padStart(2, '0'))
120
+ .join(separator)
121
+ .toUpperCase();
122
+ const equalBytes = (left, right) => {
123
+ if (left.byteLength !== right.byteLength) {
124
+ return false;
125
+ }
126
+ let mismatch = 0;
127
+ for (let index = 0; index < left.byteLength; index += 1) {
128
+ mismatch |= left[index] ^ right[index];
129
+ }
130
+ return mismatch === 0;
131
+ };
132
+ const formatOid = (oid) => (oid ? OID_LABELS[oid] || oid : 'Unknown');
133
+ const assertBounds = (condition, message) => {
134
+ if (!condition) {
135
+ throw new Error(message);
136
+ }
137
+ };
138
+ const parseNode = (state, offset, limit, depth) => {
139
+ assertBounds(depth <= state.maxDepth, `ASN.1 nesting exceeds ${state.maxDepth}.`);
140
+ assertBounds(offset < limit, 'Unexpected end of ASN.1 input.');
141
+ state.nodes += 1;
142
+ assertBounds(state.nodes <= state.maxNodes, `ASN.1 node count exceeds ${state.maxNodes}.`);
143
+ const bytes = state.bytes;
144
+ const start = offset;
145
+ const identifier = bytes[offset++];
146
+ const tagClass = identifier >> 6;
147
+ const constructed = (identifier & 0x20) !== 0;
148
+ let tagNumber = identifier & 0x1f;
149
+ if (tagNumber === 0x1f) {
150
+ tagNumber = 0;
151
+ let octets = 0;
152
+ for (;;) {
153
+ assertBounds(offset < limit, 'Truncated ASN.1 high-tag identifier.');
154
+ const octet = bytes[offset++];
155
+ octets += 1;
156
+ assertBounds(octets <= 6, 'ASN.1 high-tag identifier is too large.');
157
+ tagNumber = tagNumber * 128 + (octet & 0x7f);
158
+ if ((octet & 0x80) === 0) {
159
+ break;
160
+ }
161
+ }
162
+ }
163
+ assertBounds(offset < limit, 'Missing ASN.1 length.');
164
+ const lengthOctet = bytes[offset++];
165
+ let length = 0;
166
+ let indefinite = false;
167
+ if (lengthOctet === 0x80) {
168
+ indefinite = true;
169
+ assertBounds(constructed, 'Primitive ASN.1 values cannot use indefinite length.');
170
+ }
171
+ else if ((lengthOctet & 0x80) === 0) {
172
+ length = lengthOctet;
173
+ }
174
+ else {
175
+ const lengthOctets = lengthOctet & 0x7f;
176
+ assertBounds(lengthOctets > 0 && lengthOctets <= 6, 'Unsupported ASN.1 length width.');
177
+ assertBounds(offset + lengthOctets <= limit, 'Truncated ASN.1 length.');
178
+ for (let index = 0; index < lengthOctets; index += 1) {
179
+ length = length * 256 + bytes[offset++];
180
+ }
181
+ }
182
+ const contentStart = offset;
183
+ const children = [];
184
+ let contentEnd;
185
+ let end;
186
+ if (indefinite) {
187
+ for (;;) {
188
+ assertBounds(offset + 1 < limit, 'Unterminated indefinite-length ASN.1 value.');
189
+ if (bytes[offset] === 0 && bytes[offset + 1] === 0) {
190
+ contentEnd = offset;
191
+ end = offset + 2;
192
+ break;
193
+ }
194
+ const parsed = parseNode(state, offset, limit, depth + 1);
195
+ children.push(parsed.node);
196
+ offset = parsed.next;
197
+ }
198
+ }
199
+ else {
200
+ contentEnd = contentStart + length;
201
+ assertBounds(contentEnd <= limit, 'ASN.1 length extends beyond its parent.');
202
+ end = contentEnd;
203
+ if (constructed) {
204
+ while (offset < contentEnd) {
205
+ const parsed = parseNode(state, offset, contentEnd, depth + 1);
206
+ children.push(parsed.node);
207
+ offset = parsed.next;
208
+ }
209
+ assertBounds(offset === contentEnd, 'ASN.1 child does not end at its parent boundary.');
210
+ }
211
+ }
212
+ return {
213
+ node: {
214
+ tagClass,
215
+ tagNumber,
216
+ constructed,
217
+ start,
218
+ headerEnd: contentStart,
219
+ contentStart,
220
+ contentEnd: contentEnd,
221
+ end: end,
222
+ indefinite,
223
+ children
224
+ },
225
+ next: end
226
+ };
227
+ };
228
+ const parseRoot = (bytes, limits = DEFAULT_SIGNATURE_ASN1_LIMITS) => {
229
+ const state = {
230
+ bytes,
231
+ nodes: 0,
232
+ maxNodes: limits.maxAsn1Nodes,
233
+ maxDepth: limits.maxAsn1NestingDepth
234
+ };
235
+ const { node, next } = parseNode(state, 0, bytes.byteLength, 0);
236
+ for (let offset = next; offset < bytes.byteLength; offset += 1) {
237
+ assertBounds(bytes[offset] === 0, 'Unexpected bytes after the ASN.1 root value.');
238
+ }
239
+ return node;
240
+ };
241
+ const isTag = (node, tagClass, tagNumber) => Boolean(node && node.tagClass === tagClass && node.tagNumber === tagNumber);
242
+ const contentBytes = (bytes, node) => bytes.subarray(node.contentStart, node.contentEnd);
243
+ const fullBytes = (bytes, node) => bytes.subarray(node.start, node.end);
244
+ const decodeOid = (bytes, node) => {
245
+ assertBounds(isTag(node, UNIVERSAL, TAG.oid), 'Expected ASN.1 OBJECT IDENTIFIER.');
246
+ const value = contentBytes(bytes, node);
247
+ assertBounds(value.byteLength > 0, 'Empty ASN.1 OBJECT IDENTIFIER.');
248
+ const subIdentifiers = [];
249
+ let current = 0;
250
+ let continued = false;
251
+ for (const octet of value) {
252
+ current = current * 128 + (octet & 0x7f);
253
+ assertBounds(Number.isSafeInteger(current), 'ASN.1 OBJECT IDENTIFIER arc is too large.');
254
+ continued = (octet & 0x80) !== 0;
255
+ if (!continued) {
256
+ subIdentifiers.push(current);
257
+ current = 0;
258
+ }
259
+ }
260
+ assertBounds(!continued, 'Truncated ASN.1 OBJECT IDENTIFIER.');
261
+ const first = subIdentifiers.shift() || 0;
262
+ const firstArc = first < 40 ? 0 : first < 80 ? 1 : 2;
263
+ const secondArc = first - firstArc * 40;
264
+ return [firstArc, secondArc, ...subIdentifiers].map(String).join('.');
265
+ };
266
+ const integerBytes = (bytes, node) => {
267
+ assertBounds(isTag(node, UNIVERSAL, TAG.integer), 'Expected ASN.1 INTEGER.');
268
+ return contentBytes(bytes, node);
269
+ };
270
+ const positiveIntegerBytes = (bytes, node) => {
271
+ let value = integerBytes(bytes, node);
272
+ while (value.byteLength > 1 && value[0] === 0) {
273
+ value = value.subarray(1);
274
+ }
275
+ return value;
276
+ };
277
+ const integerHex = (bytes, node) => bytesToHex(positiveIntegerBytes(bytes, node));
278
+ const integerNumber = (bytes, node) => {
279
+ const value = positiveIntegerBytes(bytes, node);
280
+ let result = 0;
281
+ for (const octet of value) {
282
+ result = result * 256 + octet;
283
+ if (!Number.isSafeInteger(result)) {
284
+ return undefined;
285
+ }
286
+ }
287
+ return result;
288
+ };
289
+ const decodeBoolean = (bytes, node) => {
290
+ assertBounds(isTag(node, UNIVERSAL, TAG.boolean), 'Expected ASN.1 BOOLEAN.');
291
+ const value = contentBytes(bytes, node);
292
+ return value.some((octet) => octet !== 0);
293
+ };
294
+ const decodeAscii = (value) => String.fromCharCode(...value);
295
+ const decodeBmpString = (value) => {
296
+ let result = '';
297
+ for (let index = 0; index + 1 < value.byteLength; index += 2) {
298
+ result += String.fromCharCode((value[index] << 8) | value[index + 1]);
299
+ }
300
+ return result;
301
+ };
302
+ const decodeUniversalString = (value) => {
303
+ let result = '';
304
+ for (let index = 0; index + 3 < value.byteLength; index += 4) {
305
+ const codePoint = value[index] * 0x1000000 +
306
+ (value[index + 1] << 16) +
307
+ (value[index + 2] << 8) +
308
+ value[index + 3];
309
+ result += String.fromCodePoint(codePoint);
310
+ }
311
+ return result;
312
+ };
313
+ const decodeString = (bytes, node) => {
314
+ const value = contentBytes(bytes, node);
315
+ switch (node.tagNumber) {
316
+ case TAG.utf8String:
317
+ return new TextDecoder('utf-8', { fatal: false }).decode(value);
318
+ case TAG.bmpString:
319
+ return decodeBmpString(value);
320
+ case TAG.universalString:
321
+ return decodeUniversalString(value);
322
+ case TAG.printableString:
323
+ case TAG.teletexString:
324
+ case TAG.ia5String:
325
+ return decodeAscii(value);
326
+ default:
327
+ return decodeAscii(value);
328
+ }
329
+ };
330
+ const parseTimeString = (value, generalized) => {
331
+ const match = generalized
332
+ ? /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:\.(\d+))?(Z|[+-]\d{4})$/.exec(value)
333
+ : /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(Z|[+-]\d{4})$/.exec(value);
334
+ if (!match) {
335
+ return value;
336
+ }
337
+ const year = generalized
338
+ ? Number(match[1])
339
+ : (Number(match[1]) >= 50 ? 1900 : 2000) + Number(match[1]);
340
+ const baseIndex = generalized ? 2 : 2;
341
+ const month = Number(match[baseIndex]);
342
+ const day = Number(match[baseIndex + 1]);
343
+ const hour = Number(match[baseIndex + 2]);
344
+ const minute = Number(match[baseIndex + 3]);
345
+ const second = Number(match[baseIndex + 4]);
346
+ const fraction = generalized ? match[7] : undefined;
347
+ const zone = generalized ? match[8] : match[7];
348
+ const millis = fraction ? Number(`0.${fraction}`) * 1000 : 0;
349
+ let timestamp = Date.UTC(year, month - 1, day, hour, minute, second, millis);
350
+ if (zone !== 'Z') {
351
+ const sign = zone[0] === '+' ? 1 : -1;
352
+ const offsetMinutes = Number(zone.slice(1, 3)) * 60 + Number(zone.slice(3, 5));
353
+ timestamp -= sign * offsetMinutes * 60000;
354
+ }
355
+ const date = new Date(timestamp);
356
+ return Number.isNaN(date.getTime()) ? value : date.toISOString();
357
+ };
358
+ const decodeTime = (bytes, node) => {
359
+ const raw = decodeAscii(contentBytes(bytes, node));
360
+ if (isTag(node, UNIVERSAL, TAG.utcTime)) {
361
+ return parseTimeString(raw, false);
362
+ }
363
+ if (isTag(node, UNIVERSAL, TAG.generalizedTime)) {
364
+ return parseTimeString(raw, true);
365
+ }
366
+ return raw;
367
+ };
368
+ const decodeName = (bytes, node) => {
369
+ const rdns = [];
370
+ for (const set of node.children) {
371
+ for (const attribute of set.children) {
372
+ const [oidNode, valueNode] = attribute.children;
373
+ if (!oidNode || !valueNode || !isTag(oidNode, UNIVERSAL, TAG.oid)) {
374
+ continue;
375
+ }
376
+ const oid = decodeOid(bytes, oidNode);
377
+ const label = NAME_LABELS[oid] || oid;
378
+ rdns.push(`${label}=${decodeString(bytes, valueNode)}`);
379
+ }
380
+ }
381
+ return rdns.join(', ');
382
+ };
383
+ const algorithmOid = (bytes, node) => {
384
+ const oidNode = node === null || node === void 0 ? void 0 : node.children.find((child) => isTag(child, UNIVERSAL, TAG.oid));
385
+ return oidNode ? decodeOid(bytes, oidNode) : undefined;
386
+ };
387
+ const curveNameForOid = (oid) => {
388
+ if (oid === OID.prime256v1)
389
+ return 'P-256';
390
+ if (oid === OID.secp384r1)
391
+ return 'P-384';
392
+ if (oid === OID.secp521r1)
393
+ return 'P-521';
394
+ return undefined;
395
+ };
396
+ const collectOctetString = (bytes, node, maxBytes = DEFAULT_SIGNATURE_ASN1_LIMITS.maxEmbeddedContentBytes) => {
397
+ if (isTag(node, UNIVERSAL, TAG.octetString) && !node.constructed) {
398
+ const value = contentBytes(bytes, node);
399
+ assertBounds(value.byteLength <= maxBytes, `Embedded content exceeds the ${maxBytes}-byte boundary.`);
400
+ return value;
401
+ }
402
+ const parts = node.children
403
+ .filter((child) => isTag(child, UNIVERSAL, TAG.octetString) || child.tagClass === CONTEXT)
404
+ .map((child) => collectOctetString(bytes, child, maxBytes));
405
+ if (!parts.length) {
406
+ return new Uint8Array();
407
+ }
408
+ const size = parts.reduce((sum, part) => sum + part.byteLength, 0);
409
+ assertBounds(size <= maxBytes, `Embedded content exceeds the ${maxBytes}-byte boundary.`);
410
+ const output = new Uint8Array(size);
411
+ let offset = 0;
412
+ for (const part of parts) {
413
+ output.set(part, offset);
414
+ offset += part.byteLength;
415
+ }
416
+ return output;
417
+ };
418
+ const parseExtensions = (bytes, tbsChildren) => {
419
+ var _a;
420
+ const extensionsContainer = tbsChildren.find((child) => isTag(child, CONTEXT, 3));
421
+ const extensions = ((_a = extensionsContainer === null || extensionsContainer === void 0 ? void 0 : extensionsContainer.children[0]) === null || _a === void 0 ? void 0 : _a.children) || [];
422
+ let subjectKeyIdentifier;
423
+ for (const extension of extensions) {
424
+ const oidNode = extension.children.find((child) => isTag(child, UNIVERSAL, TAG.oid));
425
+ const valueNode = [...extension.children]
426
+ .reverse()
427
+ .find((child) => isTag(child, UNIVERSAL, TAG.octetString));
428
+ if (!oidNode || !valueNode) {
429
+ continue;
430
+ }
431
+ const oid = decodeOid(bytes, oidNode);
432
+ if (oid === OID.subjectKeyIdentifier) {
433
+ try {
434
+ const innerBytes = contentBytes(bytes, valueNode);
435
+ const innerNode = parseRoot(innerBytes);
436
+ if (isTag(innerNode, UNIVERSAL, TAG.octetString)) {
437
+ subjectKeyIdentifier = bytesToHex(contentBytes(innerBytes, innerNode));
438
+ }
439
+ }
440
+ catch {
441
+ subjectKeyIdentifier = bytesToHex(contentBytes(bytes, valueNode));
442
+ }
443
+ }
444
+ }
445
+ return { subjectKeyIdentifier };
446
+ };
447
+ const parseCertificate = (bytes, node, index) => {
448
+ var _a;
449
+ if (!isTag(node, UNIVERSAL, TAG.sequence) || node.children.length < 3) {
450
+ return undefined;
451
+ }
452
+ const [tbs, outerSignatureAlgorithm] = node.children;
453
+ if (!tbs || !isTag(tbs, UNIVERSAL, TAG.sequence)) {
454
+ return undefined;
455
+ }
456
+ const children = tbs.children;
457
+ let cursor = 0;
458
+ let version = 1;
459
+ if (isTag(children[cursor], CONTEXT, 0)) {
460
+ const versionNode = (_a = children[cursor]) === null || _a === void 0 ? void 0 : _a.children[0];
461
+ if (versionNode && isTag(versionNode, UNIVERSAL, TAG.integer)) {
462
+ version = (integerNumber(bytes, versionNode) || 0) + 1;
463
+ }
464
+ cursor += 1;
465
+ }
466
+ const serialNode = children[cursor++];
467
+ const tbsSignatureAlgorithm = children[cursor++];
468
+ const issuerNode = children[cursor++];
469
+ const validityNode = children[cursor++];
470
+ const subjectNode = children[cursor++];
471
+ const spkiNode = children[cursor];
472
+ if (!serialNode || !issuerNode || !validityNode || !subjectNode || !spkiNode) {
473
+ return undefined;
474
+ }
475
+ const publicKeyAlgorithmNode = spkiNode.children[0];
476
+ const publicKeyAlgorithmOid = algorithmOid(bytes, publicKeyAlgorithmNode);
477
+ const curveOidNode = publicKeyAlgorithmNode === null || publicKeyAlgorithmNode === void 0 ? void 0 : publicKeyAlgorithmNode.children[1];
478
+ const publicKeyCurveOid = curveOidNode && isTag(curveOidNode, UNIVERSAL, TAG.oid)
479
+ ? decodeOid(bytes, curveOidNode)
480
+ : undefined;
481
+ const signatureAlgorithmOid = algorithmOid(bytes, outerSignatureAlgorithm) || algorithmOid(bytes, tbsSignatureAlgorithm);
482
+ const { subjectKeyIdentifier } = parseExtensions(bytes, children);
483
+ const issuerDer = bytesToHex(fullBytes(bytes, issuerNode));
484
+ const serialDer = bytesToHex(positiveIntegerBytes(bytes, serialNode));
485
+ const validity = validityNode.children.filter((child) => isTag(child, UNIVERSAL, TAG.utcTime) || isTag(child, UNIVERSAL, TAG.generalizedTime));
486
+ return {
487
+ node,
488
+ summary: {
489
+ index,
490
+ version,
491
+ serialNumber: serialDer,
492
+ subject: decodeName(bytes, subjectNode),
493
+ issuer: decodeName(bytes, issuerNode),
494
+ notBefore: validity[0] ? decodeTime(bytes, validity[0]) : undefined,
495
+ notAfter: validity[1] ? decodeTime(bytes, validity[1]) : undefined,
496
+ signatureAlgorithmOid,
497
+ signatureAlgorithm: formatOid(signatureAlgorithmOid),
498
+ publicKeyAlgorithmOid,
499
+ publicKeyAlgorithm: formatOid(publicKeyAlgorithmOid),
500
+ publicKeyCurveOid,
501
+ subjectKeyIdentifier
502
+ },
503
+ issuerDer,
504
+ serialDer,
505
+ subjectKeyIdentifier,
506
+ spki: fullBytes(bytes, spkiNode),
507
+ publicKeyAlgorithmOid,
508
+ publicKeyCurveName: curveNameForOid(publicKeyCurveOid)
509
+ };
510
+ };
511
+ const parseAttribute = (bytes, node) => {
512
+ const [oidNode, valuesNode] = node.children;
513
+ if (!oidNode || !valuesNode || !isTag(oidNode, UNIVERSAL, TAG.oid)) {
514
+ return undefined;
515
+ }
516
+ return {
517
+ oid: decodeOid(bytes, oidNode),
518
+ values: valuesNode.children
519
+ };
520
+ };
521
+ const parseSigner = (bytes, node, index, limits) => {
522
+ if (!isTag(node, UNIVERSAL, TAG.sequence)) {
523
+ return undefined;
524
+ }
525
+ const children = node.children;
526
+ let cursor = 0;
527
+ const versionNode = children[cursor++];
528
+ const sidNode = children[cursor++];
529
+ const digestAlgorithmNode = children[cursor++];
530
+ if (!versionNode || !sidNode || !digestAlgorithmNode) {
531
+ return undefined;
532
+ }
533
+ const signedAttributesNode = isTag(children[cursor], CONTEXT, 0) ? children[cursor++] : undefined;
534
+ const signatureAlgorithmNode = children[cursor++];
535
+ const signatureNode = children[cursor++];
536
+ const unsignedAttributesNode = isTag(children[cursor], CONTEXT, 1) ? children[cursor] : undefined;
537
+ assertBounds(((signedAttributesNode === null || signedAttributesNode === void 0 ? void 0 : signedAttributesNode.children.length) || 0) <= limits.maxAttributesPerSigner, `CMS signed attribute count exceeds ${limits.maxAttributesPerSigner}.`);
538
+ assertBounds(((unsignedAttributesNode === null || unsignedAttributesNode === void 0 ? void 0 : unsignedAttributesNode.children.length) || 0) <= limits.maxAttributesPerSigner, `CMS unsigned attribute count exceeds ${limits.maxAttributesPerSigner}.`);
539
+ let issuerDer;
540
+ let serialDer;
541
+ let subjectKeyIdentifier;
542
+ let sid = 'Unknown signer identifier';
543
+ if (isTag(sidNode, UNIVERSAL, TAG.sequence)) {
544
+ const issuerNode = sidNode.children[0];
545
+ const serialNode = sidNode.children[1];
546
+ if (issuerNode && serialNode) {
547
+ issuerDer = bytesToHex(fullBytes(bytes, issuerNode));
548
+ serialDer = bytesToHex(positiveIntegerBytes(bytes, serialNode));
549
+ sid = `${decodeName(bytes, issuerNode)} · ${serialDer}`;
550
+ }
551
+ }
552
+ else if (isTag(sidNode, CONTEXT, 0)) {
553
+ subjectKeyIdentifier = bytesToHex(contentBytes(bytes, sidNode));
554
+ sid = `Subject key identifier ${subjectKeyIdentifier}`;
555
+ }
556
+ let signingTime;
557
+ let messageDigest;
558
+ let contentTypeOid;
559
+ let signingCertificateV2 = false;
560
+ for (const child of (signedAttributesNode === null || signedAttributesNode === void 0 ? void 0 : signedAttributesNode.children) || []) {
561
+ const attribute = parseAttribute(bytes, child);
562
+ const value = attribute === null || attribute === void 0 ? void 0 : attribute.values[0];
563
+ if (!attribute || !value) {
564
+ continue;
565
+ }
566
+ if (attribute.oid === OID.signingTimeAttribute &&
567
+ (isTag(value, UNIVERSAL, TAG.utcTime) || isTag(value, UNIVERSAL, TAG.generalizedTime))) {
568
+ signingTime = decodeTime(bytes, value);
569
+ }
570
+ else if (attribute.oid === OID.messageDigestAttribute &&
571
+ isTag(value, UNIVERSAL, TAG.octetString)) {
572
+ messageDigest = bytesToHex(contentBytes(bytes, value));
573
+ }
574
+ else if (attribute.oid === OID.contentTypeAttribute && isTag(value, UNIVERSAL, TAG.oid)) {
575
+ contentTypeOid = decodeOid(bytes, value);
576
+ }
577
+ else if (attribute.oid === OID.signingCertificateV2Attribute) {
578
+ signingCertificateV2 = true;
579
+ }
580
+ }
581
+ let signatureTimestampTokens = 0;
582
+ for (const child of (unsignedAttributesNode === null || unsignedAttributesNode === void 0 ? void 0 : unsignedAttributesNode.children) || []) {
583
+ const attribute = parseAttribute(bytes, child);
584
+ if ((attribute === null || attribute === void 0 ? void 0 : attribute.oid) === OID.signatureTimestampTokenAttribute) {
585
+ signatureTimestampTokens += attribute.values.length;
586
+ }
587
+ }
588
+ const digestAlgorithmOid = algorithmOid(bytes, digestAlgorithmNode);
589
+ const signatureAlgorithmOid = algorithmOid(bytes, signatureAlgorithmNode);
590
+ return {
591
+ summary: {
592
+ index,
593
+ sid,
594
+ digestAlgorithmOid,
595
+ digestAlgorithm: formatOid(digestAlgorithmOid),
596
+ signatureAlgorithmOid,
597
+ signatureAlgorithm: formatOid(signatureAlgorithmOid),
598
+ signingTime,
599
+ messageDigest,
600
+ contentTypeOid,
601
+ signatureTimestampTokens,
602
+ signingCertificateV2
603
+ },
604
+ issuerDer,
605
+ serialDer,
606
+ subjectKeyIdentifier,
607
+ digestAlgorithmNode,
608
+ signatureAlgorithmNode,
609
+ signedAttributesNode,
610
+ signature: signatureNode && isTag(signatureNode, UNIVERSAL, TAG.octetString)
611
+ ? contentBytes(bytes, signatureNode)
612
+ : undefined
613
+ };
614
+ };
615
+ const parseCmsContentInfo = (bytes, root, limits) => {
616
+ assertBounds(isTag(root, UNIVERSAL, TAG.sequence), 'CMS ContentInfo must be an ASN.1 SEQUENCE.');
617
+ const oidNode = root.children[0];
618
+ assertBounds(oidNode && isTag(oidNode, UNIVERSAL, TAG.oid), 'CMS ContentInfo is missing contentType.');
619
+ const contentTypeOid = decodeOid(bytes, oidNode);
620
+ if (contentTypeOid !== OID.signedData) {
621
+ return {
622
+ contentTypeOid,
623
+ digestAlgorithmOids: [],
624
+ certificates: [],
625
+ crlCount: 0,
626
+ signers: []
627
+ };
628
+ }
629
+ const explicitContent = root.children.find((child) => isTag(child, CONTEXT, 0));
630
+ const signedData = explicitContent === null || explicitContent === void 0 ? void 0 : explicitContent.children[0];
631
+ assertBounds(signedData && isTag(signedData, UNIVERSAL, TAG.sequence), 'CMS SignedData payload is missing.');
632
+ const children = signedData.children;
633
+ const digestAlgorithmsNode = children[1];
634
+ const encapContentInfo = children[2];
635
+ assertBounds(digestAlgorithmsNode && encapContentInfo, 'CMS SignedData is truncated.');
636
+ assertBounds(digestAlgorithmsNode.children.length <= limits.maxDigestAlgorithms, `CMS digest algorithm count exceeds ${limits.maxDigestAlgorithms}.`);
637
+ const digestAlgorithmOids = digestAlgorithmsNode.children
638
+ .map((child) => algorithmOid(bytes, child))
639
+ .filter((value) => Boolean(value));
640
+ const signedContentTypeNode = encapContentInfo.children[0];
641
+ const signedContentTypeOid = signedContentTypeNode && isTag(signedContentTypeNode, UNIVERSAL, TAG.oid)
642
+ ? decodeOid(bytes, signedContentTypeNode)
643
+ : undefined;
644
+ const eContentNode = encapContentInfo.children.find((child) => isTag(child, CONTEXT, 0));
645
+ const embeddedContent = eContentNode
646
+ ? collectOctetString(bytes, eContentNode, limits.maxEmbeddedContentBytes)
647
+ : undefined;
648
+ const certificatesNode = children.find((child) => isTag(child, CONTEXT, 0));
649
+ const crlsNode = children.find((child) => isTag(child, CONTEXT, 1));
650
+ const signerInfosNode = [...children].reverse().find((child) => isTag(child, UNIVERSAL, TAG.set));
651
+ assertBounds(((certificatesNode === null || certificatesNode === void 0 ? void 0 : certificatesNode.children.length) || 0) <= limits.maxCertificates, `CMS certificate count exceeds ${limits.maxCertificates}.`);
652
+ assertBounds(((crlsNode === null || crlsNode === void 0 ? void 0 : crlsNode.children.length) || 0) <= limits.maxCrls, `CMS CRL count exceeds ${limits.maxCrls}.`);
653
+ assertBounds(((signerInfosNode === null || signerInfosNode === void 0 ? void 0 : signerInfosNode.children.length) || 0) <= limits.maxSigners, `CMS signer count exceeds ${limits.maxSigners}.`);
654
+ const certificates = ((certificatesNode === null || certificatesNode === void 0 ? void 0 : certificatesNode.children) || [])
655
+ .map((child, index) => parseCertificate(bytes, child, index))
656
+ .filter((value) => Boolean(value));
657
+ const signers = ((signerInfosNode === null || signerInfosNode === void 0 ? void 0 : signerInfosNode.children) || [])
658
+ .map((child, index) => parseSigner(bytes, child, index, limits))
659
+ .filter((value) => Boolean(value));
660
+ return {
661
+ contentTypeOid,
662
+ signedContentTypeOid,
663
+ digestAlgorithmOids,
664
+ embeddedContent,
665
+ certificates,
666
+ crlCount: (crlsNode === null || crlsNode === void 0 ? void 0 : crlsNode.children.length) || 0,
667
+ signers
668
+ };
669
+ };
670
+ const subtleCrypto = () => { var _a; return (_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle; };
671
+ const cryptoBytes = (bytes) => new Uint8Array(bytes);
672
+ const digestNameForOid = (oid) => {
673
+ if (oid === OID.sha1)
674
+ return 'SHA-1';
675
+ if (oid === OID.sha256)
676
+ return 'SHA-256';
677
+ if (oid === OID.sha384)
678
+ return 'SHA-384';
679
+ if (oid === OID.sha512)
680
+ return 'SHA-512';
681
+ return undefined;
682
+ };
683
+ const digestBytes = async (oid, bytes) => {
684
+ const subtle = subtleCrypto();
685
+ const algorithm = digestNameForOid(oid);
686
+ if (!subtle || !algorithm) {
687
+ return undefined;
688
+ }
689
+ return new Uint8Array(await subtle.digest(algorithm, cryptoBytes(bytes)));
690
+ };
691
+ const pssSaltLength = (bytes, algorithmNode, hashOid) => {
692
+ const defaultLength = hashOid === OID.sha512 ? 64 : hashOid === OID.sha384 ? 48 : hashOid === OID.sha1 ? 20 : 32;
693
+ const params = algorithmNode === null || algorithmNode === void 0 ? void 0 : algorithmNode.children[1];
694
+ const saltContainer = params === null || params === void 0 ? void 0 : params.children.find((child) => isTag(child, CONTEXT, 2));
695
+ const saltNode = saltContainer === null || saltContainer === void 0 ? void 0 : saltContainer.children[0];
696
+ return saltNode && isTag(saltNode, UNIVERSAL, TAG.integer)
697
+ ? integerNumber(bytes, saltNode) || defaultLength
698
+ : defaultLength;
699
+ };
700
+ const signatureHashOid = (signatureOid, digestOid) => {
701
+ if (signatureOid === OID.sha1WithRsa)
702
+ return OID.sha1;
703
+ if (signatureOid === OID.sha256WithRsa || signatureOid === OID.ecdsaSha256)
704
+ return OID.sha256;
705
+ if (signatureOid === OID.sha384WithRsa || signatureOid === OID.ecdsaSha384)
706
+ return OID.sha384;
707
+ if (signatureOid === OID.sha512WithRsa || signatureOid === OID.ecdsaSha512)
708
+ return OID.sha512;
709
+ return digestOid;
710
+ };
711
+ const signedAttributesBytes = (bytes, node) => {
712
+ const raw = new Uint8Array(fullBytes(bytes, node));
713
+ if (raw.byteLength && raw[0] === 0xa0) {
714
+ raw[0] = 0x31;
715
+ }
716
+ return raw;
717
+ };
718
+ const ecdsaDerToRaw = (signature, coordinateLength) => {
719
+ try {
720
+ const root = parseRoot(signature);
721
+ if (!isTag(root, UNIVERSAL, TAG.sequence) || root.children.length < 2) {
722
+ return signature;
723
+ }
724
+ const normalize = (node) => {
725
+ let value = positiveIntegerBytes(signature, node);
726
+ if (value.byteLength > coordinateLength) {
727
+ value = value.subarray(value.byteLength - coordinateLength);
728
+ }
729
+ const output = new Uint8Array(coordinateLength);
730
+ output.set(value, coordinateLength - value.byteLength);
731
+ return output;
732
+ };
733
+ const raw = new Uint8Array(coordinateLength * 2);
734
+ raw.set(normalize(root.children[0]), 0);
735
+ raw.set(normalize(root.children[1]), coordinateLength);
736
+ return raw;
737
+ }
738
+ catch {
739
+ return signature;
740
+ }
741
+ };
742
+ const findSignerCertificate = (signer, certificates) => {
743
+ if (signer.subjectKeyIdentifier) {
744
+ return certificates.find((certificate) => certificate.subjectKeyIdentifier === signer.subjectKeyIdentifier);
745
+ }
746
+ if (signer.issuerDer && signer.serialDer) {
747
+ return certificates.find((certificate) => certificate.issuerDer === signer.issuerDer && certificate.serialDer === signer.serialDer);
748
+ }
749
+ return undefined;
750
+ };
751
+ const verifySigner = async (bytes, signer, certificates, content) => {
752
+ var _a;
753
+ const summary = signer.summary;
754
+ const digestOid = summary.digestAlgorithmOid;
755
+ if (content && summary.messageDigest) {
756
+ const digest = await digestBytes(digestOid, content);
757
+ if (digest) {
758
+ summary.digestMatches = equalBytes(digest, Uint8Array.from(((_a = summary.messageDigest.match(/.{2}/g)) === null || _a === void 0 ? void 0 : _a.map((value) => Number.parseInt(value, 16))) || []));
759
+ }
760
+ }
761
+ const certificate = findSignerCertificate(signer, certificates);
762
+ if (!certificate) {
763
+ summary.verificationError = 'Matching signer certificate is not included.';
764
+ return;
765
+ }
766
+ summary.certificateIndex = certificate.summary.index;
767
+ if (!signer.signature) {
768
+ summary.verificationError = 'Signature value is missing.';
769
+ return;
770
+ }
771
+ const signedInput = signer.signedAttributesNode
772
+ ? signedAttributesBytes(bytes, signer.signedAttributesNode)
773
+ : content;
774
+ if (!signedInput) {
775
+ summary.verificationError = 'Original content is required to verify this signature.';
776
+ return;
777
+ }
778
+ const subtle = subtleCrypto();
779
+ if (!subtle) {
780
+ summary.verificationError = 'Web Crypto is unavailable.';
781
+ return;
782
+ }
783
+ const signatureOid = summary.signatureAlgorithmOid;
784
+ const hashOid = signatureHashOid(signatureOid, digestOid);
785
+ const hash = digestNameForOid(hashOid);
786
+ try {
787
+ if (certificate.publicKeyAlgorithmOid === OID.rsaEncryption ||
788
+ signatureOid === OID.rsaEncryption ||
789
+ signatureOid === OID.sha1WithRsa ||
790
+ signatureOid === OID.sha256WithRsa ||
791
+ signatureOid === OID.sha384WithRsa ||
792
+ signatureOid === OID.sha512WithRsa) {
793
+ assertBounds(hash, `Unsupported RSA digest algorithm ${hashOid || 'unknown'}.`);
794
+ const algorithm = { name: 'RSASSA-PKCS1-v1_5', hash };
795
+ const key = await subtle.importKey('spki', cryptoBytes(certificate.spki), algorithm, false, [
796
+ 'verify'
797
+ ]);
798
+ summary.cryptographicSignatureValid = await subtle.verify({ name: 'RSASSA-PKCS1-v1_5' }, key, cryptoBytes(signer.signature), cryptoBytes(signedInput));
799
+ return;
800
+ }
801
+ if (signatureOid === OID.rsaPss) {
802
+ assertBounds(hash, `Unsupported RSA-PSS digest algorithm ${hashOid || 'unknown'}.`);
803
+ const algorithm = { name: 'RSA-PSS', hash };
804
+ const key = await subtle.importKey('spki', cryptoBytes(certificate.spki), algorithm, false, [
805
+ 'verify'
806
+ ]);
807
+ summary.cryptographicSignatureValid = await subtle.verify({
808
+ name: 'RSA-PSS',
809
+ saltLength: pssSaltLength(bytes, signer.signatureAlgorithmNode, hashOid)
810
+ }, key, cryptoBytes(signer.signature), cryptoBytes(signedInput));
811
+ return;
812
+ }
813
+ if (certificate.publicKeyAlgorithmOid === OID.ecPublicKey ||
814
+ signatureOid === OID.ecdsaSha256 ||
815
+ signatureOid === OID.ecdsaSha384 ||
816
+ signatureOid === OID.ecdsaSha512) {
817
+ assertBounds(hash, `Unsupported ECDSA digest algorithm ${hashOid || 'unknown'}.`);
818
+ assertBounds(certificate.publicKeyCurveName, 'Unsupported or missing EC named curve.');
819
+ const key = await subtle.importKey('spki', cryptoBytes(certificate.spki), { name: 'ECDSA', namedCurve: certificate.publicKeyCurveName }, false, ['verify']);
820
+ const coordinateLength = certificate.publicKeyCurveName === 'P-521'
821
+ ? 66
822
+ : certificate.publicKeyCurveName === 'P-384'
823
+ ? 48
824
+ : 32;
825
+ summary.cryptographicSignatureValid = await subtle.verify({ name: 'ECDSA', hash }, key, cryptoBytes(ecdsaDerToRaw(signer.signature, coordinateLength)), cryptoBytes(signedInput));
826
+ return;
827
+ }
828
+ if (certificate.publicKeyAlgorithmOid === OID.ed25519 || signatureOid === OID.ed25519) {
829
+ const key = await subtle.importKey('spki', cryptoBytes(certificate.spki), { name: 'Ed25519' }, false, ['verify']);
830
+ summary.cryptographicSignatureValid = await subtle.verify('Ed25519', key, cryptoBytes(signer.signature), cryptoBytes(signedInput));
831
+ return;
832
+ }
833
+ summary.verificationError = `Unsupported signature algorithm ${signatureOid || 'unknown'}.`;
834
+ }
835
+ catch (error) {
836
+ summary.verificationError = error instanceof Error ? error.message : String(error);
837
+ }
838
+ };
839
+ const enrichCertificateFingerprints = async (certificates, bytes) => {
840
+ const subtle = subtleCrypto();
841
+ if (!subtle) {
842
+ return;
843
+ }
844
+ await Promise.all(certificates.map(async (certificate) => {
845
+ try {
846
+ const digest = new Uint8Array(await subtle.digest('SHA-256', cryptoBytes(fullBytes(bytes, certificate.node))));
847
+ certificate.summary.fingerprintSha256 = bytesToHex(digest, ':');
848
+ }
849
+ catch {
850
+ // Fingerprints are presentation metadata; parsing remains useful without one.
851
+ }
852
+ }));
853
+ };
854
+ const parseMessageImprint = (bytes, node) => {
855
+ if (!node || !isTag(node, UNIVERSAL, TAG.sequence)) {
856
+ return {};
857
+ }
858
+ const algorithm = node.children[0];
859
+ const digest = node.children[1];
860
+ const algorithmOidValue = algorithmOid(bytes, algorithm);
861
+ return {
862
+ algorithmOid: algorithmOidValue,
863
+ algorithm: formatOid(algorithmOidValue),
864
+ digest: digest && isTag(digest, UNIVERSAL, TAG.octetString)
865
+ ? bytesToHex(contentBytes(bytes, digest))
866
+ : undefined
867
+ };
868
+ };
869
+ const parseAccuracy = (bytes, node) => {
870
+ const accuracy = {};
871
+ for (const child of node.children) {
872
+ if (isTag(child, UNIVERSAL, TAG.integer)) {
873
+ accuracy.seconds = integerNumber(bytes, child);
874
+ }
875
+ else if (isTag(child, CONTEXT, 0)) {
876
+ const value = child.children[0];
877
+ accuracy.millis = value
878
+ ? integerNumber(bytes, value)
879
+ : Number.parseInt(bytesToHex(contentBytes(bytes, child)), 16);
880
+ }
881
+ else if (isTag(child, CONTEXT, 1)) {
882
+ const value = child.children[0];
883
+ accuracy.micros = value
884
+ ? integerNumber(bytes, value)
885
+ : Number.parseInt(bytesToHex(contentBytes(bytes, child)), 16);
886
+ }
887
+ }
888
+ return accuracy;
889
+ };
890
+ const decodeGeneralName = (bytes, node) => {
891
+ if (node.tagClass !== CONTEXT) {
892
+ return bytesToHex(contentBytes(bytes, node));
893
+ }
894
+ if (node.tagNumber === 4 && node.children[0]) {
895
+ return decodeName(bytes, node.children[0]);
896
+ }
897
+ if ([1, 2, 6].includes(node.tagNumber)) {
898
+ return decodeAscii(contentBytes(bytes, node));
899
+ }
900
+ if (node.tagNumber === 8) {
901
+ try {
902
+ return decodeOid(bytes, { ...node, tagClass: UNIVERSAL, tagNumber: TAG.oid });
903
+ }
904
+ catch {
905
+ return bytesToHex(contentBytes(bytes, node));
906
+ }
907
+ }
908
+ return bytesToHex(contentBytes(bytes, node));
909
+ };
910
+ const parseTstInfo = async (bytes, originalContent, limits) => {
911
+ const root = parseRoot(bytes, limits);
912
+ assertBounds(isTag(root, UNIVERSAL, TAG.sequence), 'RFC 3161 TSTInfo must be a SEQUENCE.');
913
+ const children = root.children;
914
+ const versionNode = children[0];
915
+ const policyNode = children[1];
916
+ const imprintNode = children[2];
917
+ const serialNode = children[3];
918
+ const generationTimeNode = children[4];
919
+ const imprint = parseMessageImprint(bytes, imprintNode);
920
+ const summary = {
921
+ version: versionNode ? integerNumber(bytes, versionNode) : undefined,
922
+ policyOid: policyNode && isTag(policyNode, UNIVERSAL, TAG.oid)
923
+ ? decodeOid(bytes, policyNode)
924
+ : undefined,
925
+ serialNumber: serialNode ? integerHex(bytes, serialNode) : undefined,
926
+ generationTime: generationTimeNode ? decodeTime(bytes, generationTimeNode) : undefined,
927
+ messageImprintAlgorithmOid: imprint.algorithmOid,
928
+ messageImprintAlgorithm: imprint.algorithm,
929
+ messageImprint: imprint.digest
930
+ };
931
+ for (const child of children.slice(5)) {
932
+ if (isTag(child, UNIVERSAL, TAG.sequence) && summary.accuracy === undefined) {
933
+ summary.accuracy = parseAccuracy(bytes, child);
934
+ }
935
+ else if (isTag(child, UNIVERSAL, TAG.boolean)) {
936
+ summary.ordering = decodeBoolean(bytes, child);
937
+ }
938
+ else if (isTag(child, UNIVERSAL, TAG.integer) && !summary.nonce) {
939
+ summary.nonce = integerHex(bytes, child);
940
+ }
941
+ else if (isTag(child, CONTEXT, 0)) {
942
+ const generalName = child.children[0] || child;
943
+ summary.tsa = decodeGeneralName(bytes, generalName);
944
+ }
945
+ }
946
+ if (originalContent && imprint.algorithmOid && imprint.digest) {
947
+ const digest = await digestBytes(imprint.algorithmOid, originalContent);
948
+ if (digest) {
949
+ summary.messageImprintMatchesOriginal = bytesToHex(digest) === imprint.digest;
950
+ }
951
+ }
952
+ return summary;
953
+ };
954
+ const parseTimestampRequest = async (bytes, root, originalContent) => {
955
+ const children = root.children;
956
+ const versionNode = children[0];
957
+ const imprint = parseMessageImprint(bytes, children[1]);
958
+ const summary = {
959
+ version: versionNode ? integerNumber(bytes, versionNode) : undefined,
960
+ messageImprintAlgorithmOid: imprint.algorithmOid,
961
+ messageImprintAlgorithm: imprint.algorithm,
962
+ messageImprint: imprint.digest
963
+ };
964
+ for (const child of children.slice(2)) {
965
+ if (isTag(child, UNIVERSAL, TAG.oid) && !summary.policyOid) {
966
+ summary.policyOid = decodeOid(bytes, child);
967
+ }
968
+ else if (isTag(child, UNIVERSAL, TAG.integer) && !summary.nonce) {
969
+ summary.nonce = integerHex(bytes, child);
970
+ }
971
+ else if (isTag(child, UNIVERSAL, TAG.boolean)) {
972
+ summary.certReq = decodeBoolean(bytes, child);
973
+ }
974
+ }
975
+ if (originalContent && imprint.algorithmOid && imprint.digest) {
976
+ const digest = await digestBytes(imprint.algorithmOid, originalContent);
977
+ if (digest) {
978
+ summary.messageImprintMatchesOriginal = bytesToHex(digest) === imprint.digest;
979
+ }
980
+ }
981
+ return summary;
982
+ };
983
+ const timestampStatusLabel = (status) => {
984
+ const labels = {
985
+ 0: 'granted',
986
+ 1: 'grantedWithMods',
987
+ 2: 'rejection',
988
+ 3: 'waiting',
989
+ 4: 'revocationWarning',
990
+ 5: 'revocationNotification'
991
+ };
992
+ return status === undefined ? undefined : labels[status] || `status-${status}`;
993
+ };
994
+ const decodeTimestampFailureInfo = (bytes, node) => {
995
+ if (!node || !isTag(node, UNIVERSAL, TAG.bitString))
996
+ return undefined;
997
+ const value = contentBytes(bytes, node);
998
+ if (value.byteLength < 2)
999
+ return bytesToHex(value);
1000
+ const unused = value[0] || 0;
1001
+ const payload = value.subarray(1);
1002
+ const names = [
1003
+ [0, 'badAlg'],
1004
+ [2, 'badRequest'],
1005
+ [5, 'badDataFormat'],
1006
+ [14, 'timeNotAvailable'],
1007
+ [15, 'unacceptedPolicy'],
1008
+ [16, 'unacceptedExtension'],
1009
+ [17, 'addInfoNotAvailable'],
1010
+ [25, 'systemFailure']
1011
+ ];
1012
+ const set = [];
1013
+ for (const [bit, name] of names) {
1014
+ const byteIndex = Math.floor(bit / 8);
1015
+ const bitIndex = 7 - (bit % 8);
1016
+ if (byteIndex < payload.byteLength && payload[byteIndex] & (1 << bitIndex))
1017
+ set.push(name);
1018
+ }
1019
+ return set.length ? set.join(', ') : `0x${bytesToHex(payload)} (unused bits ${unused})`;
1020
+ };
1021
+ const parseTimestampResponse = (bytes, root) => {
1022
+ const statusInfoNode = root.children[0];
1023
+ const tokenNode = root.children[1];
1024
+ assertBounds(statusInfoNode && isTag(statusInfoNode, UNIVERSAL, TAG.sequence), 'RFC 3161 response is missing PKIStatusInfo.');
1025
+ const statusNode = statusInfoNode.children[0];
1026
+ const status = statusNode ? integerNumber(bytes, statusNode) : undefined;
1027
+ const stringsNode = statusInfoNode.children.find((child) => isTag(child, UNIVERSAL, TAG.sequence));
1028
+ const failureNode = statusInfoNode.children.find((child) => isTag(child, UNIVERSAL, TAG.bitString));
1029
+ return {
1030
+ response: {
1031
+ status,
1032
+ statusLabel: timestampStatusLabel(status),
1033
+ statusText: ((stringsNode === null || stringsNode === void 0 ? void 0 : stringsNode.children) || []).map((child) => decodeString(bytes, child)),
1034
+ failureInfo: decodeTimestampFailureInfo(bytes, failureNode)
1035
+ },
1036
+ tokenNode
1037
+ };
1038
+ };
1039
+ const looksLikeTimestampRequest = (root) => {
1040
+ const [version, imprint] = root.children;
1041
+ return Boolean(isTag(version, UNIVERSAL, TAG.integer) &&
1042
+ isTag(imprint, UNIVERSAL, TAG.sequence) &&
1043
+ isTag(imprint === null || imprint === void 0 ? void 0 : imprint.children[0], UNIVERSAL, TAG.sequence) &&
1044
+ isTag(imprint === null || imprint === void 0 ? void 0 : imprint.children[1], UNIVERSAL, TAG.octetString));
1045
+ };
1046
+ const looksLikeTimestampResponse = (root) => {
1047
+ const statusInfo = root.children[0];
1048
+ return Boolean(isTag(statusInfo, UNIVERSAL, TAG.sequence) &&
1049
+ isTag(statusInfo === null || statusInfo === void 0 ? void 0 : statusInfo.children[0], UNIVERSAL, TAG.integer));
1050
+ };
1051
+ const cmsKind = (cms) => {
1052
+ if (cms.contentTypeOid !== OID.signedData) {
1053
+ return 'cms-content';
1054
+ }
1055
+ if (cms.signedContentTypeOid === OID.tstInfo) {
1056
+ return 'timestamp-token';
1057
+ }
1058
+ if (!cms.signers.length && cms.certificates.length) {
1059
+ return 'cms-certificates';
1060
+ }
1061
+ return 'cms-signed-data';
1062
+ };
1063
+ const detectedFormatForKind = (kind) => {
1064
+ const labels = {
1065
+ 'cms-signed-data': 'CMS / PKCS#7 SignedData',
1066
+ 'cms-certificates': 'CMS / PKCS#7 certificate container',
1067
+ 'cms-content': 'CMS / PKCS#7 content container',
1068
+ 'timestamp-request': 'RFC 3161 TimeStampReq',
1069
+ 'timestamp-response': 'RFC 3161 TimeStampResp',
1070
+ 'timestamp-token': 'RFC 3161 TimeStampToken',
1071
+ 'timestamped-data': 'RFC 5544 TimeStampedData',
1072
+ unknown: 'Unknown cryptographic container'
1073
+ };
1074
+ return labels[kind];
1075
+ };
1076
+ const baseWarnings = () => [
1077
+ 'Cryptographic verification does not establish certificate trust, policy compliance, qualified status, or legal validity.',
1078
+ 'This ASN.1 path covers bounded CMS/PKCS#7, selected CAdES attributes, RFC 3161, and RFC 5544 TimeStampedData; ASiC, RFC 4998, JWS/JAdES metadata, and OpenPGP use separate bounded paths in this renderer.',
1079
+ 'PAdES, full XMLDSig/XAdES/JAdES profile validation, S/MIME, and PGP/MIME host integration are not performed by this ASN.1 path.',
1080
+ 'No private-key operations, passphrases, trust stores, network lookups, or LGPL OpenPGP source are used or bundled.'
1081
+ ];
1082
+ const inspectCmsNode = async (bytes, root, originalContent, limits) => {
1083
+ const cms = parseCmsContentInfo(bytes, root, limits);
1084
+ await enrichCertificateFingerprints(cms.certificates, bytes);
1085
+ const verificationContent = cms.embeddedContent || originalContent;
1086
+ await Promise.all(cms.signers.map((signer) => verifySigner(bytes, signer, cms.certificates, verificationContent)));
1087
+ const kind = cmsKind(cms);
1088
+ const timestamp = cms.signedContentTypeOid === OID.tstInfo && cms.embeddedContent
1089
+ ? await parseTstInfo(cms.embeddedContent, originalContent, limits)
1090
+ : undefined;
1091
+ return {
1092
+ kind,
1093
+ detectedFormat: detectedFormatForKind(kind),
1094
+ sourceSize: bytes.byteLength,
1095
+ contentTypeOid: cms.contentTypeOid,
1096
+ contentType: formatOid(cms.contentTypeOid),
1097
+ signedContentTypeOid: cms.signedContentTypeOid,
1098
+ signedContentType: formatOid(cms.signedContentTypeOid),
1099
+ digestAlgorithms: cms.digestAlgorithmOids.map(formatOid),
1100
+ embeddedContent: cms.embeddedContent,
1101
+ detached: cms.contentTypeOid === OID.signedData && !cms.embeddedContent && cms.signers.length > 0,
1102
+ requiresOriginalContent: (cms.contentTypeOid === OID.signedData && !cms.embeddedContent && cms.signers.length > 0) ||
1103
+ Boolean((timestamp === null || timestamp === void 0 ? void 0 : timestamp.messageImprint) && originalContent === undefined),
1104
+ originalContentSupplied: Boolean(originalContent),
1105
+ certificates: cms.certificates.map((certificate) => certificate.summary),
1106
+ signers: cms.signers.map((signer) => signer.summary),
1107
+ timestamp,
1108
+ crlCount: cms.crlCount,
1109
+ warnings: baseWarnings()
1110
+ };
1111
+ };
1112
+ const PEM_ENVELOPES = [
1113
+ {
1114
+ begin: '-----BEGIN PKCS7-----',
1115
+ end: '-----END PKCS7-----'
1116
+ },
1117
+ {
1118
+ begin: '-----BEGIN CMS-----',
1119
+ end: '-----END CMS-----'
1120
+ }
1121
+ ];
1122
+ const isPemWhitespaceCode = (code) => code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d || code === 0x20;
1123
+ const isBase64Code = (code) => (code >= 0x41 && code <= 0x5a) ||
1124
+ (code >= 0x61 && code <= 0x7a) ||
1125
+ (code >= 0x30 && code <= 0x39) ||
1126
+ code === 0x2b ||
1127
+ code === 0x2f ||
1128
+ code === 0x3d;
1129
+ const decodePemIfNeeded = (input) => {
1130
+ const head = new TextDecoder('ascii', { fatal: false }).decode(input.subarray(0, Math.min(input.byteLength, 128)));
1131
+ if (!head.includes('-----BEGIN '))
1132
+ return input;
1133
+ const text = new TextDecoder('ascii', { fatal: false }).decode(input);
1134
+ let envelope;
1135
+ let beginIndex = -1;
1136
+ for (const candidate of PEM_ENVELOPES) {
1137
+ const candidateIndex = text.indexOf(candidate.begin);
1138
+ if (candidateIndex >= 0 && (beginIndex < 0 || candidateIndex < beginIndex)) {
1139
+ envelope = candidate;
1140
+ beginIndex = candidateIndex;
1141
+ }
1142
+ }
1143
+ assertBounds(envelope && beginIndex >= 0, 'Unsupported PEM cryptographic container. Expected PKCS7 or CMS armor.');
1144
+ const payloadStart = beginIndex + envelope.begin.length;
1145
+ const payloadEnd = text.indexOf(envelope.end, payloadStart);
1146
+ assertBounds(payloadEnd >= 0, 'PEM cryptographic container is missing its matching footer.');
1147
+ const chunks = [];
1148
+ let chunk = '';
1149
+ let compactLength = 0;
1150
+ let paddingStart = -1;
1151
+ for (let index = payloadStart; index < payloadEnd; index += 1) {
1152
+ const code = text.charCodeAt(index);
1153
+ if (isPemWhitespaceCode(code))
1154
+ continue;
1155
+ assertBounds(isBase64Code(code), 'Malformed PEM base64 payload.');
1156
+ if (code === 0x3d) {
1157
+ if (paddingStart < 0)
1158
+ paddingStart = compactLength;
1159
+ }
1160
+ else {
1161
+ assertBounds(paddingStart < 0, 'Malformed PEM base64 padding.');
1162
+ }
1163
+ chunk += String.fromCharCode(code);
1164
+ compactLength += 1;
1165
+ if (chunk.length === 8192) {
1166
+ chunks.push(chunk);
1167
+ chunk = '';
1168
+ }
1169
+ }
1170
+ if (chunk)
1171
+ chunks.push(chunk);
1172
+ assertBounds(compactLength > 0 && compactLength % 4 === 0, 'Malformed PEM base64 payload.');
1173
+ assertBounds(paddingStart < 0 || compactLength - paddingStart <= 2, 'Malformed PEM base64 padding.');
1174
+ assertBounds(typeof atob === 'function', 'PEM decoding requires a browser with atob support.');
1175
+ let binary;
1176
+ try {
1177
+ binary = atob(chunks.join(''));
1178
+ }
1179
+ catch {
1180
+ throw new Error('Malformed PEM base64 payload.');
1181
+ }
1182
+ const output = new Uint8Array(binary.length);
1183
+ for (let index = 0; index < binary.length; index += 1)
1184
+ output[index] = binary.charCodeAt(index);
1185
+ return output;
1186
+ };
1187
+ const findCmsContentInfoNode = (bytes, node, depth = 0) => {
1188
+ if (depth > 12)
1189
+ return undefined;
1190
+ if (isTag(node, UNIVERSAL, TAG.sequence)) {
1191
+ const first = node.children[0];
1192
+ if (first && isTag(first, UNIVERSAL, TAG.oid) && decodeOid(bytes, first) === OID.signedData)
1193
+ return node;
1194
+ }
1195
+ for (const child of node.children) {
1196
+ const found = findCmsContentInfoNode(bytes, child, depth + 1);
1197
+ if (found)
1198
+ return found;
1199
+ }
1200
+ return undefined;
1201
+ };
1202
+ const parseTimestampedData = async (bytes, root, originalContent, limits) => {
1203
+ var _a;
1204
+ const content = (_a = root.children.find((child) => isTag(child, CONTEXT, 0))) === null || _a === void 0 ? void 0 : _a.children[0];
1205
+ assertBounds(content && isTag(content, UNIVERSAL, TAG.sequence), 'RFC 5544 TimeStampedData payload is missing.');
1206
+ const fields = content.children;
1207
+ let cursor = 0;
1208
+ const versionNode = fields[cursor++];
1209
+ assertBounds(versionNode && isTag(versionNode, UNIVERSAL, TAG.integer), 'RFC 5544 TimeStampedData version is missing.');
1210
+ let dataUri;
1211
+ if (isTag(fields[cursor], UNIVERSAL, TAG.ia5String))
1212
+ dataUri = decodeString(bytes, fields[cursor++]);
1213
+ let filename;
1214
+ let mediaType;
1215
+ let hashProtected;
1216
+ const metadataNode = fields[cursor] && isTag(fields[cursor], UNIVERSAL, TAG.sequence) ? fields[cursor++] : undefined;
1217
+ if (metadataNode) {
1218
+ const metadata = metadataNode.children;
1219
+ if (metadata[0] && isTag(metadata[0], UNIVERSAL, TAG.boolean))
1220
+ hashProtected = decodeBoolean(bytes, metadata[0]);
1221
+ for (const child of metadata.slice(1)) {
1222
+ if (!filename &&
1223
+ (isTag(child, UNIVERSAL, TAG.utf8String) || isTag(child, UNIVERSAL, TAG.printableString)))
1224
+ filename = decodeString(bytes, child);
1225
+ else if (!mediaType && isTag(child, UNIVERSAL, TAG.ia5String))
1226
+ mediaType = decodeString(bytes, child);
1227
+ }
1228
+ }
1229
+ const embeddedNode = fields[cursor] && isTag(fields[cursor], UNIVERSAL, TAG.octetString)
1230
+ ? fields[cursor++]
1231
+ : undefined;
1232
+ const embeddedContent = embeddedNode
1233
+ ? collectOctetString(bytes, embeddedNode, limits.maxEmbeddedContentBytes)
1234
+ : undefined;
1235
+ const evidenceNode = fields[cursor];
1236
+ const cmsNode = evidenceNode ? findCmsContentInfoNode(bytes, evidenceNode) : undefined;
1237
+ const evidenceInspection = cmsNode
1238
+ ? await inspectCmsNode(bytes, cmsNode, embeddedContent || originalContent, limits)
1239
+ : undefined;
1240
+ const evidenceEntries = (evidenceNode === null || evidenceNode === void 0 ? void 0 : evidenceNode.children.length) || 0;
1241
+ assertBounds(evidenceEntries <= limits.maxTimestampEvidenceEntries, `RFC 5544 temporal evidence count exceeds ${limits.maxTimestampEvidenceEntries}.`);
1242
+ return {
1243
+ kind: 'timestamped-data',
1244
+ detectedFormat: detectedFormatForKind('timestamped-data'),
1245
+ sourceSize: bytes.byteLength,
1246
+ contentTypeOid: OID.timestampedData,
1247
+ contentType: formatOid(OID.timestampedData),
1248
+ digestAlgorithms: (evidenceInspection === null || evidenceInspection === void 0 ? void 0 : evidenceInspection.digestAlgorithms) || [],
1249
+ embeddedContent,
1250
+ detached: !embeddedContent,
1251
+ requiresOriginalContent: !embeddedContent && Boolean(dataUri),
1252
+ originalContentSupplied: Boolean(originalContent),
1253
+ certificates: (evidenceInspection === null || evidenceInspection === void 0 ? void 0 : evidenceInspection.certificates) || [],
1254
+ signers: (evidenceInspection === null || evidenceInspection === void 0 ? void 0 : evidenceInspection.signers) || [],
1255
+ timestamp: evidenceInspection === null || evidenceInspection === void 0 ? void 0 : evidenceInspection.timestamp,
1256
+ crlCount: (evidenceInspection === null || evidenceInspection === void 0 ? void 0 : evidenceInspection.crlCount) || 0,
1257
+ timestampedData: {
1258
+ dataUri,
1259
+ filename,
1260
+ mediaType,
1261
+ hashProtected,
1262
+ temporalEvidenceEntries: evidenceEntries
1263
+ },
1264
+ warnings: [
1265
+ ...baseWarnings(),
1266
+ ...(dataUri
1267
+ ? [
1268
+ 'External RFC 5544 dataUri values are displayed as untrusted metadata and are never fetched automatically.'
1269
+ ]
1270
+ : [])
1271
+ ]
1272
+ };
1273
+ };
1274
+ export const inspectSignatureContainer = async (input, options = {}) => {
1275
+ const limits = normalizeSignatureAsn1Limits(options.limits);
1276
+ const rawBytes = toUint8Array(input);
1277
+ assertBounds(rawBytes.byteLength <= limits.maxInputBytes, `Cryptographic container exceeds the ${limits.maxInputBytes}-byte boundary.`);
1278
+ const bytes = decodePemIfNeeded(rawBytes);
1279
+ assertBounds(bytes.byteLength > 0, 'The cryptographic container is empty.');
1280
+ assertBounds(bytes.byteLength <= limits.maxInputBytes, `Decoded cryptographic container exceeds the ${limits.maxInputBytes}-byte boundary.`);
1281
+ const originalContent = options.originalContent
1282
+ ? toUint8Array(options.originalContent)
1283
+ : undefined;
1284
+ assertBounds(((originalContent === null || originalContent === void 0 ? void 0 : originalContent.byteLength) || 0) <= limits.maxOriginalContentBytes, `Original content exceeds the ${limits.maxOriginalContentBytes}-byte boundary.`);
1285
+ const root = parseRoot(bytes, limits);
1286
+ assertBounds(isTag(root, UNIVERSAL, TAG.sequence), 'Unsupported cryptographic container: ASN.1 root is not a SEQUENCE.');
1287
+ const first = root.children[0];
1288
+ if (first && isTag(first, UNIVERSAL, TAG.oid)) {
1289
+ const contentTypeOid = decodeOid(bytes, first);
1290
+ if (contentTypeOid === OID.timestampedData) {
1291
+ return parseTimestampedData(bytes, root, originalContent, limits);
1292
+ }
1293
+ return inspectCmsNode(bytes, root, originalContent, limits);
1294
+ }
1295
+ if (looksLikeTimestampRequest(root)) {
1296
+ const timestamp = await parseTimestampRequest(bytes, root, originalContent);
1297
+ return {
1298
+ kind: 'timestamp-request',
1299
+ detectedFormat: detectedFormatForKind('timestamp-request'),
1300
+ sourceSize: bytes.byteLength,
1301
+ digestAlgorithms: timestamp.messageImprintAlgorithm
1302
+ ? [timestamp.messageImprintAlgorithm]
1303
+ : [],
1304
+ detached: true,
1305
+ requiresOriginalContent: Boolean(timestamp.messageImprint && !originalContent),
1306
+ originalContentSupplied: Boolean(originalContent),
1307
+ certificates: [],
1308
+ signers: [],
1309
+ timestamp,
1310
+ crlCount: 0,
1311
+ warnings: baseWarnings()
1312
+ };
1313
+ }
1314
+ if (looksLikeTimestampResponse(root)) {
1315
+ const { response, tokenNode } = parseTimestampResponse(bytes, root);
1316
+ if (tokenNode) {
1317
+ const tokenInspection = await inspectCmsNode(bytes, tokenNode, originalContent, limits);
1318
+ return {
1319
+ ...tokenInspection,
1320
+ kind: 'timestamp-response',
1321
+ detectedFormat: detectedFormatForKind('timestamp-response'),
1322
+ timestampResponse: response
1323
+ };
1324
+ }
1325
+ return {
1326
+ kind: 'timestamp-response',
1327
+ detectedFormat: detectedFormatForKind('timestamp-response'),
1328
+ sourceSize: bytes.byteLength,
1329
+ digestAlgorithms: [],
1330
+ detached: true,
1331
+ requiresOriginalContent: false,
1332
+ originalContentSupplied: Boolean(originalContent),
1333
+ certificates: [],
1334
+ signers: [],
1335
+ timestampResponse: response,
1336
+ crlCount: 0,
1337
+ warnings: baseWarnings()
1338
+ };
1339
+ }
1340
+ return {
1341
+ kind: 'unknown',
1342
+ detectedFormat: detectedFormatForKind('unknown'),
1343
+ sourceSize: bytes.byteLength,
1344
+ digestAlgorithms: [],
1345
+ detached: false,
1346
+ requiresOriginalContent: false,
1347
+ originalContentSupplied: Boolean(originalContent),
1348
+ certificates: [],
1349
+ signers: [],
1350
+ crlCount: 0,
1351
+ warnings: baseWarnings()
1352
+ };
1353
+ };
1354
+ const countReducedHashTreeNodes = (node, maximum) => {
1355
+ if (!node)
1356
+ return 0;
1357
+ let count = 0;
1358
+ const pending = [node];
1359
+ while (pending.length) {
1360
+ const current = pending.pop();
1361
+ if (isTag(current, UNIVERSAL, TAG.octetString)) {
1362
+ count += 1;
1363
+ assertBounds(count <= maximum, `EvidenceRecord reduced hash tree exceeds ${maximum} nodes.`);
1364
+ }
1365
+ for (const child of current.children)
1366
+ pending.push(child);
1367
+ }
1368
+ return count;
1369
+ };
1370
+ const findEvidenceTimestampContentInfo = (bytes, node) => [...node.children].reverse().find((child) => {
1371
+ if (!isTag(child, UNIVERSAL, TAG.sequence))
1372
+ return false;
1373
+ const oidNode = child.children[0];
1374
+ if (!oidNode || !isTag(oidNode, UNIVERSAL, TAG.oid))
1375
+ return false;
1376
+ try {
1377
+ return decodeOid(bytes, oidNode) === OID.signedData;
1378
+ }
1379
+ catch {
1380
+ return false;
1381
+ }
1382
+ });
1383
+ /**
1384
+ * Bounded RFC 4998 structural inspection. Only the first archive timestamp can
1385
+ * be compared directly with supplied original bytes when no reduced hash tree
1386
+ * is present. Renewal chains are reported but never over-claimed as validated.
1387
+ */
1388
+ export const inspectEvidenceRecord = async (input, options = {}) => {
1389
+ var _a;
1390
+ const limits = normalizeSignatureAsn1Limits(options.limits);
1391
+ const bytes = toUint8Array(input);
1392
+ assertBounds(bytes.byteLength > 0, 'EvidenceRecord input is empty.');
1393
+ assertBounds(bytes.byteLength <= limits.maxInputBytes, `EvidenceRecord input exceeds the ${limits.maxInputBytes}-byte boundary.`);
1394
+ const root = parseRoot(bytes, limits);
1395
+ assertBounds(isTag(root, UNIVERSAL, TAG.sequence), 'RFC 4998 EvidenceRecord must be an ASN.1 SEQUENCE.');
1396
+ const versionNode = root.children[0];
1397
+ const algorithmsNode = root.children[1];
1398
+ assertBounds(versionNode && isTag(versionNode, UNIVERSAL, TAG.integer), 'EvidenceRecord version is missing.');
1399
+ assertBounds(algorithmsNode && isTag(algorithmsNode, UNIVERSAL, TAG.sequence), 'EvidenceRecord digestAlgorithms are missing.');
1400
+ const version = integerNumber(bytes, versionNode);
1401
+ assertBounds(version === 1, `Unsupported EvidenceRecord version ${version !== null && version !== void 0 ? version : 'unknown'}.`);
1402
+ assertBounds(algorithmsNode.children.length > 0 &&
1403
+ algorithmsNode.children.length <= limits.maxDigestAlgorithms, `EvidenceRecord digest algorithm count must be between 1 and ${limits.maxDigestAlgorithms}.`);
1404
+ const digestAlgorithmOids = algorithmsNode.children.map((node) => algorithmOid(bytes, node));
1405
+ assertBounds(digestAlgorithmOids.every(Boolean), 'EvidenceRecord contains a malformed digest algorithm.');
1406
+ const digestAlgorithms = digestAlgorithmOids.map((oid) => formatOid(oid));
1407
+ const sequenceNode = [...root.children]
1408
+ .reverse()
1409
+ .find((node) => isTag(node, UNIVERSAL, TAG.sequence) && node !== algorithmsNode);
1410
+ assertBounds(sequenceNode, 'EvidenceRecord archiveTimeStampSequence is missing.');
1411
+ assertBounds(sequenceNode.children.length > 0 && sequenceNode.children.length <= limits.maxEvidenceChains, `EvidenceRecord archive timestamp chain count exceeds ${limits.maxEvidenceChains}.`);
1412
+ const originalContent = options.originalContent
1413
+ ? toUint8Array(options.originalContent)
1414
+ : undefined;
1415
+ assertBounds(((originalContent === null || originalContent === void 0 ? void 0 : originalContent.byteLength) || 0) <= limits.maxOriginalContentBytes, `Original content exceeds the ${limits.maxOriginalContentBytes}-byte boundary.`);
1416
+ const archiveTimestamps = [];
1417
+ let totalTimestamps = 0;
1418
+ for (let chainIndex = 0; chainIndex < sequenceNode.children.length; chainIndex += 1) {
1419
+ const chain = sequenceNode.children[chainIndex];
1420
+ assertBounds(isTag(chain, UNIVERSAL, TAG.sequence), 'EvidenceRecord archive timestamp chain is malformed.');
1421
+ assertBounds(chain.children.length > 0 && chain.children.length <= limits.maxEvidenceTimestampsPerChain, `EvidenceRecord archive timestamp chain exceeds ${limits.maxEvidenceTimestampsPerChain} entries.`);
1422
+ for (let index = 0; index < chain.children.length; index += 1) {
1423
+ totalTimestamps += 1;
1424
+ assertBounds(totalTimestamps <= limits.maxEvidenceTimestamps, `EvidenceRecord archive timestamp total exceeds ${limits.maxEvidenceTimestamps}.`);
1425
+ const archiveTimestamp = chain.children[index];
1426
+ assertBounds(isTag(archiveTimestamp, UNIVERSAL, TAG.sequence), 'EvidenceRecord archive timestamp is malformed.');
1427
+ const explicitDigest = archiveTimestamp.children.find((child) => isTag(child, CONTEXT, 0));
1428
+ const explicitAlgorithmNode = explicitDigest === null || explicitDigest === void 0 ? void 0 : explicitDigest.children.find((child) => isTag(child, UNIVERSAL, TAG.sequence));
1429
+ const digestAlgorithmOid = explicitAlgorithmNode
1430
+ ? algorithmOid(bytes, explicitAlgorithmNode)
1431
+ : digestAlgorithmOids[0];
1432
+ const reducedTree = archiveTimestamp.children.find((child) => isTag(child, CONTEXT, 2));
1433
+ const reducedHashTreeNodes = countReducedHashTreeNodes(reducedTree, limits.maxReducedHashTreeNodes);
1434
+ const contentInfo = findEvidenceTimestampContentInfo(bytes, archiveTimestamp);
1435
+ assertBounds(contentInfo, 'EvidenceRecord archive timestamp is missing its RFC 3161 ContentInfo.');
1436
+ const mayCompareOriginal = chainIndex === 0 && index === 0 && reducedHashTreeNodes === 0;
1437
+ const token = await inspectCmsNode(bytes, contentInfo, mayCompareOriginal ? originalContent : undefined, limits);
1438
+ const allSignaturesChecked = token.signers.length > 0 &&
1439
+ token.signers.every((signer) => signer.cryptographicSignatureValid !== undefined);
1440
+ const timestampSignaturesValid = allSignaturesChecked
1441
+ ? token.signers.every((signer) => signer.cryptographicSignatureValid === true)
1442
+ : undefined;
1443
+ archiveTimestamps.push({
1444
+ chainIndex,
1445
+ index,
1446
+ digestAlgorithmOid,
1447
+ digestAlgorithm: formatOid(digestAlgorithmOid),
1448
+ reducedHashTreeNodes,
1449
+ timestamp: token.timestamp,
1450
+ timestampSignerCount: token.signers.length,
1451
+ timestampSignaturesValid,
1452
+ evidenceDigestMatchesOriginal: mayCompareOriginal
1453
+ ? (_a = token.timestamp) === null || _a === void 0 ? void 0 : _a.messageImprintMatchesOriginal
1454
+ : undefined,
1455
+ warnings: [
1456
+ ...(mayCompareOriginal
1457
+ ? []
1458
+ : [
1459
+ 'This archive timestamp is part of a renewal/hash-tree chain and was structurally inspected, not fully replayed.'
1460
+ ]),
1461
+ ...(token.warnings || [])
1462
+ ]
1463
+ });
1464
+ }
1465
+ }
1466
+ const first = archiveTimestamps[0];
1467
+ return {
1468
+ kind: 'evidence-record',
1469
+ detectedFormat: 'RFC 4998 EvidenceRecord',
1470
+ sourceSize: bytes.byteLength,
1471
+ version,
1472
+ digestAlgorithms,
1473
+ archiveTimestampChains: sequenceNode.children.length,
1474
+ archiveTimestamps,
1475
+ originalContentSupplied: Boolean(originalContent),
1476
+ originalEvidenceMatches: first === null || first === void 0 ? void 0 : first.evidenceDigestMatchesOriginal,
1477
+ fullyValidated: false,
1478
+ warnings: [
1479
+ 'EvidenceRecord parsing is separate from complete archival-evidence validation.',
1480
+ 'The first timestamp can be compared directly only when it has no reduced hash tree; renewal chains are not over-claimed as validated.',
1481
+ 'No TSA, OCSP, CRL, AIA, trust-list or other network endpoint is contacted automatically.',
1482
+ 'Certificate trust, retention-policy compliance and legal validity are not established.'
1483
+ ]
1484
+ };
1485
+ };
1486
+ export const signatureOidLabels = OID_LABELS;