@blamejs/pki 0.8.2 → 0.8.4

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.
@@ -98,9 +98,9 @@ function _rawSignature(field) {
98
98
  var CERTS = schema.seqOf(certificateBytes(), { assert: "sequence", code: "ocsp/bad-certs", what: "certs", max: constants.LIMITS.OCSP_MAX_CERTS, maxCode: "ocsp/too-many-certs" });
99
99
 
100
100
  /** @internal RFC 6960 sec. 4.4.4: ArchiveCutoff ::= GeneralizedTime, carried as a singleExtension. */
101
- function _readArchiveCutoff(ext) {
101
+ function _readArchiveCutoff(ext, ctx) {
102
102
  var node;
103
- try { node = asn1.decode(ext.value); }
103
+ try { node = pkix.decodeNested(ext.value, ctx, "an archiveCutoff extension value"); }
104
104
  catch (e) { throw NS.E("ocsp/bad-archive-cutoff", "the archiveCutoff extension value did not decode: " + ((e && e.message) || _String(e)), e); }
105
105
  if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.GENERALIZED_TIME)) {
106
106
  throw NS.E("ocsp/bad-archive-cutoff", "the archiveCutoff extension value must be a GeneralizedTime (RFC 6960 sec. 4.4.4)");
@@ -112,10 +112,10 @@ function _readArchiveCutoff(ext) {
112
112
  /** @internal RFC 6960 sec. 4.4.2: CrlID ::= SEQUENCE { crlUrl [0] EXPLICIT IA5String OPTIONAL,
113
113
  * crlNum [1] EXPLICIT INTEGER OPTIONAL, crlTime [2] EXPLICIT GeneralizedTime OPTIONAL }. Every
114
114
  * field is optional and each may appear at most once, in tag order. */
115
- function _readCrlId(ext) {
115
+ function _readCrlId(ext, ctx) {
116
116
  var C = "ocsp/bad-crl-id";
117
117
  var node;
118
- try { node = asn1.decode(ext.value); }
118
+ try { node = pkix.decodeNested(ext.value, ctx, "a crlID extension value"); }
119
119
  catch (e) { throw NS.E(C, "the CRL references extension value did not decode: " + ((e && e.message) || _String(e)), e); }
120
120
  if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.SEQUENCE && node.children)) {
121
121
  throw NS.E(C, "CrlID must be a SEQUENCE (RFC 6960 sec. 4.4.2)");
@@ -154,15 +154,15 @@ function _readCrlId(ext) {
154
154
  /** @internal The extensions this profile reads are decoded onto their entry beside the raw value,
155
155
  * so a consumer gets the value by name and a malformed one is refused here rather than passed
156
156
  * through as opaque bytes. The nonce is RFC 9654; the other two are RFC 6960 sec. 4.4. */
157
- function _validateOcspExtensions(list) {
157
+ function _validateOcspExtensions(list, ctx) {
158
158
  if (!list) return list;
159
159
  for (var i = 0; i < list.length; i++) {
160
160
  var ext = list[i];
161
- if (ext.oid === OID_OCSP_ARCHIVE_CUTOFF) { ext.archiveCutoff = _readArchiveCutoff(ext); continue; }
162
- if (ext.oid === OID_OCSP_CRL) { ext.crlReferences = _readCrlId(ext); continue; }
161
+ if (ext.oid === OID_OCSP_ARCHIVE_CUTOFF) { ext.archiveCutoff = _readArchiveCutoff(ext, ctx); continue; }
162
+ if (ext.oid === OID_OCSP_CRL) { ext.crlReferences = _readCrlId(ext, ctx); continue; }
163
163
  if (ext.oid !== OID_OCSP_NONCE) continue;
164
164
  var node;
165
- try { node = asn1.decode(ext.value); }
165
+ try { node = pkix.decodeNested(ext.value, ctx, "a nonce extension value"); }
166
166
  catch (e) { throw NS.E("ocsp/bad-nonce", "the nonce extension value did not decode: " + ((e && e.message) || String(e)), e); }
167
167
  if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.OCTET_STRING && node.content)) {
168
168
  throw NS.E("ocsp/bad-nonce", "the nonce extension value must be an OCTET STRING (RFC 9654 sec. 2.1)");
@@ -244,13 +244,13 @@ var SINGLE_RESPONSE = schema.seq([
244
244
  ], { minTag: 0, maxTag: 1, unexpectedCode: "ocsp/bad-single-response", orderCode: "ocsp/bad-single-response" }),
245
245
  ], {
246
246
  assert: "sequence", code: "ocsp/bad-single-response", what: "SingleResponse",
247
- build: function (m) {
247
+ build: function (m, ctx) {
248
248
  return {
249
249
  certID: m.fields.certID.value.result,
250
250
  certStatus: _shapeCertStatus(m.fields.certStatus),
251
251
  thisUpdate: m.fields.thisUpdate.value,
252
252
  nextUpdate: m.fields.nextUpdate.present ? m.fields.nextUpdate.value : null,
253
- singleExtensions: m.fields.singleExtensions.present ? _validateOcspExtensions(m.fields.singleExtensions.value.result) : null,
253
+ singleExtensions: m.fields.singleExtensions.present ? _validateOcspExtensions(m.fields.singleExtensions.value.result, ctx) : null,
254
254
  };
255
255
  },
256
256
  });
@@ -277,13 +277,13 @@ var RESPONSE_DATA = schema.seq([
277
277
  ], { minTag: 1, maxTag: 1, unexpectedCode: "ocsp/bad-response-data", orderCode: "ocsp/bad-response-data" }),
278
278
  ], {
279
279
  assert: "sequence", code: "ocsp/bad-response-data", what: "ResponseData",
280
- build: function (m) {
280
+ build: function (m, ctx) {
281
281
  return {
282
282
  version: m.fields.version.value,
283
283
  responderID: _shapeResponderID(m.fields.responderID),
284
284
  producedAt: m.fields.producedAt.value,
285
285
  responses: m.fields.responses.value.items.map(function (it) { return it.value.result; }),
286
- responseExtensions: m.fields.responseExtensions.present ? _validateOcspExtensions(m.fields.responseExtensions.value.result) : null,
286
+ responseExtensions: m.fields.responseExtensions.present ? _validateOcspExtensions(m.fields.responseExtensions.value.result, ctx) : null,
287
287
  };
288
288
  },
289
289
  });
@@ -362,10 +362,10 @@ var REQUEST = schema.seq([
362
362
  ], { minTag: 0, maxTag: 0, unexpectedCode: "ocsp/bad-request", orderCode: "ocsp/bad-request" }),
363
363
  ], {
364
364
  assert: "sequence", code: "ocsp/bad-request", what: "Request",
365
- build: function (m) {
365
+ build: function (m, ctx) {
366
366
  return {
367
367
  certID: m.fields.reqCert.value.result,
368
- singleRequestExtensions: m.fields.singleRequestExtensions.present ? _validateOcspExtensions(m.fields.singleRequestExtensions.value.result) : null,
368
+ singleRequestExtensions: m.fields.singleRequestExtensions.present ? _validateOcspExtensions(m.fields.singleRequestExtensions.value.result, ctx) : null,
369
369
  };
370
370
  },
371
371
  });
@@ -396,13 +396,13 @@ var TBS_REQUEST = schema.seq([
396
396
  ], { minTag: 2, maxTag: 2, unexpectedCode: "ocsp/bad-tbs-request", orderCode: "ocsp/bad-tbs-request" }),
397
397
  ], {
398
398
  assert: "sequence", code: "ocsp/bad-tbs-request", what: "TBSRequest",
399
- build: function (m) {
399
+ build: function (m, ctx) {
400
400
  var rn = m.fields.requestorName;
401
401
  return {
402
402
  version: m.fields.version.value,
403
403
  requestorName: rn.present ? { bytes: rn.value.bytes, tagClass: rn.value.tagClass, tagNumber: rn.value.tagNumber, value: rn.value.value } : null,
404
404
  requestList: m.fields.requestList.value.items.map(function (it) { return it.value.result; }),
405
- requestExtensions: m.fields.requestExtensions.present ? _validateOcspExtensions(m.fields.requestExtensions.value.result) : null,
405
+ requestExtensions: m.fields.requestExtensions.present ? _validateOcspExtensions(m.fields.requestExtensions.value.result, ctx) : null,
406
406
  };
407
407
  },
408
408
  });
@@ -431,7 +431,7 @@ var OCSP_REQUEST = schema.seq([
431
431
 
432
432
  /**
433
433
  * @primitive pki.schema.ocsp.parseRequest
434
- * @signature pki.schema.ocsp.parseRequest(input) -> ocspRequest
434
+ * @signature pki.schema.ocsp.parseRequest(input, caps?) -> ocspRequest
435
435
  * @since 0.1.11
436
436
  * @status stable
437
437
  * @spec RFC 6960
@@ -458,12 +458,17 @@ var OCSP_REQUEST = schema.seq([
458
458
  * var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, {});
459
459
  * var req = pki.schema.ocsp.parseRequest(der);
460
460
  * req.requestList[0].certID.serialNumberHex; // -> "1332"
461
+ * @opts
462
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
463
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
464
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
465
+ * refused with the `/too-large` code of this format.
461
466
  */
462
467
  var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemError, ErrorClass: OcspError, prefix: "ocsp", what: "OCSP request", topSchema: OCSP_REQUEST, ns: NS });
463
468
 
464
469
  /**
465
470
  * @primitive pki.schema.ocsp.parseResponse
466
- * @signature pki.schema.ocsp.parseResponse(input) -> ocspResponse
471
+ * @signature pki.schema.ocsp.parseResponse(input, caps?) -> ocspResponse
467
472
  * @since 0.1.11
468
473
  * @status stable
469
474
  * @spec RFC 6960
@@ -495,6 +500,11 @@ var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemErro
495
500
  * var res = pki.schema.ocsp.parseResponse(der);
496
501
  * res.responseStatus.name; // -> "successful"
497
502
  * res.basicResponse.responses[0].certStatus.type; // -> "good" | "revoked" | "unknown"
503
+ * @opts
504
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
505
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
506
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
507
+ * refused with the `/too-large` code of this format.
498
508
  */
499
509
  var parseResponse = pkix.makeRecordingParser({ pemLabel: "OCSP RESPONSE", PemError: PemError, ErrorClass: OcspError, prefix: "ocsp", what: "OCSP response", topSchema: OCSP_RESPONSE, ns: NS }, "ocspResponse");
500
510
 
@@ -582,6 +592,7 @@ module.exports = {
582
592
  * reads. Throws the parser's own ocsp/* code for a malformed entry. */
583
593
  readSingleExtensionsDer: function (derList) {
584
594
  var node;
595
+ // allow:bare-decode reached from the signer, not from a parse, so no parse context carries caps here.
585
596
  try { node = asn1.decode(asn1.build.sequence(intrinsic.map(derList, function (d) { return asn1.build.raw(d); }))); }
586
597
  catch (e) { throw NS.E("ocsp/bad-single-response", "a pre-encoded singleExtension is not DER", e); }
587
598
  return _validateOcspExtensions(schema.walk(EXTENSIONS, node, NS).result);
@@ -188,7 +188,8 @@ var SAFE_BAG = schema.seq([
188
188
  }
189
189
  if (a.type === OID_LOCAL_KEY_ID) {
190
190
  if (seen[a.type] || a.values.length !== 1) throw ctx.E("pkcs12/bad-local-key-id", "localKeyId carries exactly one OCTET STRING value (PKCS#9 SINGLE VALUE)");
191
- localKeyId = _octetContent(asn1.decode(a.values[0], { ber: true }), ctx, "a localKeyId value", "pkcs12/bad-local-key-id");
191
+ localKeyId = _octetContent(pkix.decodeNested(a.values[0], ctx, "a localKeyId value", { ber: true }), ctx,
192
+ "a localKeyId value", "pkcs12/bad-local-key-id");
192
193
  }
193
194
  seen[a.type] = true;
194
195
  }
@@ -255,7 +256,7 @@ var PFX = schema.seq([
255
256
  }
256
257
  if (authSafe.contentType === OID_SIGNED_DATA) {
257
258
  if (macPresent) throw ctx.E("pkcs12/bad-integrity-mode", "an id-signedData authSafe (public-key integrity) must omit macData (RFC 7292 sec. 4)");
258
- var signed = cms.walkSignedData(authSafe.innerNode);
259
+ var signed = cms.walkSignedData(authSafe.innerNode, ctx);
259
260
  if (signed.encapContentInfo.eContentType !== OID_DATA) {
260
261
  throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must encapsulate id-data carrying the AuthenticatedSafe (RFC 7292 sec. 4.1)");
261
262
  }
@@ -296,7 +297,7 @@ function _privacySafe(content, ctx) {
296
297
  }
297
298
 
298
299
  function _bmpValue(bytes, ctx) {
299
- var node = asn1.decode(bytes, { ber: true });
300
+ var node = pkix.decodeNested(bytes, ctx, "a friendlyName value", { ber: true });
300
301
  if (!(node.tagClass === "universal" && node.tagNumber === TAGS.BMP_STRING)) {
301
302
  throw ctx.E("pkcs12/bad-friendly-name", "a friendlyName value must be a BMPString (RFC 7292 sec. 4.2)");
302
303
  }
@@ -310,12 +311,15 @@ function _walkAuthenticatedSafe(bytes, state, ctx) {
310
311
  });
311
312
  }
312
313
 
313
- // @internal
314
- function walkSafeContents(der) {
315
- var walked = schema.walk(SAFE_CONTENTS, asn1.decode(der, { ber: true }), NS);
314
+ /** @internal Reads a decrypted SafeContents. `ctx` is the parse context of the door that produced
315
+ * the bytes when there is one, so its caps and budget reach the bags; the decrypt path in
316
+ * pki.pkcs12.open has none and takes the built-in ceilings. */
317
+ function walkSafeContents(der, ctx) {
318
+ var ns = schema.withResources(NS, ctx);
319
+ var walked = schema.walk(SAFE_CONTENTS, pkix.decodeNested(der, ns, "a SafeContents", { ber: true }), ns);
316
320
  var state = { budget: { remaining: C.LIMITS.PKCS12_MAX_REDECODES }, bagDepth: 0 };
317
321
  var bags = [];
318
- for (var i = 0; i < walked.items.length; i++) bags.push(_buildBag(walked.items[i].value.result, state, NS));
322
+ for (var i = 0; i < walked.items.length; i++) bags.push(_buildBag(walked.items[i].value.result, state, ns));
319
323
  return bags;
320
324
  }
321
325
 
@@ -333,9 +337,9 @@ function _dispatchSafes(asMatch, state, ctx) {
333
337
  safeBags.push(_buildBag(contents.items[j].value.result, state, ctx));
334
338
  }
335
339
  } else if (el.contentType === OID_ENCRYPTED_DATA) {
336
- encryptedSafes.push({ type: "encryptedData", content: _privacySafe(cms.walkEncryptedData(el.innerNode), ctx) });
340
+ encryptedSafes.push({ type: "encryptedData", content: _privacySafe(cms.walkEncryptedData(el.innerNode, ctx), ctx) });
337
341
  } else if (el.contentType === OID_ENVELOPED_DATA) {
338
- encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode), ctx), envelopedDataDer: el.innerNode.bytes });
342
+ encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode, ctx), ctx), envelopedDataDer: el.innerNode.bytes });
339
343
  } else {
340
344
  throw ctx.E("pkcs12/bad-safe-contentinfo-type", "an AuthenticatedSafe element must be id-data, id-encryptedData, or id-envelopedData (RFC 7292 sec. 4.1), got " +
341
345
  (ctx.oid.name(el.contentType) || el.contentType));
@@ -351,13 +355,13 @@ function _buildBag(rec, state, ctx) {
351
355
  };
352
356
  if (rec.bagId === OID_KEY_BAG) {
353
357
  bag.type = "keyBag";
354
- bag.key = pkcs8.walkPrivateKeyInfo(rec.valueNode);
358
+ bag.key = pkcs8.walkPrivateKeyInfo(rec.valueNode, ctx);
355
359
  bag.keyDer = rec.valueNode.bytes;
356
360
  return bag;
357
361
  }
358
362
  if (rec.bagId === OID_SHROUDED_KEY_BAG) {
359
363
  bag.type = "pkcs8ShroudedKeyBag";
360
- bag.encrypted = pkcs8.walkEncryptedPrivateKeyInfo(rec.valueNode);
364
+ bag.encrypted = pkcs8.walkEncryptedPrivateKeyInfo(rec.valueNode, ctx);
361
365
  return bag;
362
366
  }
363
367
  if (rec.bagId === OID_CERT_BAG) {
@@ -396,7 +400,7 @@ function _buildBag(rec, state, ctx) {
396
400
 
397
401
  /**
398
402
  * @primitive pki.schema.pkcs12.parse
399
- * @signature pki.schema.pkcs12.parse(input) -> pfx
403
+ * @signature pki.schema.pkcs12.parse(input, caps?) -> pfx
400
404
  * @since 0.1.18
401
405
  * @status stable
402
406
  * @spec RFC 7292, RFC 9579
@@ -441,6 +445,11 @@ function _buildBag(rec, state, ctx) {
441
445
  * var der = await pki.pkcs12.build({ key: key, cert: cert }, { password: "changeit" });
442
446
  * var store = pki.schema.pkcs12.parse(der);
443
447
  * store.safeBags.map(function (b) { return b.type; });
448
+ * @opts
449
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
450
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
451
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
452
+ * refused with the `/too-large` code of this format.
444
453
  */
445
454
  var parse = pkix.makeRecordingParser({
446
455
  pemLabel: "PKCS12", PemError: PemError, ErrorClass: Pkcs12Error,
@@ -88,7 +88,7 @@ var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
88
88
 
89
89
  /**
90
90
  * @primitive pki.schema.pkcs8.parse
91
- * @signature pki.schema.pkcs8.parse(input) -> privateKey
91
+ * @signature pki.schema.pkcs8.parse(input, caps?) -> privateKey
92
92
  * @since 0.1.9
93
93
  * @status stable
94
94
  * @spec RFC 5208, RFC 5958
@@ -107,12 +107,17 @@ var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
107
107
  * var key = pki.schema.pkcs8.parse(der);
108
108
  * key.privateKeyAlgorithm.oid; // -> "1.3.101.112" (Ed25519)
109
109
  * key.privateKey; // -> Buffer (the inner key encoding)
110
+ * @opts
111
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
112
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
113
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
114
+ * refused with the `/too-large` code of this format.
110
115
  */
111
116
  var parse = pkix.makeParser({ pemLabel: "PRIVATE KEY", PemError: PemError, ErrorClass: Pkcs8Error, prefix: "pkcs8", what: "private key", topSchema: PRIVATE_KEY_INFO, ns: NS });
112
117
 
113
118
  /**
114
119
  * @primitive pki.schema.pkcs8.parseEncrypted
115
- * @signature pki.schema.pkcs8.parseEncrypted(input) -> encrypted
120
+ * @signature pki.schema.pkcs8.parseEncrypted(input, caps?) -> encrypted
116
121
  * @since 0.1.9
117
122
  * @status stable
118
123
  * @spec RFC 5958, RFC 5208
@@ -128,6 +133,11 @@ var parse = pkix.makeParser({ pemLabel: "PRIVATE KEY", PemError: PemError, Error
128
133
  * var der = await pki.key.encrypt(await pki.key.export(pair.privateKey), "s3cr3t");
129
134
  * var enc = pki.schema.pkcs8.parseEncrypted(der);
130
135
  * enc.encryptionAlgorithm.oid; // -> "1.2.840.113549.1.5.13" (PBES2)
136
+ * @opts
137
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
138
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
139
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
140
+ * refused with the `/too-large` code of this format.
131
141
  */
132
142
  var parseEncrypted = pkix.makeParser({ pemLabel: "ENCRYPTED PRIVATE KEY", PemError: PemError, ErrorClass: Pkcs8Error, prefix: "pkcs8", what: "encrypted private key", topSchema: ENCRYPTED_PRIVATE_KEY_INFO, ns: NS });
133
143
 
@@ -183,8 +193,15 @@ function matches(root) {
183
193
  }
184
194
 
185
195
 
186
- function walkPrivateKeyInfo(node) { return schema.walk(PRIVATE_KEY_INFO, node, NS).result; }
187
- function walkEncryptedPrivateKeyInfo(node) { return schema.walk(ENCRYPTED_PRIVATE_KEY_INFO, node, NS).result; }
196
+ /** @internal `ctx` is the walk context of the parse that reached this key, when another format
197
+ * carries it. Only its caps and budget cross over: the key is still read in this namespace, so a
198
+ * fault in it keeps the PKCS#8 error class and code. */
199
+ function walkPrivateKeyInfo(node, ctx) {
200
+ return schema.walk(PRIVATE_KEY_INFO, node, schema.withResources(NS, ctx)).result;
201
+ }
202
+ function walkEncryptedPrivateKeyInfo(node, ctx) {
203
+ return schema.walk(ENCRYPTED_PRIVATE_KEY_INFO, node, schema.withResources(NS, ctx)).result;
204
+ }
188
205
 
189
206
  module.exports = {
190
207
  parse: parse,
@@ -219,15 +219,174 @@ function _isPemArmor(buf) {
219
219
  return true;
220
220
  }
221
221
 
222
- function decodeRoot(der, opts) {
223
- try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
222
+ /** @internal The codec's resource refusals, as distinct from its malformed-input refusals. A
223
+ * caller who tightened a cap has to be able to tell "your bytes are wrong" from "your cap is
224
+ * too small", and only the second is answered by raising the cap. */
225
+ var _isResourceRefusal = schema.isResourceRefusal;
226
+
227
+ function decodeRoot(der, opts, caps, budget) {
228
+ // allow:bare-decode the root decode a door performs, which is where the caller's caps enter.
229
+ try { return asn1.decode(der, _decodeOptions(opts, caps)); }
224
230
  catch (e) {
231
+ /** @internal `rawDecodeErrors` keeps the codec's own code for a malformed body, which some
232
+ * callers pin. A resource refusal is still translated, that code being new. */
233
+ if (opts.rawDecodeErrors && !_isResourceRefusal(e)) throw e;
234
+ if (_isResourceRefusal(e)) {
235
+ if (budget) {
236
+ try {
237
+ budget.trip(opts.what || "the input");
238
+ } catch (_b) {
239
+ /* allow:swallow-unverified the latch is the point here; the refusal below carries the cause */
240
+ }
241
+ }
242
+ throw new opts.ErrorClass(opts.prefix + "/too-large",
243
+ (opts.what || "the input") + " exceeds the decode budget for this parse: " + (e.message || _String(e)), e);
244
+ }
225
245
  throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || _String(e)), e);
226
246
  }
227
247
  }
228
248
 
229
- function runParse(input, opts) {
230
- return schema.walk(opts.topSchema, decodeRoot(coerceToDer(input, opts), opts), opts.ns).result;
249
+ /** @internal A fresh options object per decode. The strictness a format declares and the caps
250
+ * a caller asked for are combined here and nowhere else, so neither can be written onto the
251
+ * other's object and outlive this call. */
252
+ function _decodeOptions(opts, caps) {
253
+ if (!caps) return opts.ber ? { ber: true } : undefined;
254
+ var out = { maxBytes: caps.maxBytes, maxDepth: caps.maxDepth, maxItems: caps.maxItems };
255
+ if (opts.ber) out.ber = true;
256
+ return out;
257
+ }
258
+
259
+ /** @internal The decode caps a caller may name, each paired with the `C.LIMITS` default that
260
+ * is also its ceiling. A cap may tighten and may not loosen: the defaults are the resource
261
+ * the toolkit is willing to spend, and a caller raising one past them would be granting
262
+ * themselves more than the built-in refusal allows. */
263
+ var PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
264
+ maxBytes: "DER_MAX_BYTES",
265
+ maxDepth: "DER_MAX_DEPTH",
266
+ maxItems: "DER_MAX_ITEMS",
267
+ });
268
+
269
+ /** @internal A format that reads something other than DER declares its own ceilings through
270
+ * `opts.capLimits`, so the bound a caller is held to is the one their parser actually uses. */
271
+ var CBOR_PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
272
+ maxBytes: "CBOR_MAX_BYTES",
273
+ maxDepth: "CBOR_MAX_DEPTH",
274
+ maxItems: "CBOR_MAX_ITEMS",
275
+ });
276
+
277
+ var CBOR_RESOURCE_REFUSALS = intrinsic.assign(intrinsic.create(null), {
278
+ "cbor/too-large": 1, "cbor/too-deep": 1, "cbor/too-many-items": 1,
279
+ });
280
+
281
+ function isCborResourceRefusal(e) {
282
+ return !!(e && typeof e.code === "string" && CBOR_RESOURCE_REFUSALS[e.code] === 1);
283
+ }
284
+
285
+ function cborParseOpts(ErrorClass, prefix, what) {
286
+ var out = intrinsic.create(null);
287
+ out.ErrorClass = ErrorClass;
288
+ out.prefix = prefix;
289
+ out.what = what;
290
+ out.capLimits = CBOR_PARSE_CAPS;
291
+ return out;
292
+ }
293
+
294
+ /** @internal A parser door's options, held on a prototype-less object so a computed lookup on
295
+ * it answers from what the format declared. Every hand-rolled door builds its options here
296
+ * rather than as a literal in its own module. */
297
+ function parseOpts(spec) {
298
+ var out = intrinsic.create(null);
299
+ _forEach(intrinsic.keys(spec), function (k) { out[k] = spec[k]; });
300
+ return out;
301
+ }
302
+
303
+ /** @internal Reads the caller's caps into a FRESH object. Nothing is written back onto what
304
+ * the caller passed, and nothing else is ever written onto the returned one, so a resource
305
+ * option cannot become a strictness flag on a shared object. */
306
+ function parseCaps(callerOpts, opts) {
307
+ var E = function (message) { return new opts.ErrorClass(opts.prefix + "/bad-input", message); };
308
+ if (callerOpts === undefined || callerOpts === null) callerOpts = _emptyOptions;
309
+ if (typeof callerOpts !== "object" || intrinsic.isArray(callerOpts) || guard.bytes.isByteSource(callerOpts)) {
310
+ throw E("parse options must be an object of decode caps");
311
+ }
312
+ var capLimits = opts.capLimits || PARSE_CAPS;
313
+ guard.identifier.assertKnownKeys(callerOpts, capLimits,
314
+ function (code, message) { return new opts.ErrorClass(code, message); },
315
+ opts.prefix + "/bad-input", "parse does not take the option ");
316
+ var caps = intrinsic.create(null);
317
+ _forEach(intrinsic.keys(capLimits), function (key) {
318
+ var ceiling = constants.LIMITS[capLimits[key]];
319
+ var asked = callerOpts[key];
320
+ if (asked === undefined) { caps[key] = ceiling; return; }
321
+ if (!intrinsic.isInteger(asked) || asked < 0) {
322
+ throw E(key + " must be a non-negative integer no greater than " + ceiling);
323
+ }
324
+ if (asked > ceiling) {
325
+ throw E(key + " " + asked + " is above the built-in ceiling " + ceiling + "; a cap may only tighten");
326
+ }
327
+ caps[key] = asked;
328
+ });
329
+ return caps;
330
+ }
331
+ var _emptyOptions = intrinsic.freeze(intrinsic.create(null));
332
+
333
+ /** @internal The door every format parser opens, whether it composes the shared factory or
334
+ * hand-rolls its walk. `build(rootNode, ns)` does the format's own work; everything around it
335
+ * is the part that must not be re-implemented per parser, because each re-implementation is
336
+ * another place the caller's caps can go missing or the latch can go unread. */
337
+ function runParseWith(input, opts, callerOpts, build) {
338
+ var caps = parseCaps(callerOpts, opts);
339
+ var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
340
+ var ns = _nsWithBudget(opts.ns, budget, caps);
341
+ var result;
342
+ try {
343
+ result = build(decodeRoot(coerceToDer(input, opts), opts, caps, budget), ns, caps, budget);
344
+ } catch (e) {
345
+ if (guard.limits.isBudgetExceeded(e)) throw _budgetRefusal(opts, e);
346
+ /** @internal A refusal that is already this door's own resource verdict has been translated
347
+ * once and carries the codec's refusal as its cause; wrapping it again would put a copy of
348
+ * the verdict where a caller looks for the cap that was exceeded. */
349
+ if (budget.exhausted() && !(e && e.code === opts.prefix + "/too-large")) {
350
+ throw _budgetRefusal(opts, e);
351
+ }
352
+ throw e;
353
+ }
354
+ if (budget.exhausted()) throw _budgetRefusal(opts, null);
355
+ return result;
356
+ }
357
+
358
+ function runParse(input, opts, callerOpts) {
359
+ return runParseWith(input, opts, callerOpts, function (root, ns) {
360
+ return schema.walk(opts.topSchema, root, ns).result;
361
+ });
362
+ }
363
+
364
+ /** @internal The re-read at the door is what makes the latch worth having: a catch anywhere
365
+ * under the walk may absorb the exception or rewrap it as a domain error, and this still
366
+ * refuses. The rewrapped error is carried as the cause. */
367
+ function _budgetRefusal(opts, cause) {
368
+ return new opts.ErrorClass(opts.prefix + "/too-large",
369
+ (opts.what || "the input") + " exceeds the decode budget for this parse", cause || undefined);
370
+ }
371
+
372
+ function _nsWithBudget(ns, budget, caps) {
373
+ return schema.withResources(ns, { budget: budget, caps: caps });
374
+ }
375
+
376
+ /** @internal The walk context a hand-rolled door builds for the structures it reads out of its
377
+ * own input, so the caller's caps reach them the way `runParseWith` makes them reach a factory
378
+ * parser's. The door re-reads `budget.exhausted()` before it returns a result. */
379
+ function nestedContext(ns, caps) {
380
+ var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
381
+ return { ns: _nsWithBudget(ns, budget, caps), budget: budget };
382
+ }
383
+
384
+ /** @internal The decode a format performs on a value it has already read out of its input: an
385
+ * embedded OCTET STRING payload, an encapsulated protocol body. The engine owns it, because
386
+ * `schema.embeddedDer` performs the same decode from inside a walk and the two must apply the
387
+ * caller's caps and trip the parse budget the same way. This is the name format modules call. */
388
+ function decodeNested(bytes, ctx, label, opts) {
389
+ return schema.decodeNested(bytes, ctx, label, opts);
231
390
  }
232
391
 
233
392
  var DN_SHORT = constants.NAMES.DN_SHORT;
@@ -349,6 +508,7 @@ function attrValueToString(ns) {
349
508
  throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must be a non-empty even run of hex digits (RFC 4514 sec. 2.4)");
350
509
  }
351
510
  var raw = guard.encoding.hex(hex, null, ns.E, ns.prefix + "/bad-atv", "a #hex attribute value");
511
+ // allow:bare-decode the encoder side, checking bytes a caller wrote by hand, outside any parse.
352
512
  try { asn1.decode(raw); }
353
513
  catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
354
514
  return raw;
@@ -686,7 +846,7 @@ function decodeIssuingDistributionPointValue(ns, bytes, code) {
686
846
  indirect: false, onlyAttr: false, fields: null };
687
847
  var m;
688
848
  try {
689
- m = schema.walk(issuingDistributionPoint(code), asn1.decode(bytes), ns);
849
+ m = schema.walk(issuingDistributionPoint(code), schema.decodeNested(bytes, ns, "an IssuingDistributionPoint value"), ns);
690
850
  if (m.fields.distributionPoint.present) {
691
851
  var wrap = m.fields.distributionPoint.node;
692
852
  if (!wrap.children || wrap.children.length !== 1) {
@@ -788,15 +948,20 @@ function userNoticeTexts(qualifier) {
788
948
  return i === kids.length ? out : [];
789
949
  }
790
950
 
951
+ /** @internal The readers an extension or attribute value is decoded through. `ctx` is the walk
952
+ * context of the parse that reached the value, when one did: the bytes here are an OCTET STRING
953
+ * payload the root decode stopped at, so this decode restarts depth and item counting and needs
954
+ * the caller's caps handed to it. A caller outside a parse passes none and takes the built-in
955
+ * ceilings, which is what `ns` carries. */
791
956
  function _decodeHelpers(ns) {
792
- function decodeTop(buf, code, what) {
957
+ function decodeTop(buf, code, what, ctx) {
793
958
  var n;
794
- try { n = asn1.decode(buf); }
959
+ try { n = schema.decodeNested(buf, ctx || ns, what); }
795
960
  catch (e) { throw ns.E(code, "malformed " + what + " value: " + ((e && e.message) || _String(e)), e); }
796
961
  return n;
797
962
  }
798
- function seqChildren(buf, code, what) {
799
- var n = decodeTop(buf, code, what);
963
+ function seqChildren(buf, code, what, ctx) {
964
+ var n = decodeTop(buf, code, what, ctx);
800
965
  if (n.tagClass !== "universal" || n.tagNumber !== _T.SEQUENCE || !n.children) {
801
966
  throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
802
967
  }
@@ -1356,7 +1521,7 @@ function rootSequenceChildren(root, minLen, maxLen) {
1356
1521
  }
1357
1522
 
1358
1523
  function makeParser(opts) {
1359
- return function (input) { return runParse(input, opts); };
1524
+ return function (input, callerOpts) { return runParse(input, opts, callerOpts); };
1360
1525
  }
1361
1526
 
1362
1527
  function makeRecordingParser(opts, kind) {
@@ -1430,6 +1595,14 @@ module.exports = {
1430
1595
  isPemArmor: _isPemArmor,
1431
1596
  decodeRoot: decodeRoot,
1432
1597
  runParse: runParse,
1598
+ runParseWith: runParseWith,
1599
+ parseCaps: parseCaps,
1600
+ CBOR_PARSE_CAPS: CBOR_PARSE_CAPS,
1601
+ isCborResourceRefusal: isCborResourceRefusal,
1602
+ cborParseOpts: cborParseOpts,
1603
+ parseOpts: parseOpts,
1604
+ decodeNested: decodeNested,
1605
+ nestedContext: nestedContext,
1433
1606
  makeNS: makeNS,
1434
1607
  versionReader: versionReader,
1435
1608
  DN_SHORT: DN_SHORT,
@@ -187,7 +187,7 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
187
187
 
188
188
  /**
189
189
  * @primitive pki.schema.smime.parseSigningCertificate
190
- * @signature pki.schema.smime.parseSigningCertificate(der) -> { certs, policies }
190
+ * @signature pki.schema.smime.parseSigningCertificate(der, caps?) -> { certs, policies }
191
191
  * @since 0.1.22
192
192
  * @status stable
193
193
  * @spec RFC 5035, RFC 2634
@@ -207,6 +207,11 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
207
207
  * var av = b.sequence([b.sequence([essCertId])]); // SigningCertificate { certs }
208
208
  * var sc = pki.schema.smime.parseSigningCertificate(av);
209
209
  * sc.certs[0].hashAlgorithm.name; // "sha1" (implied)
210
+ * @opts
211
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
212
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
213
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
214
+ * refused with the `/too-large` code of this format.
210
215
  */
211
216
  var parseSigningCertificate = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SigningCertificate", topSchema: SIGNING_CERTIFICATE, ns: NS });
212
217
 
@@ -236,7 +241,7 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
236
241
 
237
242
  /**
238
243
  * @primitive pki.schema.smime.parseSmimeCapabilities
239
- * @signature pki.schema.smime.parseSmimeCapabilities(der) -> { capabilities }
244
+ * @signature pki.schema.smime.parseSmimeCapabilities(der, caps?) -> { capabilities }
240
245
  * @since 0.1.22
241
246
  * @status stable
242
247
  * @spec RFC 8551
@@ -253,6 +258,11 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
253
258
  * var cap = b.sequence([b.oid(pki.oid.byName("aes256-CBC"))]);
254
259
  * var caps = pki.schema.smime.parseSmimeCapabilities(b.sequence([cap]));
255
260
  * caps.capabilities[0].name; // "aes256-CBC"
261
+ * @opts
262
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
263
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
264
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
265
+ * refused with the `/too-large` code of this format.
256
266
  */
257
267
  var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SMIMECapabilities", topSchema: SMIME_CAPABILITIES, ns: NS });
258
268