@blamejs/pki 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/asn1-der.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * @slug asn1
11
11
  *
12
12
  * @intro
13
- * A strict DER (Distinguished Encoding Rules) codec the byte layer
13
+ * A strict DER (Distinguished Encoding Rules) codec -- the byte layer
14
14
  * every X.509 / PKCS / CMS structure is built on. The decoder is
15
15
  * fail-closed: it rejects the BER shapes DER forbids (indefinite
16
16
  * length, non-minimal length or integer encodings, trailing garbage,
@@ -22,8 +22,8 @@
22
22
  * turn a node into a JS value (BigInt, dotted OID, Date, string); the
23
23
  * `build.*` helpers construct canonical DER from JS values. Because DER
24
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.
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
27
  *
28
28
  * @card
29
29
  * Strict, fail-closed DER decode / encode with a navigable node tree
@@ -38,10 +38,10 @@ var OidError = frameworkError.OidError;
38
38
 
39
39
  // ---- Tag constants (universal class) --------------------------------
40
40
 
41
- // L1 the ASN.1 universal-type descriptor registry: the single source of
42
- // truth for the codec's type metadata. Each entry is DECLARATIVE DATA
41
+ // L1 -- the ASN.1 universal-type descriptor registry: the single source of
42
+ // truth for the codec's type metadata. Each entry is DECLARATIVE DATA --
43
43
  // { tag, form } where form is the DER encoding form ("primitive" or
44
- // "constructed", X.690 §810.2). TAGS and the two structural type-sets below
44
+ // "constructed", X.690 sec. 8/sec. 10.2). TAGS and the two structural type-sets below
45
45
  // are all DERIVED from it, so registering a universal type is one descriptor
46
46
  // entry and the decoder's form checks (below) cover it automatically.
47
47
  var UNIVERSAL_TYPES = {
@@ -51,7 +51,9 @@ var UNIVERSAL_TYPES = {
51
51
  OCTET_STRING: { tag: 0x04, form: "primitive" },
52
52
  NULL: { tag: 0x05, form: "primitive" },
53
53
  OBJECT_IDENTIFIER: { tag: 0x06, form: "primitive" },
54
+ EXTERNAL: { tag: 0x08, form: "constructed" },
54
55
  ENUMERATED: { tag: 0x0a, form: "primitive" },
56
+ EMBEDDED_PDV: { tag: 0x0b, form: "constructed" },
55
57
  UTF8_STRING: { tag: 0x0c, form: "primitive" },
56
58
  SEQUENCE: { tag: 0x10, form: "constructed" },
57
59
  SET: { tag: 0x11, form: "constructed" },
@@ -62,10 +64,11 @@ var UNIVERSAL_TYPES = {
62
64
  GENERALIZED_TIME: { tag: 0x18, form: "primitive" },
63
65
  VISIBLE_STRING: { tag: 0x1a, form: "primitive" },
64
66
  UNIVERSAL_STRING: { tag: 0x1c, form: "primitive" },
67
+ CHARACTER_STRING: { tag: 0x1d, form: "constructed" },
65
68
  BMP_STRING: { tag: 0x1e, form: "primitive" },
66
69
  };
67
70
 
68
- // TAGS { name: tag } derived from the registry; the public constant consumers
71
+ // TAGS { name: tag } -- derived from the registry; the public constant consumers
69
72
  // read (pki.asn1.TAGS.INTEGER, ...).
70
73
  var TAGS = {};
71
74
  Object.keys(UNIVERSAL_TYPES).forEach(function (k) { TAGS[k] = UNIVERSAL_TYPES[k].tag; });
@@ -76,17 +79,19 @@ var CLASS_CONTEXT = 0x80;
76
79
  var CLASS_PRIVATE = 0xc0;
77
80
  var CONSTRUCTED_BIT = 0x20;
78
81
 
79
- // X.690 §8.9.1 / §8.11.1 / §10.2 (DER), the mirror rules a constructed-only
80
- // universal type (SEQUENCE/SET) MUST be encoded constructed, and a
81
- // primitive-only type (every other universal type the codec handles) MUST be
82
- // encoded primitive. Both sets are DERIVED from the registry above and keyed by
83
- // tag for an O(1) decode-time form check; a new descriptor is covered with no
84
- // code change here.
85
- var PRIMITIVE_ONLY_UNIVERSAL_TAGS = Object.create(null);
82
+ // X.690 sec. 8.9.1 / sec. 8.11.1 / sec. 10.2 (DER), the mirror rules -- an
83
+ // always-constructed universal type (SEQUENCE, SET, and the SEQUENCE-based
84
+ // EXTERNAL / EMBEDDED PDV / unrestricted CHARACTER STRING) MUST be encoded
85
+ // constructed, and every other universal type MUST be encoded primitive. The
86
+ // constructed-capable set is DERIVED from the registry above and keyed by tag
87
+ // for an O(1) decode-time form check. It is a WHITELIST: a universal tag
88
+ // outside it -- a registered primitive type and an unregistered one
89
+ // (NumericString, GeneralString, ObjectDescriptor, REAL, ...) alike -- has no
90
+ // DER constructed form, so the default is reject.
86
91
  var CONSTRUCTED_ONLY_UNIVERSAL_TAGS = Object.create(null);
87
92
  Object.keys(UNIVERSAL_TYPES).forEach(function (k) {
88
93
  var d = UNIVERSAL_TYPES[k];
89
- (d.form === "constructed" ? CONSTRUCTED_ONLY_UNIVERSAL_TAGS : PRIMITIVE_ONLY_UNIVERSAL_TAGS)[d.tag] = true;
94
+ if (d.form === "constructed") CONSTRUCTED_ONLY_UNIVERSAL_TAGS[d.tag] = true;
90
95
  });
91
96
 
92
97
  function _className(bits) {
@@ -118,7 +123,7 @@ function _asBuffer(input, who) {
118
123
  *
119
124
  * Parse DER into a node tree. Each node is
120
125
  * `{ tagClass, constructed, tagNumber, header, length, content, children,
121
- * bytes }` `content` is the primitive value slice, `children` the
126
+ * bytes }` -- `content` is the primitive value slice, `children` the
122
127
  * decoded sub-nodes of a constructed node, and `bytes` the full TLV slice
123
128
  * (all zero-copy views over the input).
124
129
  *
@@ -130,26 +135,39 @@ function _asBuffer(input, who) {
130
135
  * `ber: true` is a scoped relaxation for formats whose content regions are
131
136
  * normatively BER (RFC 7292 PKCS#12): it accepts an indefinite length on a
132
137
  * constructed value and a constructed OCTET STRING, whose segments are
133
- * reassembled into one primitive `content`. Nothing else is relaxed
138
+ * reassembled into one primitive `content`. Nothing else is relaxed --
134
139
  * definite lengths stay minimal, an indefinite length on a primitive value
135
140
  * and a foreign-type segment still reject, and the size / depth caps hold.
136
141
  *
137
142
  * @opts
138
143
  * maxBytes: number, // default: C.LIMITS.DER_MAX_BYTES (16 MiB)
139
144
  * maxDepth: number, // default: C.LIMITS.DER_MAX_DEPTH (64)
140
- * allowTrailing: boolean, // default: false allow bytes after the top TLV
141
- * ber: boolean, // default: false accept indefinite lengths +
145
+ * allowTrailing: boolean, // default: false -- allow bytes after the top TLV
146
+ * ber: boolean, // default: false -- accept indefinite lengths +
142
147
  * // constructed OCTET STRINGs (BER content regions)
143
148
  *
144
149
  * @example
145
150
  * var node = pki.asn1.decode(der);
146
151
  * node.tagNumber === pki.asn1.TAGS.SEQUENCE;
147
152
  */
153
+ // A size/depth cap opt: absent means the constants.LIMITS default; a provided
154
+ // value must be a non-negative finite integer. A non-finite cap (NaN, Infinity)
155
+ // silently DISABLES the guard -- every `>` compare against it is false -- so a
156
+ // deeply nested input would run to a bare RangeError instead of the typed
157
+ // asn1/too-deep verdict. A bad cap is a config fault: throw at entry.
158
+ function _capOpt(v, key, dflt) {
159
+ if (v === undefined) return dflt;
160
+ if (typeof v !== "number" || !isFinite(v) || v < 0 || Math.floor(v) !== v) {
161
+ throw new TypeError("decode: " + key + " must be a non-negative integer");
162
+ }
163
+ return v;
164
+ }
165
+
148
166
  function decode(input, opts) {
149
167
  opts = opts || {};
150
168
  var buf = _asBuffer(input, "decode");
151
- var maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : constants.LIMITS.DER_MAX_BYTES;
152
- var maxDepth = typeof opts.maxDepth === "number" ? opts.maxDepth : constants.LIMITS.DER_MAX_DEPTH;
169
+ var maxBytes = _capOpt(opts.maxBytes, "maxBytes", constants.LIMITS.DER_MAX_BYTES);
170
+ var maxDepth = _capOpt(opts.maxDepth, "maxDepth", constants.LIMITS.DER_MAX_DEPTH);
153
171
  if (buf.length > maxBytes) {
154
172
  throw new Asn1Error("asn1/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
155
173
  }
@@ -191,26 +209,30 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth) {
191
209
  }
192
210
  }
193
211
 
194
- // X.690 8.9.1 / 8.11.1 a universal constructed-only type (SEQUENCE / SET)
195
- // is always constructed; a primitive-tagged one is not valid DER (and would
196
- // decode to a leaf that constructed-structure consumers dereference as a
197
- // parent). Driven by the registry-derived set, not a hardcoded tag list.
212
+ // X.690 8.9.1 / 8.11.1 -- an always-constructed universal type (SEQUENCE, SET,
213
+ // EXTERNAL, EMBEDDED PDV, CHARACTER STRING) is always constructed; a
214
+ // primitive-tagged one is not valid DER (and would decode to a leaf that
215
+ // constructed-structure consumers dereference as a parent). Driven by the
216
+ // registry-derived set, not a hardcoded tag list.
198
217
  if (tagClassBits === CLASS_UNIVERSAL && CONSTRUCTED_ONLY_UNIVERSAL_TAGS[tagNumber] && !constructed) {
199
- throw new Asn1Error("asn1/bad-tlv", "a universal constructed-only type (SEQUENCE/SET) must be constructed");
218
+ throw new Asn1Error("asn1/bad-tlv", "a universal constructed-only type (SEQUENCE/SET/EXTERNAL/EMBEDDED PDV/CHARACTER STRING) must be constructed");
200
219
  }
201
220
 
202
- // X.690 §8.x / §10.2 the mirror rule: a universal primitive-only type
203
- // (INTEGER, OID, BOOLEAN, the restricted strings, times, BIT/OCTET STRING)
204
- // encoded constructed is not valid DER. Without this a constructed string
205
- // tag decodes to a childless node that later paths (an X.509 DN attribute
206
- // value) would hex-render or dereference instead of failing closed.
207
- // The ber mode licenses exactly one constructed primitive-only type the
208
- // OCTET STRING (the streamed content carrier RFC 7292 §4.1 permits); its
221
+ // X.690 sec. 8.x / sec. 10.2 -- the mirror rule: a universal type other than
222
+ // SEQUENCE/SET (INTEGER, OID, BOOLEAN, every restricted string type --
223
+ // registered or not -- times, BIT/OCTET STRING, REAL, ...) encoded
224
+ // constructed is not valid DER. Without this a constructed string tag
225
+ // decodes to a childless node that later paths (an X.509 DN attribute
226
+ // value) would hex-render or dereference instead of failing closed; the
227
+ // constructed-capable set is a whitelist so an UNREGISTERED string type
228
+ // (a constructed GeneralString) cannot slip past the form check either.
229
+ // The ber mode licenses exactly one constructed primitive-only type -- the
230
+ // OCTET STRING (the streamed content carrier RFC 7292 sec. 4.1 permits); its
209
231
  // segments are reassembled below. Every other primitive-only type stays
210
232
  // primitive even in BER. Reassembly re-copies each nesting level's payload,
211
233
  // so nesting is capped on the way down: real producers segment one level
212
234
  // deep, and a deep chain multiplies transient memory without carrying data.
213
- if (tagClassBits === CLASS_UNIVERSAL && constructed && PRIMITIVE_ONLY_UNIVERSAL_TAGS[tagNumber]) {
235
+ if (tagClassBits === CLASS_UNIVERSAL && constructed && !CONSTRUCTED_ONLY_UNIVERSAL_TAGS[tagNumber]) {
214
236
  if (!(ber && tagNumber === TAGS.OCTET_STRING)) {
215
237
  throw new Asn1Error("asn1/constructed-primitive-type", "a universal primitive-only type must be encoded primitive in DER");
216
238
  }
@@ -226,7 +248,7 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth) {
226
248
  if (lenByte < 0x80) {
227
249
  length = lenByte;
228
250
  } else if (lenByte === 0x80) {
229
- // X.690 §8.1.3.6 indefinite length is only defined for a constructed
251
+ // X.690 sec. 8.1.3.6 -- indefinite length is only defined for a constructed
230
252
  // encoding; the value runs to the end-of-contents octets.
231
253
  if (!ber || !constructed) {
232
254
  throw new Asn1Error("asn1/indefinite-length", "indefinite length is not valid DER");
@@ -296,7 +318,7 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth) {
296
318
  // single-segment encoding would carry; `bytes` keeps the original wire
297
319
  // range for raw round-trip surfaces. Nested constructed segments arrive
298
320
  // already reassembled by the recursion (their nesting depth is capped on
299
- // the way down see the strDepth check above).
321
+ // the way down -- see the strDepth check above).
300
322
  if (ber && constructed && tagClassBits === CLASS_UNIVERSAL && tagNumber === TAGS.OCTET_STRING) {
301
323
  var segments = [];
302
324
  for (var s = 0; s < children.length; s++) {
@@ -311,7 +333,7 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth) {
311
333
  }
312
334
  // A context-tagged constructed node from a ber decode may be an IMPLICIT
313
335
  // streamed string (the tag hides the underlying type), which only the
314
- // schema-driven typed reader can identify mark it so readers reassemble
336
+ // schema-driven typed reader can identify -- mark it so readers reassemble
315
337
  // exactly those nodes and a strict decode's verdicts never change.
316
338
  if (ber && constructed && tagClassBits === CLASS_CONTEXT && node.children) {
317
339
  node.ber = true;
@@ -345,7 +367,7 @@ function readBoolean(node) {
345
367
  }
346
368
 
347
369
  // INTEGER and ENUMERATED share DER content encoding (a two's-complement big-endian
348
- // value) but are DISTINCT universal types. This decodes that shared content the
370
+ // value) but are DISTINCT universal types. This decodes that shared content -- the
349
371
  // caller asserts the tag first. `typeName` names the type in error messages; the
350
372
  // stable error codes stay the integer family (a caller switching on `code` treats
351
373
  // both alike since the encoding is identical).
@@ -355,7 +377,7 @@ function _readIntegerLikeContent(node, typeName, who) {
355
377
  if (c.length === 0) throw new Asn1Error("asn1/bad-integer", typeName + " must have at least 1 content octet");
356
378
  // Defense-in-depth: refuse an over-cap magnitude before touching BigInt.
357
379
  // A one-shot hex parse is linear, but the length still bounds worst-case
358
- // work reject a hostile length prefix up front. The cap bounds the
380
+ // work -- reject a hostile length prefix up front. The cap bounds the
359
381
  // MAGNITUDE; a positive INTEGER whose top bit is set carries one leading
360
382
  // 0x00 DER sign octet, so allow cap + 1 content bytes (an RSA-131072
361
383
  // modulus is 16384 magnitude bytes plus that sign pad).
@@ -374,7 +396,7 @@ function _readIntegerLikeContent(node, typeName, who) {
374
396
 
375
397
  // read.integer is STRICT on the tag: an ENUMERATED node (which shares INTEGER's
376
398
  // content encoding) is NOT accepted here. Coercing an ENUMERATED to an INTEGER is a
377
- // type confusion an INTEGER-pinned field (a certificate/CSR version, a serial
399
+ // type confusion -- an INTEGER-pinned field (a certificate/CSR version, a serial
378
400
  // number, a cRLNumber) encoded as ENUMERATED would slip past a lenient reader.
379
401
  // Read ENUMERATED values with read.enumerated.
380
402
  function readInteger(node) {
@@ -389,7 +411,7 @@ function readEnumerated(node) {
389
411
  return _readIntegerLikeContent(node, "ENUMERATED", "readEnumerated");
390
412
  }
391
413
 
392
- // Read a [tag] IMPLICIT INTEGER a context-class PRIMITIVE node whose content is an
414
+ // Read a [tag] IMPLICIT INTEGER -- a context-class PRIMITIVE node whose content is an
393
415
  // integer body (the IMPLICIT tag replaces the universal INTEGER tag). Same minimal
394
416
  // two's-complement content rules as a universal INTEGER. The integer counterpart of
395
417
  // readBitStringImplicit, for the RFC 3161 Accuracy millis [0] / micros [1] fields.
@@ -403,7 +425,7 @@ function readIntegerImplicit(node, tag) {
403
425
 
404
426
  // The BIT STRING content decoder (the leading unused-bit octet + body, with the
405
427
  // DER zero-pad rule). Shared by the universal reader and the IMPLICIT
406
- // context-tagged reader the caller asserts the tag first.
428
+ // context-tagged reader -- the caller asserts the tag first.
407
429
  function _readBitStringContent(node, who) {
408
430
  _expectPrimitive(node, who);
409
431
  var c = node.content;
@@ -411,7 +433,7 @@ function _readBitStringContent(node, who) {
411
433
  var unusedBits = c[0];
412
434
  if (unusedBits > 7) throw new Asn1Error("asn1/bad-bit-string", "unused-bit count " + unusedBits + " > 7");
413
435
  if (unusedBits > 0 && c.length === 1) throw new Asn1Error("asn1/bad-bit-string", "unused bits declared over an empty body");
414
- // X.690 11.2.1 the declared unused low bits of the final octet must be
436
+ // X.690 11.2.1 -- the declared unused low bits of the final octet must be
415
437
  // zero in DER.
416
438
  if (unusedBits > 0 && c.length > 1) {
417
439
  var mask = (1 << unusedBits) - 1;
@@ -425,10 +447,10 @@ function readBitString(node) {
425
447
  return _readBitStringContent(node, "readBitString");
426
448
  }
427
449
 
428
- // Read a [tag] IMPLICIT BIT STRING a context-class PRIMITIVE node whose content
450
+ // Read a [tag] IMPLICIT BIT STRING -- a context-class PRIMITIVE node whose content
429
451
  // is a bit-string body (identical content rules to a universal BIT STRING). The
430
452
  // IMPLICIT tag replaces the universal one, so there is no inner universal node.
431
- // Used for the PKCS#8 OneAsymmetricKey publicKey [1] (RFC 5958 §2).
453
+ // Used for the PKCS#8 OneAsymmetricKey publicKey [1] (RFC 5958 sec. 2).
432
454
  function readBitStringImplicit(node, tag) {
433
455
  if (node.tagClass !== "context" || node.tagNumber !== tag) {
434
456
  throw new Asn1Error("asn1/unexpected-tag", "readBitStringImplicit: expected context tag [" + tag +
@@ -443,16 +465,16 @@ function readOctetString(node) {
443
465
  return node.content;
444
466
  }
445
467
 
446
- // Read a [tag] IMPLICIT OCTET STRING a context-class PRIMITIVE node whose
468
+ // Read a [tag] IMPLICIT OCTET STRING -- a context-class PRIMITIVE node whose
447
469
  // content is the octet-string body (the IMPLICIT tag replaces the universal one,
448
470
  // so there is no inner universal node). Primitive-only, like its universal
449
471
  // counterpart, so a strict decode's constructed/streamed form is rejected.
450
- // A node from a ber decode (the `ber` marker) may carry the streamed form
472
+ // A node from a ber decode (the `ber` marker) may carry the streamed form --
451
473
  // the context tag hides the underlying type from the decoder, so THIS reader
452
474
  // is where a [tag] IMPLICIT OCTET STRING's segments reassemble (the shape CMS
453
475
  // ciphertext streams as). Used for the CMS SignerIdentifier
454
- // subjectKeyIdentifier [0] (RFC 5652 §5.3) and EncryptedContentInfo
455
- // encryptedContent [0] (§6.1).
476
+ // subjectKeyIdentifier [0] (RFC 5652 sec. 5.3) and EncryptedContentInfo
477
+ // encryptedContent [0] (sec. 6.1).
456
478
  function readOctetStringImplicit(node, tag) {
457
479
  if (node.tagClass !== "context" || node.tagNumber !== tag) {
458
480
  throw new Asn1Error("asn1/unexpected-tag", "readOctetStringImplicit: expected context tag [" + tag +
@@ -480,10 +502,27 @@ function readNull(node) {
480
502
  return null;
481
503
  }
482
504
 
483
- // Read a [tag] IMPLICIT NULL a context-class PRIMITIVE node with empty content.
505
+ // Read a [tag] IMPLICIT BOOLEAN -- a context-class PRIMITIVE node whose single
506
+ // content octet obeys the DER BOOLEAN rules (0x00 FALSE / 0xFF TRUE; anything
507
+ // else rejected). The IMPLICIT tag replaces the universal BOOLEAN tag. Used for
508
+ // the RFC 5280 sec. 5.2.5 IssuingDistributionPoint scope flags.
509
+ function readBooleanImplicit(node, tag) {
510
+ if (node.tagClass !== "context" || node.tagNumber !== tag) {
511
+ throw new Asn1Error("asn1/unexpected-tag", "readBooleanImplicit: expected context tag [" + tag +
512
+ "], got " + node.tagClass + "/" + node.tagNumber);
513
+ }
514
+ _expectPrimitive(node, "readBooleanImplicit");
515
+ if (node.content.length !== 1) throw new Asn1Error("asn1/bad-boolean", "BOOLEAN content must be 1 octet");
516
+ var v = node.content[0];
517
+ if (v === 0x00) return false;
518
+ if (v === 0xff) return true;
519
+ throw new Asn1Error("asn1/bad-boolean", "DER BOOLEAN must be 0x00 or 0xFF, got 0x" + v.toString(16));
520
+ }
521
+
522
+ // Read a [tag] IMPLICIT NULL -- a context-class PRIMITIVE node with empty content.
484
523
  // The IMPLICIT tag replaces the universal NULL tag, so there is no inner universal
485
524
  // node. The empty-content and primitive-form rules of a universal NULL still hold.
486
- // Used for the OCSP CertStatus good [0] / unknown [2] arms (RFC 6960 §4.2.1).
525
+ // Used for the OCSP CertStatus good [0] / unknown [2] arms (RFC 6960 sec. 4.2.1).
487
526
  function readNullImplicit(node, tag) {
488
527
  if (node.tagClass !== "context" || node.tagNumber !== tag) {
489
528
  throw new Asn1Error("asn1/unexpected-tag", "readNullImplicit: expected context tag [" + tag +
@@ -500,7 +539,7 @@ function readNullImplicit(node, tag) {
500
539
  * @since 0.1.15
501
540
  * @originated 0.1.0
502
541
  * @status stable
503
- * @spec X.690 §8.19
542
+ * @spec X.690 sec. 8.19
504
543
  * @related pki.oid.name
505
544
  *
506
545
  * Decode an OBJECT IDENTIFIER node to its dotted-decimal string, enforcing
@@ -553,7 +592,7 @@ function _decodeText(buf, encoding) {
553
592
  return buf.toString(encoding);
554
593
  }
555
594
 
556
- // IA5String is 7-bit ASCII (T.50 / IA5) every content octet is 0x00..0x7F.
595
+ // IA5String is 7-bit ASCII (T.50 / IA5) -- every content octet is 0x00..0x7F.
557
596
  function _decodeIa5(buf) {
558
597
  for (var i = 0; i < buf.length; i++) {
559
598
  if (buf[i] > 0x7F) throw new Asn1Error("asn1/bad-ia5-string", "IA5String requires 7-bit ASCII");
@@ -562,7 +601,7 @@ function _decodeIa5(buf) {
562
601
  }
563
602
 
564
603
  // VisibleString (ISO 646 IRV / X.690) is the printable ASCII range 0x20..0x7E
565
- // no control characters and no bytes with the high bit set.
604
+ // -- no control characters and no bytes with the high bit set.
566
605
  function _decodeVisible(buf) {
567
606
  for (var i = 0; i < buf.length; i++) {
568
607
  if (buf[i] < 0x20 || buf[i] > 0x7E) throw new Asn1Error("asn1/bad-visible-string", "VisibleString must be 0x20..0x7E");
@@ -633,14 +672,14 @@ function _decodeUtf32be(buf) {
633
672
 
634
673
  var UTC_RE = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
635
674
  var GEN_RE = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
636
- // The X.690 §11.7 DER fractional-seconds GeneralizedTime profile: seconds present,
675
+ // The X.690 sec. 11.7 DER fractional-seconds GeneralizedTime profile: seconds present,
637
676
  // a `.` decimal separator (never `,`), a non-empty fraction, Z-terminated. The
638
- // trailing-zeros rule (§11.7.3) is enforced separately. Used by RFC 3161 genTime.
677
+ // trailing-zeros rule (sec. 11.7.3) is enforced separately. Used by RFC 3161 genTime.
639
678
  var GEN_FRAC_RE = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d+)Z$/;
640
679
 
641
- // readTime(node, opts?) opts.allowFractional enables the RFC 3161 / X.690 §11.7
680
+ // readTime(node, opts?) -- opts.allowFractional enables the RFC 3161 / X.690 sec. 11.7
642
681
  // fractional-seconds GeneralizedTime profile. Fractional is OFF by default because
643
- // most profiles (RFC 5280 certificate/CRL Validity, §4.1.2.5.2) require the integer-
682
+ // most profiles (RFC 5280 certificate/CRL Validity, sec. 4.1.2.5.2) require the integer-
644
683
  // second YYYYMMDDHHMMSSZ form and MUST NOT include a fraction; only callers that
645
684
  // permit sub-second precision (TSP genTime) pass allowFractional.
646
685
  function readTime(node, opts) {
@@ -652,7 +691,7 @@ function readTime(node, opts) {
652
691
  m = UTC_RE.exec(s);
653
692
  if (!m) throw new Asn1Error("asn1/bad-utctime", "UTCTime must be YYMMDDHHMMSSZ, got " + JSON.stringify(s));
654
693
  year = parseInt(m[1], 10);
655
- // RFC 5280 §4.1.2.5.1 YY < 50 => 20YY, else 19YY.
694
+ // RFC 5280 sec. 4.1.2.5.1 -- YY < 50 => 20YY, else 19YY.
656
695
  year += (year < 50) ? 2000 : 1900;
657
696
  } else if (node.tagNumber === TAGS.GENERALIZED_TIME) {
658
697
  m = GEN_RE.exec(s);
@@ -660,9 +699,9 @@ function readTime(node, opts) {
660
699
  if (!(opts && opts.allowFractional)) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSSZ, got " + JSON.stringify(s));
661
700
  m = GEN_FRAC_RE.exec(s);
662
701
  if (!m) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime must be YYYYMMDDHHMMSS[.fraction]Z, got " + JSON.stringify(s));
663
- // X.690 §11.7.3 the fractional part MUST NOT end in a zero (and is non-empty
702
+ // X.690 sec. 11.7.3 -- the fractional part MUST NOT end in a zero (and is non-empty
664
703
  // by the regex). ".5Z" is valid; ".50Z" and ".500Z" are non-canonical. X.690
665
- // §11.7 places NO cap on the fraction length (RFC 3161 §2.4.2 gives the 5-digit
704
+ // sec. 11.7 places NO cap on the fraction length (RFC 3161 sec. 2.4.2 gives the 5-digit
666
705
  // example 19990609001326.34352Z), so a fraction of any length is accepted; the
667
706
  // returned Date is millisecond-precision, and a caller needing the exact fraction
668
707
  // reads it from node.content (the raw bytes, surfaced losslessly like the OID /
@@ -673,7 +712,9 @@ function readTime(node, opts) {
673
712
  } else {
674
713
  throw new Asn1Error("asn1/expected-time", "readTime: tag " + node.tagNumber + " is not a time type");
675
714
  }
676
- var month = parseInt(m[node.tagNumber === TAGS.UTC_TIME ? 2 : 2], 10);
715
+ // The month is capture group 2 in UTC_RE, GEN_RE, and GEN_FRAC_RE alike
716
+ // (the groups diverge only at 1, the year, and 7, the fraction).
717
+ var month = parseInt(m[2], 10);
677
718
  var day = parseInt(m[3], 10);
678
719
  var hour = parseInt(m[4], 10);
679
720
  var min = parseInt(m[5], 10);
@@ -727,11 +768,21 @@ function sequenceTlv(node) {
727
768
  }
728
769
 
729
770
  function encodeIdentifier(classBits, constructed, tagNumber) {
771
+ // Mirror encodeLength's input guard: a negative tag corrupts the identifier
772
+ // octet (-1 emits 0xff..), and a fractional tag is silently bit-truncated to
773
+ // a DIFFERENT tag -- an entry-point builder handed a bad tag throws instead
774
+ // of emitting malformed or non-round-trippable DER.
775
+ if (typeof tagNumber !== "number" || !isFinite(tagNumber) || tagNumber < 0 || Math.floor(tagNumber) !== tagNumber) {
776
+ throw new Asn1Error("asn1/bad-tag", "tag number must be a non-negative integer");
777
+ }
730
778
  var lead = classBits | (constructed ? CONSTRUCTED_BIT : 0);
731
779
  if (tagNumber < 0x1f) return Buffer.from([lead | tagNumber]);
732
780
  var body = [];
733
781
  var v = tagNumber;
734
782
  do { body.unshift(v & 0x7f); v = Math.floor(v / 128); } while (v > 0);
783
+ // Symmetric with the decoder's 4-octet high-tag-number cap, so the encoder
784
+ // can't emit an identifier decode() refuses as hostile.
785
+ if (body.length > 4) throw new Asn1Error("asn1/tag-too-large", "high-tag-number too large");
735
786
  for (var i = 0; i < body.length - 1; i++) body[i] |= 0x80;
736
787
  return Buffer.concat([Buffer.from([lead | 0x1f]), Buffer.from(body)]);
737
788
  }
@@ -744,7 +795,7 @@ function encodeIdentifier(classBits, constructed, tagNumber) {
744
795
  * @spec X.690, ISO/IEC 8825-1
745
796
  * @related pki.asn1.decode
746
797
  *
747
- * Low-level TLV encoder prepend the identifier + DER length to a content
798
+ * Low-level TLV encoder -- prepend the identifier + DER length to a content
748
799
  * buffer. Most callers use the higher-level `build.*` helpers; this is the
749
800
  * escape hatch for context-tagged and implicitly-tagged constructions.
750
801
  *
@@ -817,7 +868,7 @@ var PRINTABLE_RE = /^[A-Za-z0-9 '()+,\-./:=?]*$/;
817
868
  function _fmtTwo(n) { return (n < 10 ? "0" : "") + n; }
818
869
 
819
870
  function _generalizedTimeString(date) {
820
- // GeneralizedTime is YYYYMMDDHHMMSSZ a year below 1000 must still emit
871
+ // GeneralizedTime is YYYYMMDDHHMMSSZ -- a year below 1000 must still emit
821
872
  // four digits, or read.time rejects the short (12-14 char) content.
822
873
  var y = date.getUTCFullYear();
823
874
  if (y < 0 || y > 9999) throw new Asn1Error("asn1/bad-generalizedtime", "GeneralizedTime year " + y + " outside 0000..9999");
@@ -828,7 +879,7 @@ function _generalizedTimeString(date) {
828
879
  }
829
880
 
830
881
  function _utcTimeString(date) {
831
- // RFC 5280 §4.1.2.5.1 restricts UTCTime to 1950..2049; a year outside that
882
+ // RFC 5280 sec. 4.1.2.5.1 restricts UTCTime to 1950..2049; a year outside that
832
883
  // wraps to the wrong century (2050 -> "50" -> decodes as 1950).
833
884
  var y = date.getUTCFullYear();
834
885
  if (y < 1950 || y > 2049) throw new Asn1Error("asn1/bad-utctime", "UTCTime year " + y + " outside 1950..2049; use GeneralizedTime");
@@ -864,11 +915,17 @@ var build = {
864
915
  oid: function (dotted) { return _universal(TAGS.OBJECT_IDENTIFIER, false, encodeOidContent(dotted)); },
865
916
  octetString: function (buf) { return _universal(TAGS.OCTET_STRING, false, _asBuffer(buf, "build.octetString")); },
866
917
  bitString: function (buf, unusedBits) {
867
- var u = unusedBits || 0;
868
- if (u < 0 || u > 7) throw new Asn1Error("asn1/bad-bit-string", "unusedBits must be 0..7");
918
+ var u = unusedBits == null ? 0 : unusedBits;
919
+ // An integer in 0..7 (X.690 sec. 8.6.2.2). A fractional / non-finite count
920
+ // would be silently bit-truncated into a DIFFERENT value when written
921
+ // into the leading octet -- reject it like the other numeric builder
922
+ // inputs (encodeLength, the tag number).
923
+ if (typeof u !== "number" || !Number.isInteger(u) || u < 0 || u > 7) {
924
+ throw new Asn1Error("asn1/bad-bit-string", "unusedBits must be an integer 0..7");
925
+ }
869
926
  var body = _asBuffer(buf, "build.bitString");
870
927
  // X.690 8.6.2.3: an empty BIT STRING has no content octets, so its
871
- // unused-bit count must be zero there are no bits to leave unused.
928
+ // unused-bit count must be zero -- there are no bits to leave unused.
872
929
  if (u > 0 && body.length === 0) throw new Asn1Error("asn1/bad-bit-string", "empty BIT STRING must declare zero unused bits");
873
930
  // Stay canonical: refuse a tail whose declared unused low bits are set.
874
931
  if (u > 0 && body.length > 0) {
@@ -963,6 +1020,7 @@ module.exports = {
963
1020
  octetStringImplicit: readOctetStringImplicit,
964
1021
  nullValue: readNull,
965
1022
  nullImplicit: readNullImplicit,
1023
+ booleanImplicit: readBooleanImplicit,
966
1024
  oid: readOid,
967
1025
  string: readString,
968
1026
  time: readTime,
package/lib/constants.js CHANGED
@@ -17,8 +17,10 @@
17
17
  * and a reviewer never has to decode it.
18
18
  *
19
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.
20
+ * `ConstantsError` on a non-finite or negative argument, and on a product
21
+ * outside the safe-integer range, so an operator catches the typo at boot
22
+ * rather than shipping a silently-wrong window or an Infinity that would
23
+ * disable a size cap.
22
24
  *
23
25
  * @card
24
26
  * Functional scale helpers (`C.TIME.*`, `C.BYTES.*`) plus the toolkit
@@ -29,7 +31,7 @@ var frameworkError = require("./framework-error");
29
31
 
30
32
  var ConstantsError = frameworkError.ConstantsError;
31
33
 
32
- // _positive(n, who) the shared guard every scale helper runs. Config-
34
+ // _positive(n, who) -- the shared guard every scale helper runs. Config-
33
35
  // time tier: a bad scale argument is an authoring bug, so it throws.
34
36
  function _positive(n, who) {
35
37
  if (typeof n !== "number" || !isFinite(n) || n < 0) {
@@ -41,6 +43,22 @@ function _positive(n, who) {
41
43
  return n;
42
44
  }
43
45
 
46
+ // _scale(n, who, factor) -- validate the argument AND the product. A finite
47
+ // operand can still overflow the multiplication (days(1e304) -> Infinity),
48
+ // and an Infinity handed onward silently disables any bound compared
49
+ // against it (`len > Infinity` is always false) -- so a product outside the
50
+ // safe-integer range throws instead of returning.
51
+ function _scale(n, who, factor) {
52
+ var out = Math.round(_positive(n, who) * factor);
53
+ if (!Number.isSafeInteger(out)) {
54
+ throw new ConstantsError(
55
+ "constants/bad-scale",
56
+ who + ": the result " + String(out) + " is not a safe integer"
57
+ );
58
+ }
59
+ return out;
60
+ }
61
+
44
62
  var MS_PER_SECOND = 1000;
45
63
  var SECONDS_PER_MINUTE = 60;
46
64
  var MINUTES_PER_HOUR = 60;
@@ -64,12 +82,12 @@ var DAYS_PER_WEEK = 7;
64
82
  * // -> 31536000000
65
83
  */
66
84
  var TIME = {
67
- milliseconds: function (n) { return Math.round(_positive(n, "C.TIME.milliseconds")); },
68
- seconds: function (n) { return Math.round(_positive(n, "C.TIME.seconds") * MS_PER_SECOND); },
69
- minutes: function (n) { return Math.round(_positive(n, "C.TIME.minutes") * SECONDS_PER_MINUTE * MS_PER_SECOND); },
70
- hours: function (n) { return Math.round(_positive(n, "C.TIME.hours") * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
71
- days: function (n) { return Math.round(_positive(n, "C.TIME.days") * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
72
- 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); },
85
+ milliseconds: function (n) { return _scale(n, "C.TIME.milliseconds", 1); },
86
+ seconds: function (n) { return _scale(n, "C.TIME.seconds", MS_PER_SECOND); },
87
+ minutes: function (n) { return _scale(n, "C.TIME.minutes", SECONDS_PER_MINUTE * MS_PER_SECOND); },
88
+ hours: function (n) { return _scale(n, "C.TIME.hours", MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
89
+ days: function (n) { return _scale(n, "C.TIME.days", HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
90
+ weeks: function (n) { return _scale(n, "C.TIME.weeks", DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND); },
73
91
  };
74
92
 
75
93
  var BYTES_PER_KIB = 1024;
@@ -90,13 +108,13 @@ var BYTES_PER_KIB = 1024;
90
108
  * // -> 16777216
91
109
  */
92
110
  var BYTES = {
93
- b: function (n) { return Math.round(_positive(n, "C.BYTES.b")); },
94
- kib: function (n) { return Math.round(_positive(n, "C.BYTES.kib") * BYTES_PER_KIB); },
95
- mib: function (n) { return Math.round(_positive(n, "C.BYTES.mib") * BYTES_PER_KIB * BYTES_PER_KIB); },
96
- gib: function (n) { return Math.round(_positive(n, "C.BYTES.gib") * BYTES_PER_KIB * BYTES_PER_KIB * BYTES_PER_KIB); },
111
+ b: function (n) { return _scale(n, "C.BYTES.b", 1); },
112
+ kib: function (n) { return _scale(n, "C.BYTES.kib", BYTES_PER_KIB); },
113
+ mib: function (n) { return _scale(n, "C.BYTES.mib", BYTES_PER_KIB * BYTES_PER_KIB); },
114
+ gib: function (n) { return _scale(n, "C.BYTES.gib", BYTES_PER_KIB * BYTES_PER_KIB * BYTES_PER_KIB); },
97
115
  };
98
116
 
99
- // LIMITS shared codec ceilings. A DER document larger than this, or
117
+ // LIMITS -- shared codec ceilings. A DER document larger than this, or
100
118
  // nested deeper than this, is refused before the parser walks it: an
101
119
  // unbounded length prefix or a pathologically-nested SEQUENCE is a
102
120
  // classic decoder-DoS, so the bound is a fail-closed default rather
@@ -109,7 +127,7 @@ var BYTES = {
109
127
  // decoder-DoS the document cap does not stop. 16 KiB covers any real key
110
128
  // material (an RSA-131072 modulus), and 32 bytes covers any real OID arc:
111
129
  // the largest standard sub-identifier is a 128-bit UUID-based arc (X.667,
112
- // e.g. 2.25.<uuid>), which is 19 base-128 bytes so the cap must exceed
130
+ // e.g. 2.25.<uuid>), which is 19 base-128 bytes -- so the cap must exceed
113
131
  // that, not sit at 16. Anything larger than 32 bytes is refused as hostile.
114
132
  var LIMITS = {
115
133
  DER_MAX_BYTES: BYTES.mib(16),
@@ -117,21 +135,28 @@ var LIMITS = {
117
135
  PEM_MAX_BYTES: BYTES.mib(16),
118
136
  DER_MAX_INTEGER_BYTES: BYTES.kib(16),
119
137
  OID_MAX_SUBIDENTIFIER_BYTES: 32,
120
- // Certificate Transparency SCT-list bounds (RFC 6962 §3.3). The outer sct_list
138
+ // Certificate Transparency SCT-list bounds (RFC 6962 sec. 3.3). The outer sct_list
121
139
  // vector carries a 2-byte length prefix, so a well-formed list body is at most
122
140
  // 2^16-1 = 65535 bytes and the full TLS blob (prefix + body) is at most 65537.
123
141
  // The byte cap sits at that structural maximum, so the largest conforming list
124
142
  // is accepted while an oversized inner OCTET STRING (up to the DER document cap)
125
- // is refused BEFORE the list is walked; the per-list count cap is likewise
126
- // asserted before iteration so a hostile length prefix cannot drive unbounded
127
- // work (the CVE-2022-0778 class: crafted bytes inside a certificate extension
128
- // pinning a validator). A real chain carries 2-5 SCTs; 256 is far above policy.
143
+ // is refused BEFORE the list is walked. The per-list count cap is asserted per
144
+ // element DURING the walk (an element count is unknowable before walking
145
+ // variable-length elements), bounding total work at SCT_MAX_COUNT+1 elements
146
+ // regardless of a hostile length prefix (the CVE-2022-0778 class: crafted bytes
147
+ // inside a certificate extension pinning a validator). A real chain carries
148
+ // 2-5 SCTs; 256 is far above policy.
129
149
  SCT_MAX_BYTES: BYTES.kib(64) + 1,
130
150
  SCT_MAX_COUNT: 256,
131
151
  // Certification-path length ceiling: bounds the per-cert asymmetric verify
132
152
  // work on an untrusted certificate bundle (a real chain is well under this;
133
153
  // the operator may override via opts.maxPathCerts).
134
154
  PATH_MAX_CERTS: 100,
155
+ // Valid-policy-tree node ceiling: bounds the RFC 5280 6.1.3(d) tree a
156
+ // policy-rich hostile chain can grow (the CVE-2023-0464 class). A real
157
+ // chain's tree holds a handful of nodes; the operator may override via
158
+ // opts.maxPolicyNodes.
159
+ PATH_MAX_POLICY_NODES: 4096,
135
160
  // PKCS#12 container ceilings. A PFX carries lists at three altitudes
136
161
  // (ContentInfos per AuthenticatedSafe, SafeBags per SafeContents,
137
162
  // attributes per bag) and can chain fresh DER blobs inside OCTET STRINGs,
@@ -145,7 +170,7 @@ var LIMITS = {
145
170
  // nesting multiplies transient memory. Real producers segment one level
146
171
  // deep; nesting past this cap is amplification, not a store.
147
172
  BER_MAX_STRING_NESTING: 8,
148
- // A single attribute's value SET no deployed attribute carries more than
173
+ // A single attribute's value SET -- no deployed attribute carries more than
149
174
  // a handful of values; a list at this scale is amplification.
150
175
  ATTRIBUTE_MAX_VALUES: 256,
151
176
  };