@blamejs/pki 0.1.1

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.
@@ -0,0 +1,576 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.asn1
6
+ * @nav Core
7
+ * @title ASN.1 / DER
8
+ * @order 30
9
+ * @featured true
10
+ * @slug asn1
11
+ *
12
+ * @intro
13
+ * A strict DER (Distinguished Encoding Rules) codec — the byte layer
14
+ * every X.509 / PKCS / CMS structure is built on. The decoder is
15
+ * fail-closed: it rejects the BER shapes DER forbids (indefinite
16
+ * length, non-minimal length or integer encodings, trailing garbage,
17
+ * constructed strings) and refuses input past a size or nesting cap
18
+ * before it walks a single byte, so a hostile length prefix can't turn
19
+ * into a decoder denial-of-service.
20
+ *
21
+ * `decode(bytes)` returns a navigable node tree; the `read.*` helpers
22
+ * turn a node into a JS value (BigInt, dotted OID, Date, string); the
23
+ * `build.*` helpers construct canonical DER from JS values. Because DER
24
+ * is canonical, each value the `build.*` helpers emit has exactly one
25
+ * valid encoding — byte-identical to any other conformant DER encoder's
26
+ * output — and decoding it reproduces the value it was built from.
27
+ *
28
+ * @card
29
+ * Strict, fail-closed DER decode / encode with a navigable node tree
30
+ * and typed readers + builders.
31
+ */
32
+
33
+ var constants = require("./constants");
34
+ var frameworkError = require("./framework-error");
35
+
36
+ var Asn1Error = frameworkError.Asn1Error;
37
+ var OidError = frameworkError.OidError;
38
+
39
+ // ---- Tag constants (universal class) --------------------------------
40
+
41
+ var TAGS = {
42
+ BOOLEAN: 0x01,
43
+ INTEGER: 0x02,
44
+ BIT_STRING: 0x03,
45
+ OCTET_STRING: 0x04,
46
+ NULL: 0x05,
47
+ OBJECT_IDENTIFIER: 0x06,
48
+ ENUMERATED: 0x0a,
49
+ UTF8_STRING: 0x0c,
50
+ SEQUENCE: 0x10,
51
+ SET: 0x11,
52
+ PRINTABLE_STRING: 0x13,
53
+ TELETEX_STRING: 0x14,
54
+ IA5_STRING: 0x16,
55
+ UTC_TIME: 0x17,
56
+ GENERALIZED_TIME: 0x18,
57
+ VISIBLE_STRING: 0x1a,
58
+ UNIVERSAL_STRING: 0x1c,
59
+ BMP_STRING: 0x1e,
60
+ };
61
+
62
+ var CLASS_UNIVERSAL = 0x00;
63
+ var CLASS_APPLICATION = 0x40;
64
+ var CLASS_CONTEXT = 0x80;
65
+ var CLASS_PRIVATE = 0xc0;
66
+ var CONSTRUCTED_BIT = 0x20;
67
+
68
+ function _className(bits) {
69
+ switch (bits) {
70
+ case CLASS_UNIVERSAL: return "universal";
71
+ case CLASS_APPLICATION: return "application";
72
+ case CLASS_CONTEXT: return "context";
73
+ case CLASS_PRIVATE: return "private";
74
+ default: return "universal";
75
+ }
76
+ }
77
+
78
+ function _asBuffer(input, who) {
79
+ if (Buffer.isBuffer(input)) return input;
80
+ if (input instanceof Uint8Array) return Buffer.from(input);
81
+ throw new Asn1Error("asn1/not-buffer", who + ": expected a Buffer / Uint8Array");
82
+ }
83
+
84
+ // ---- Decoder --------------------------------------------------------
85
+
86
+ /**
87
+ * @primitive pki.asn1.decode
88
+ * @signature pki.asn1.decode(bytes, opts?) -> node
89
+ * @since 0.1.0
90
+ * @status stable
91
+ * @related pki.asn1.encode
92
+ *
93
+ * Parse DER into a node tree. Each node is
94
+ * `{ tagClass, constructed, tagNumber, header, length, content, children,
95
+ * bytes }` — `content` is the primitive value slice, `children` the
96
+ * decoded sub-nodes of a constructed node, and `bytes` the full TLV slice
97
+ * (all zero-copy views over the input).
98
+ *
99
+ * Throws `Asn1Error` on any non-DER shape: indefinite length, a
100
+ * non-minimal length or a length that overruns the buffer, trailing bytes
101
+ * after the top-level value (unless `allowTrailing`), or exceeding the
102
+ * size / depth caps.
103
+ *
104
+ * @opts
105
+ * maxBytes: number, // default: C.LIMITS.DER_MAX_BYTES (16 MiB)
106
+ * maxDepth: number, // default: C.LIMITS.DER_MAX_DEPTH (64)
107
+ * allowTrailing: boolean, // default: false — allow bytes after the top TLV
108
+ *
109
+ * @example
110
+ * var node = pki.asn1.decode(der);
111
+ * node.tagNumber === pki.asn1.TAGS.SEQUENCE;
112
+ */
113
+ function decode(input, opts) {
114
+ opts = opts || {};
115
+ var buf = _asBuffer(input, "decode");
116
+ var maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : constants.LIMITS.DER_MAX_BYTES;
117
+ var maxDepth = typeof opts.maxDepth === "number" ? opts.maxDepth : constants.LIMITS.DER_MAX_DEPTH;
118
+ if (buf.length > maxBytes) {
119
+ throw new Asn1Error("asn1/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
120
+ }
121
+ var r = _decodeTLV(buf, 0, buf.length, 0, maxDepth);
122
+ if (!opts.allowTrailing && r.end !== buf.length) {
123
+ throw new Asn1Error("asn1/trailing-bytes", (buf.length - r.end) + " trailing byte(s) after the top-level value");
124
+ }
125
+ return r.node;
126
+ }
127
+
128
+ function _decodeTLV(buf, start, limit, depth, maxDepth) {
129
+ if (depth > maxDepth) {
130
+ throw new Asn1Error("asn1/too-deep", "nesting exceeds depth cap " + maxDepth);
131
+ }
132
+ var p = start;
133
+ if (p >= limit) throw new Asn1Error("asn1/truncated", "expected an identifier octet");
134
+ var first = buf[p]; p += 1;
135
+ var tagClassBits = first & 0xc0;
136
+ var constructed = (first & CONSTRUCTED_BIT) !== 0;
137
+ var tagNumber = first & 0x1f;
138
+
139
+ if (tagNumber === 0x1f) {
140
+ // High-tag-number form: base-128, minimal (no leading 0x80).
141
+ tagNumber = 0;
142
+ var seen = 0;
143
+ for (;;) {
144
+ if (p >= limit) throw new Asn1Error("asn1/truncated", "truncated high-tag-number");
145
+ var tb = buf[p]; p += 1;
146
+ if (seen === 0 && tb === 0x80) {
147
+ throw new Asn1Error("asn1/non-minimal-tag", "leading 0x80 in high-tag-number form");
148
+ }
149
+ tagNumber = (tagNumber * 128) + (tb & 0x7f);
150
+ seen += 1;
151
+ if (seen > 4) throw new Asn1Error("asn1/tag-too-large", "high-tag-number too large");
152
+ if ((tb & 0x80) === 0) break;
153
+ }
154
+ if (tagNumber < 0x1f) {
155
+ throw new Asn1Error("asn1/non-minimal-tag", "high-tag-number form used for a low tag");
156
+ }
157
+ }
158
+
159
+ if (p >= limit) throw new Asn1Error("asn1/truncated", "expected a length octet");
160
+ var lenByte = buf[p]; p += 1;
161
+ var length;
162
+ if (lenByte < 0x80) {
163
+ length = lenByte;
164
+ } else if (lenByte === 0x80) {
165
+ throw new Asn1Error("asn1/indefinite-length", "indefinite length is not valid DER");
166
+ } else {
167
+ var numLenBytes = lenByte & 0x7f;
168
+ if (numLenBytes > 6) throw new Asn1Error("asn1/length-too-large", "length uses more than 6 octets");
169
+ if (p + numLenBytes > limit) throw new Asn1Error("asn1/truncated", "truncated long-form length");
170
+ if (buf[p] === 0x00) throw new Asn1Error("asn1/non-minimal-length", "leading zero in long-form length");
171
+ length = 0;
172
+ for (var i = 0; i < numLenBytes; i++) length = (length * 256) + buf[p + i];
173
+ p += numLenBytes;
174
+ if (length < 0x80) throw new Asn1Error("asn1/non-minimal-length", "long form used for a length < 128");
175
+ }
176
+
177
+ var contentStart = p;
178
+ var contentEnd = contentStart + length;
179
+ if (contentEnd > limit) throw new Asn1Error("asn1/truncated", "content length overruns the buffer");
180
+
181
+ var node = {
182
+ tagClass: _className(tagClassBits),
183
+ constructed: constructed,
184
+ tagNumber: tagNumber,
185
+ length: length,
186
+ header: { start: start, end: contentStart },
187
+ contentStart: contentStart,
188
+ contentEnd: contentEnd,
189
+ content: null,
190
+ children: null,
191
+ bytes: buf.subarray(start, contentEnd),
192
+ };
193
+
194
+ if (constructed) {
195
+ node.children = [];
196
+ var cp = contentStart;
197
+ while (cp < contentEnd) {
198
+ var child = _decodeTLV(buf, cp, contentEnd, depth + 1, maxDepth);
199
+ node.children.push(child.node);
200
+ cp = child.end;
201
+ }
202
+ } else {
203
+ node.content = buf.subarray(contentStart, contentEnd);
204
+ }
205
+ return { node: node, end: contentEnd };
206
+ }
207
+
208
+ // ---- Node assertions ------------------------------------------------
209
+
210
+ function _expectUniversal(node, tag, who) {
211
+ if (node.tagClass !== "universal" || node.tagNumber !== tag) {
212
+ throw new Asn1Error("asn1/unexpected-tag", who + ": expected universal tag " + tag +
213
+ ", got " + node.tagClass + "/" + node.tagNumber);
214
+ }
215
+ }
216
+
217
+ function _expectPrimitive(node, who) {
218
+ if (node.constructed) throw new Asn1Error("asn1/expected-primitive", who + ": expected a primitive encoding");
219
+ }
220
+
221
+ // ---- Typed readers --------------------------------------------------
222
+
223
+ function readBoolean(node) {
224
+ _expectUniversal(node, TAGS.BOOLEAN, "readBoolean");
225
+ _expectPrimitive(node, "readBoolean");
226
+ if (node.content.length !== 1) throw new Asn1Error("asn1/bad-boolean", "BOOLEAN content must be 1 octet");
227
+ var v = node.content[0];
228
+ if (v === 0x00) return false;
229
+ if (v === 0xff) return true;
230
+ throw new Asn1Error("asn1/bad-boolean", "DER BOOLEAN must be 0x00 or 0xFF, got 0x" + v.toString(16));
231
+ }
232
+
233
+ function readInteger(node) {
234
+ if (node.tagClass === "universal" && node.tagNumber === TAGS.ENUMERATED) {
235
+ // ENUMERATED shares INTEGER's content encoding.
236
+ } else {
237
+ _expectUniversal(node, TAGS.INTEGER, "readInteger");
238
+ }
239
+ _expectPrimitive(node, "readInteger");
240
+ var c = node.content;
241
+ if (c.length === 0) throw new Asn1Error("asn1/bad-integer", "INTEGER must have at least 1 content octet");
242
+ if (c.length > 1) {
243
+ if (c[0] === 0x00 && (c[1] & 0x80) === 0) throw new Asn1Error("asn1/non-minimal-integer", "non-minimal positive INTEGER");
244
+ if (c[0] === 0xff && (c[1] & 0x80) !== 0) throw new Asn1Error("asn1/non-minimal-integer", "non-minimal negative INTEGER");
245
+ }
246
+ var neg = (c[0] & 0x80) !== 0;
247
+ var v = 0n;
248
+ for (var i = 0; i < c.length; i++) v = (v << 8n) | BigInt(c[i]);
249
+ if (neg) v -= (1n << BigInt(8 * c.length));
250
+ return v;
251
+ }
252
+
253
+ function readBitString(node) {
254
+ _expectUniversal(node, TAGS.BIT_STRING, "readBitString");
255
+ _expectPrimitive(node, "readBitString");
256
+ var c = node.content;
257
+ if (c.length === 0) throw new Asn1Error("asn1/bad-bit-string", "BIT STRING must have >= 1 content octet");
258
+ var unusedBits = c[0];
259
+ if (unusedBits > 7) throw new Asn1Error("asn1/bad-bit-string", "unused-bit count " + unusedBits + " > 7");
260
+ if (unusedBits > 0 && c.length === 1) throw new Asn1Error("asn1/bad-bit-string", "unused bits declared over an empty body");
261
+ return { unusedBits: unusedBits, bytes: c.subarray(1) };
262
+ }
263
+
264
+ function readOctetString(node) {
265
+ _expectUniversal(node, TAGS.OCTET_STRING, "readOctetString");
266
+ _expectPrimitive(node, "readOctetString");
267
+ return node.content;
268
+ }
269
+
270
+ function readNull(node) {
271
+ _expectUniversal(node, TAGS.NULL, "readNull");
272
+ _expectPrimitive(node, "readNull");
273
+ if (node.content.length !== 0) throw new Asn1Error("asn1/bad-null", "NULL must have empty content");
274
+ return null;
275
+ }
276
+
277
+ /**
278
+ * @primitive pki.asn1.readOid
279
+ * @signature pki.asn1.read.oid(node) -> "1.2.840.113549.1.1.11"
280
+ * @since 0.1.0
281
+ * @status stable
282
+ * @related pki.oid.name
283
+ *
284
+ * Decode an OBJECT IDENTIFIER node to its dotted-decimal string, enforcing
285
+ * the minimal base-128 sub-identifier encoding DER requires.
286
+ *
287
+ * @example
288
+ * pki.asn1.read.oid(node); // -> "2.5.4.3"
289
+ */
290
+ function readOid(node) {
291
+ _expectUniversal(node, TAGS.OBJECT_IDENTIFIER, "readOid");
292
+ _expectPrimitive(node, "readOid");
293
+ return decodeOidContent(node.content);
294
+ }
295
+
296
+ function decodeOidContent(buf) {
297
+ if (buf.length === 0) throw new OidError("oid/empty", "OBJECT IDENTIFIER content is empty");
298
+ var arcs = [];
299
+ var value = 0n;
300
+ var started = false;
301
+ for (var i = 0; i < buf.length; i++) {
302
+ var b = buf[i];
303
+ if (!started && b === 0x80) throw new OidError("oid/non-minimal", "non-minimal sub-identifier (leading 0x80)");
304
+ value = (value << 7n) | BigInt(b & 0x7f);
305
+ started = true;
306
+ if ((b & 0x80) === 0) { arcs.push(value); value = 0n; started = false; }
307
+ }
308
+ if (started) throw new OidError("oid/truncated", "OBJECT IDENTIFIER ends mid sub-identifier");
309
+ var first = arcs[0];
310
+ var a1, a2;
311
+ if (first < 40n) { a1 = 0n; a2 = first; }
312
+ else if (first < 80n) { a1 = 1n; a2 = first - 40n; }
313
+ else { a1 = 2n; a2 = first - 80n; }
314
+ var out = [a1.toString(), a2.toString()];
315
+ for (var j = 1; j < arcs.length; j++) out.push(arcs[j].toString());
316
+ return out.join(".");
317
+ }
318
+
319
+ function _decodeText(buf, encoding) {
320
+ return buf.toString(encoding);
321
+ }
322
+
323
+ function readString(node) {
324
+ if (node.tagClass !== "universal") throw new Asn1Error("asn1/expected-string", "readString: not a universal string type");
325
+ _expectPrimitive(node, "readString");
326
+ switch (node.tagNumber) {
327
+ case TAGS.UTF8_STRING: return _decodeText(node.content, "utf8");
328
+ case TAGS.PRINTABLE_STRING: return _decodeText(node.content, "latin1");
329
+ case TAGS.IA5_STRING: return _decodeText(node.content, "latin1");
330
+ case TAGS.TELETEX_STRING: return _decodeText(node.content, "latin1");
331
+ case TAGS.VISIBLE_STRING: return _decodeText(node.content, "latin1");
332
+ case TAGS.BMP_STRING: return _decodeUtf16be(node.content);
333
+ case TAGS.UNIVERSAL_STRING: return _decodeUtf32be(node.content);
334
+ default:
335
+ throw new Asn1Error("asn1/expected-string", "readString: tag " + node.tagNumber + " is not a known string type");
336
+ }
337
+ }
338
+
339
+ function _decodeUtf16be(buf) {
340
+ if (buf.length % 2 !== 0) throw new Asn1Error("asn1/bad-bmp-string", "BMPString length must be even");
341
+ var swapped = Buffer.from(buf);
342
+ swapped.swap16();
343
+ return swapped.toString("utf16le");
344
+ }
345
+
346
+ function _decodeUtf32be(buf) {
347
+ if (buf.length % 4 !== 0) throw new Asn1Error("asn1/bad-universal-string", "UniversalString length must be a multiple of 4");
348
+ var out = "";
349
+ for (var i = 0; i < buf.length; i += 4) {
350
+ var cp = (buf[i] * 0x1000000) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + buf[i + 3];
351
+ out += String.fromCodePoint(cp);
352
+ }
353
+ return out;
354
+ }
355
+
356
+ var UTC_RE = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
357
+ var GEN_RE = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
358
+
359
+ function readTime(node) {
360
+ if (node.tagClass !== "universal") throw new Asn1Error("asn1/expected-time", "readTime: not a universal time type");
361
+ _expectPrimitive(node, "readTime");
362
+ var s = node.content.toString("latin1");
363
+ var m, year;
364
+ if (node.tagNumber === TAGS.UTC_TIME) {
365
+ m = UTC_RE.exec(s);
366
+ if (!m) throw new Asn1Error("asn1/bad-utctime", "UTCTime must be YYMMDDHHMMSSZ, got " + JSON.stringify(s));
367
+ year = parseInt(m[1], 10);
368
+ // RFC 5280 §4.1.2.5.1 — YY < 50 => 20YY, else 19YY.
369
+ year += (year < 50) ? 2000 : 1900;
370
+ } else if (node.tagNumber === TAGS.GENERALIZED_TIME) {
371
+ m = GEN_RE.exec(s);
372
+ if (!m) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSSZ, got " + JSON.stringify(s));
373
+ year = parseInt(m[1], 10);
374
+ } else {
375
+ throw new Asn1Error("asn1/expected-time", "readTime: tag " + node.tagNumber + " is not a time type");
376
+ }
377
+ var month = parseInt(m[node.tagNumber === TAGS.UTC_TIME ? 2 : 2], 10);
378
+ var day = parseInt(m[3], 10);
379
+ var hour = parseInt(m[4], 10);
380
+ var min = parseInt(m[5], 10);
381
+ var sec = parseInt(m[6], 10);
382
+ var t = Date.UTC(year, month - 1, day, hour, min, sec);
383
+ if (isNaN(t)) throw new Asn1Error("asn1/bad-time", "unparseable time " + JSON.stringify(s));
384
+ return new Date(t);
385
+ }
386
+
387
+ // ---- Encoder / builders ---------------------------------------------
388
+
389
+ function encodeLength(n) {
390
+ if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) {
391
+ throw new Asn1Error("asn1/bad-length", "length must be a non-negative integer");
392
+ }
393
+ if (n < 0x80) return Buffer.from([n]);
394
+ var bytes = [];
395
+ var v = n;
396
+ while (v > 0) { bytes.unshift(v & 0xff); v = Math.floor(v / 256); }
397
+ if (bytes.length > 126) throw new Asn1Error("asn1/length-too-large", "length needs more than 126 octets");
398
+ return Buffer.concat([Buffer.from([0x80 | bytes.length]), Buffer.from(bytes)]);
399
+ }
400
+
401
+ function encodeIdentifier(classBits, constructed, tagNumber) {
402
+ var lead = classBits | (constructed ? CONSTRUCTED_BIT : 0);
403
+ if (tagNumber < 0x1f) return Buffer.from([lead | tagNumber]);
404
+ var body = [];
405
+ var v = tagNumber;
406
+ do { body.unshift(v & 0x7f); v = Math.floor(v / 128); } while (v > 0);
407
+ for (var i = 0; i < body.length - 1; i++) body[i] |= 0x80;
408
+ return Buffer.concat([Buffer.from([lead | 0x1f]), Buffer.from(body)]);
409
+ }
410
+
411
+ /**
412
+ * @primitive pki.asn1.encode
413
+ * @signature pki.asn1.encode(classBits, constructed, tagNumber, content) -> Buffer
414
+ * @since 0.1.0
415
+ * @status stable
416
+ * @related pki.asn1.decode
417
+ *
418
+ * Low-level TLV encoder — prepend the identifier + DER length to a content
419
+ * buffer. Most callers use the higher-level `build.*` helpers; this is the
420
+ * escape hatch for context-tagged and implicitly-tagged constructions.
421
+ *
422
+ * @example
423
+ * pki.asn1.encode(0x00, false, pki.asn1.TAGS.NULL, Buffer.alloc(0));
424
+ */
425
+ function encodeTLV(classBits, constructed, tagNumber, content) {
426
+ var body = Buffer.isBuffer(content) ? content : Buffer.from(content || []);
427
+ var id = encodeIdentifier(classBits, constructed, tagNumber);
428
+ return Buffer.concat([id, encodeLength(body.length), body]);
429
+ }
430
+
431
+ function _universal(tagNumber, constructed, content) {
432
+ return encodeTLV(CLASS_UNIVERSAL, constructed, tagNumber, content);
433
+ }
434
+
435
+ function intToDer(v) {
436
+ if (typeof v === "number") {
437
+ if (!Number.isSafeInteger(v)) throw new Asn1Error("asn1/bad-integer", "unsafe integer; pass a BigInt");
438
+ v = BigInt(v);
439
+ }
440
+ if (typeof v !== "bigint") throw new Asn1Error("asn1/bad-integer", "integer must be number or BigInt");
441
+ if (v === 0n) return Buffer.from([0]);
442
+ var bytes = [];
443
+ if (v > 0n) {
444
+ var t = v;
445
+ while (t > 0n) { bytes.unshift(Number(t & 0xffn)); t >>= 8n; }
446
+ if (bytes[0] & 0x80) bytes.unshift(0x00);
447
+ } else {
448
+ var len = 1;
449
+ while (v < -(1n << BigInt(8 * len - 1))) len += 1;
450
+ var tc = (1n << BigInt(8 * len)) + v;
451
+ for (var i = len - 1; i >= 0; i--) { bytes[i] = Number(tc & 0xffn); tc >>= 8n; }
452
+ }
453
+ return Buffer.from(bytes);
454
+ }
455
+
456
+ function encodeOidContent(dotted) {
457
+ if (typeof dotted !== "string" || dotted.length === 0) throw new OidError("oid/bad-input", "OID must be a dotted-decimal string");
458
+ var parts = dotted.split(".");
459
+ if (parts.length < 2) throw new OidError("oid/too-short", "OID needs at least two arcs");
460
+ var arcs = [];
461
+ for (var i = 0; i < parts.length; i++) {
462
+ if (!/^\d+$/.test(parts[i])) throw new OidError("oid/bad-arc", "arc " + JSON.stringify(parts[i]) + " is not a non-negative integer");
463
+ arcs.push(BigInt(parts[i]));
464
+ }
465
+ var a1 = arcs[0], a2 = arcs[1];
466
+ if (a1 > 2n) throw new OidError("oid/bad-arc", "first arc must be 0, 1, or 2");
467
+ if (a1 < 2n && a2 >= 40n) throw new OidError("oid/bad-arc", "second arc must be < 40 when the first arc is 0 or 1");
468
+ var subids = [a1 * 40n + a2].concat(arcs.slice(2));
469
+ var out = [];
470
+ for (var s = 0; s < subids.length; s++) {
471
+ var body = [];
472
+ var v = subids[s];
473
+ do { body.unshift(Number(v & 0x7fn)); v >>= 7n; } while (v > 0n);
474
+ for (var k = 0; k < body.length - 1; k++) body[k] |= 0x80;
475
+ for (var j = 0; j < body.length; j++) out.push(body[j]);
476
+ }
477
+ return Buffer.from(out);
478
+ }
479
+
480
+ var PRINTABLE_RE = /^[A-Za-z0-9 '()+,\-./:=?]*$/;
481
+
482
+ function _fmtTwo(n) { return (n < 10 ? "0" : "") + n; }
483
+
484
+ function _generalizedTimeString(date) {
485
+ return "" + date.getUTCFullYear() +
486
+ _fmtTwo(date.getUTCMonth() + 1) + _fmtTwo(date.getUTCDate()) +
487
+ _fmtTwo(date.getUTCHours()) + _fmtTwo(date.getUTCMinutes()) + _fmtTwo(date.getUTCSeconds()) + "Z";
488
+ }
489
+
490
+ function _utcTimeString(date) {
491
+ var yy = date.getUTCFullYear() % 100;
492
+ return _fmtTwo(yy) +
493
+ _fmtTwo(date.getUTCMonth() + 1) + _fmtTwo(date.getUTCDate()) +
494
+ _fmtTwo(date.getUTCHours()) + _fmtTwo(date.getUTCMinutes()) + _fmtTwo(date.getUTCSeconds()) + "Z";
495
+ }
496
+
497
+ /**
498
+ * @primitive pki.asn1.build
499
+ * @signature pki.asn1.build.sequence([ ...tlvBuffers ]) -> Buffer
500
+ * @since 0.1.0
501
+ * @status stable
502
+ * @related pki.asn1.encode
503
+ *
504
+ * Canonical-DER value builders. Each returns the full TLV Buffer for one
505
+ * value; `sequence` / `set` / `setOf` take arrays of already-built child
506
+ * TLVs. `setOf` sorts its members by their DER encoding as X.690 requires.
507
+ *
508
+ * @example
509
+ * var rdn = pki.asn1.build.sequence([
510
+ * pki.asn1.build.oid("2.5.4.3"),
511
+ * pki.asn1.build.utf8("example.com"),
512
+ * ]);
513
+ */
514
+ var build = {
515
+ boolean: function (v) { return _universal(TAGS.BOOLEAN, false, Buffer.from([v ? 0xff : 0x00])); },
516
+ integer: function (v) { return _universal(TAGS.INTEGER, false, Buffer.isBuffer(v) ? v : intToDer(v)); },
517
+ enumerated: function (v) { return _universal(TAGS.ENUMERATED, false, Buffer.isBuffer(v) ? v : intToDer(v)); },
518
+ nullValue: function () { return _universal(TAGS.NULL, false, Buffer.alloc(0)); },
519
+ oid: function (dotted) { return _universal(TAGS.OBJECT_IDENTIFIER, false, encodeOidContent(dotted)); },
520
+ octetString: function (buf) { return _universal(TAGS.OCTET_STRING, false, _asBuffer(buf, "build.octetString")); },
521
+ bitString: function (buf, unusedBits) {
522
+ var u = unusedBits || 0;
523
+ if (u < 0 || u > 7) throw new Asn1Error("asn1/bad-bit-string", "unusedBits must be 0..7");
524
+ return _universal(TAGS.BIT_STRING, false, Buffer.concat([Buffer.from([u]), _asBuffer(buf, "build.bitString")]));
525
+ },
526
+ utf8: function (s) { return _universal(TAGS.UTF8_STRING, false, Buffer.from(String(s), "utf8")); },
527
+ ia5: function (s) { return _universal(TAGS.IA5_STRING, false, Buffer.from(String(s), "latin1")); },
528
+ printable: function (s) {
529
+ s = String(s);
530
+ if (!PRINTABLE_RE.test(s)) throw new Asn1Error("asn1/bad-printable-string", "value has characters outside the PrintableString set");
531
+ return _universal(TAGS.PRINTABLE_STRING, false, Buffer.from(s, "latin1"));
532
+ },
533
+ utcTime: function (date) { return _universal(TAGS.UTC_TIME, false, Buffer.from(_utcTimeString(date), "latin1")); },
534
+ generalizedTime: function (date) { return _universal(TAGS.GENERALIZED_TIME, false, Buffer.from(_generalizedTimeString(date), "latin1")); },
535
+ sequence: function (children) { return _universal(TAGS.SEQUENCE, true, Buffer.concat(_asBufferArray(children, "build.sequence"))); },
536
+ set: function (children) { return _universal(TAGS.SET, true, Buffer.concat(_asBufferArray(children, "build.set"))); },
537
+ setOf: function (children) {
538
+ var arr = _asBufferArray(children, "build.setOf").slice();
539
+ arr.sort(Buffer.compare);
540
+ return _universal(TAGS.SET, true, Buffer.concat(arr));
541
+ },
542
+ // Context-tagged constructions for [n] EXPLICIT / IMPLICIT tagging.
543
+ explicit: function (tagNumber, inner) { return encodeTLV(CLASS_CONTEXT, true, tagNumber, _asBuffer(inner, "build.explicit")); },
544
+ contextPrimitive: function (tagNumber, content) { return encodeTLV(CLASS_CONTEXT, false, tagNumber, _asBuffer(content, "build.contextPrimitive")); },
545
+ contextConstructed: function (tagNumber, content) { return encodeTLV(CLASS_CONTEXT, true, tagNumber, _asBuffer(content, "build.contextConstructed")); },
546
+ raw: function (buf) { return _asBuffer(buf, "build.raw"); },
547
+ };
548
+
549
+ function _asBufferArray(arr, who) {
550
+ if (!Array.isArray(arr)) throw new Asn1Error("asn1/bad-children", who + ": expected an array of TLV buffers");
551
+ return arr.map(function (b) { return _asBuffer(b, who); });
552
+ }
553
+
554
+ module.exports = {
555
+ TAGS: TAGS,
556
+ decode: decode,
557
+ // `encode` is the documented public name for the low-level TLV encoder;
558
+ // `encodeTLV` remains as the descriptive alias.
559
+ encode: encodeTLV,
560
+ encodeTLV: encodeTLV,
561
+ encodeLength: encodeLength,
562
+ encodeIdentifier: encodeIdentifier,
563
+ decodeOidContent: decodeOidContent,
564
+ encodeOidContent: encodeOidContent,
565
+ build: build,
566
+ read: {
567
+ boolean: readBoolean,
568
+ integer: readInteger,
569
+ bitString: readBitString,
570
+ octetString: readOctetString,
571
+ nullValue: readNull,
572
+ oid: readOid,
573
+ string: readString,
574
+ time: readTime,
575
+ },
576
+ };
@@ -0,0 +1,115 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.C
6
+ * @nav Core
7
+ * @title Constants
8
+ * @order 10
9
+ * @featured true
10
+ * @slug constants
11
+ *
12
+ * @intro
13
+ * Version-stable constant namespace for the toolkit. Scale values are
14
+ * FUNCTIONS, not pre-baked discrete numbers: `C.TIME.days(30)` reads
15
+ * at the call site and computes at boot, so a caller never hand-writes
16
+ * `30 * 24 * 60 * 60 * 1000` (a raw-literal the codebase gate refuses)
17
+ * and a reviewer never has to decode it.
18
+ *
19
+ * Every scale helper is config-time / entry-point validation: it THROWS
20
+ * `ConstantsError` on a non-finite or negative argument, so an operator
21
+ * catches the typo at boot rather than shipping a silently-wrong window.
22
+ *
23
+ * @card
24
+ * Functional scale helpers (`C.TIME.*`, `C.BYTES.*`) plus the toolkit
25
+ * version and shared codec limits.
26
+ */
27
+
28
+ var frameworkError = require("./framework-error");
29
+
30
+ var ConstantsError = frameworkError.ConstantsError;
31
+
32
+ // _positive(n, who) — the shared guard every scale helper runs. Config-
33
+ // time tier: a bad scale argument is an authoring bug, so it throws.
34
+ function _positive(n, who) {
35
+ if (typeof n !== "number" || !isFinite(n) || n < 0) {
36
+ throw new ConstantsError(
37
+ "constants/bad-scale",
38
+ who + ": expected a finite number >= 0, got " + String(n)
39
+ );
40
+ }
41
+ return n;
42
+ }
43
+
44
+ var MS_PER_SECOND = 1000;
45
+ var SECONDS_PER_MINUTE = 60;
46
+ var MINUTES_PER_HOUR = 60;
47
+ var HOURS_PER_DAY = 24;
48
+ var DAYS_PER_WEEK = 7;
49
+
50
+ /**
51
+ * @primitive pki.C.TIME
52
+ * @signature C.TIME.days(n) -> milliseconds
53
+ * @since 0.1.0
54
+ * @status stable
55
+ *
56
+ * Duration helpers. Each returns an integer count of milliseconds so the
57
+ * value drops straight into `setTimeout`, a validity window, or an OCSP
58
+ * `nextUpdate` computation. Composing reads naturally:
59
+ * `C.TIME.days(365)` for a one-year certificate lifetime.
60
+ *
61
+ * @example
62
+ * var oneYear = pki.C.TIME.days(365);
63
+ * // -> 31536000000
64
+ */
65
+ var TIME = {
66
+ ms: function (n) { return Math.round(_positive(n, "C.TIME.ms")); },
67
+ seconds: function (n) { return Math.round(_positive(n, "C.TIME.seconds") * MS_PER_SECOND); },
68
+ minutes: function (n) { return Math.round(_positive(n, "C.TIME.minutes") * SECONDS_PER_MINUTE * MS_PER_SECOND); },
69
+ hours: function (n) { return Math.round(_positive(n, "C.TIME.hours") * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
70
+ days: function (n) { return Math.round(_positive(n, "C.TIME.days") * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
71
+ weeks: function (n) { return Math.round(_positive(n, "C.TIME.weeks") * DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
72
+ };
73
+
74
+ var BYTES_PER_KIB = 1024;
75
+
76
+ /**
77
+ * @primitive pki.C.BYTES
78
+ * @signature C.BYTES.mib(n) -> bytes
79
+ * @since 0.1.0
80
+ * @status stable
81
+ *
82
+ * Binary-magnitude size helpers. Each returns an integer byte count for
83
+ * codec limits (max DER input, max PEM block) so a size bound reads as
84
+ * `C.BYTES.mib(16)` instead of `16 * 1024 * 1024`.
85
+ *
86
+ * @example
87
+ * var cap = pki.C.BYTES.mib(16);
88
+ * // -> 16777216
89
+ */
90
+ var BYTES = {
91
+ b: function (n) { return Math.round(_positive(n, "C.BYTES.b")); },
92
+ kib: function (n) { return Math.round(_positive(n, "C.BYTES.kib") * BYTES_PER_KIB); },
93
+ mib: function (n) { return Math.round(_positive(n, "C.BYTES.mib") * BYTES_PER_KIB * BYTES_PER_KIB); },
94
+ gib: function (n) { return Math.round(_positive(n, "C.BYTES.gib") * BYTES_PER_KIB * BYTES_PER_KIB * BYTES_PER_KIB); },
95
+ };
96
+
97
+ // LIMITS — shared codec ceilings. A DER document larger than this, or
98
+ // nested deeper than this, is refused before the parser walks it: an
99
+ // unbounded length prefix or a pathologically-nested SEQUENCE is a
100
+ // classic decoder-DoS, so the bound is a fail-closed default rather
101
+ // than a tunable an operator must remember to set.
102
+ var LIMITS = {
103
+ DER_MAX_BYTES: BYTES.mib(16),
104
+ DER_MAX_DEPTH: 64,
105
+ PEM_MAX_BYTES: BYTES.mib(16),
106
+ };
107
+
108
+ var VERSION = "0.1.0";
109
+
110
+ module.exports = {
111
+ TIME: TIME,
112
+ BYTES: BYTES,
113
+ LIMITS: LIMITS,
114
+ version: VERSION,
115
+ };