@blamejs/pki 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +17 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +64 -32
- package/lib/oid.js +114 -58
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +54 -40
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +63 -14
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +306 -111
- package/lib/schema-smime.js +363 -0
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
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
|
|
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
|
|
26
|
-
* output
|
|
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
|
|
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
|
|
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 }
|
|
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
|
|
80
|
-
// universal type (SEQUENCE
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
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"
|
|
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 }`
|
|
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
|
|
141
|
-
* ber: boolean, // default: false
|
|
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 =
|
|
152
|
-
var maxDepth =
|
|
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
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// parent). Driven by the
|
|
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
|
|
203
|
-
// (INTEGER, OID, BOOLEAN,
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
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 &&
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
455
|
-
// encryptedContent [0] (
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
//
|
|
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
|
|
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 (
|
|
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?)
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
868
|
-
|
|
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
|
|
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,
|
|
21
|
-
*
|
|
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)
|
|
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
|
|
68
|
-
seconds: function (n) { return
|
|
69
|
-
minutes: function (n) { return
|
|
70
|
-
hours: function (n) { return
|
|
71
|
-
days: function (n) { return
|
|
72
|
-
weeks: function (n) { return
|
|
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
|
|
94
|
-
kib: function (n) { return
|
|
95
|
-
mib: function (n) { return
|
|
96
|
-
gib: function (n) { return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
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
|
|
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
|
};
|