@blamejs/pki 0.8.3 → 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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to `@blamejs/pki` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.8.4 — 2026-09-25
8
+
9
+ A decode cap bounds every structure a parse reads, and a refusal caused by a cap is no longer reported as malformed input.
10
+
11
+ ### Added
12
+
13
+ - `pki.schema.engine.decodeNested(bytes, ctx, label, opts?)`, the decode to perform on a value already read out of a parse's input. It applies the caps carried on the walk context and records a resource refusal against the parse before re-throwing, which is what a format parser composed on the engine needs so its embedded payloads answer to the caller's caps.
14
+ - `pki.schema.engine.withResources(ns, source)` carries a parse's caps and budget onto another namespace, for a format whose structure is carried inside another one. The structure is still walked in its own namespace, so a fault in it keeps that format's error class and code. `pki.schema.engine.isResourceRefusal(err)` reports whether a codec error was a limit rather than a claim about the bytes.
15
+
16
+ ### Changed
17
+
18
+ - An input refused for exceeding a decode limit reports `<format>/too-large` in cases where it previously reported the format's own malformed-value code, such as `cms/bad-aead-params` or `cmp/bad-body`. A parser that decodes a value out of its input wraps a decode failure in a code of its own, and that code sent a reader to inspect bytes that were fine and a cap that was too small. A caller matching on one of those codes to detect malformed input will stop seeing over-budget inputs under it. The wrapped error is carried on the refusal as its `cause`.
19
+
20
+ ### Fixed
21
+
22
+ - A decode cap bounds every structure a parse reads, not the input alone. An embedded `OCTET STRING` payload, an encapsulated protocol body, a `SafeContents` inside a PKCS#12 store and an attribute value are all decoded under the caps the caller named. The 0.8.3 notes recorded that a structure decoded from inside the input kept the built-in `pki.C.LIMITS` defaults; it no longer does.
23
+ - `pki.schema.cmc.parse` applies the caller's caps to the `PKIData` or `PKIResponse` body. It read the caps, bounded the CMS carrier with them, and then decoded the body that carries the request or response with the built-in defaults.
24
+ - A parse cannot return a result after a decode limit has refused something inside that parse, whatever the code between the two does with the failure. A parser that catches a resource refusal and re-throws its own typed error is the common shape, and that shape previously carried the parse past the latch the budget sets. The door now reads the latch on the failure path as well as on the success path.
25
+
7
26
  ## v0.8.3 — 2026-09-25
8
27
 
9
28
  A parser takes decode caps from its caller, and a resource refusal says so instead of reading as malformed input.
@@ -158,8 +158,8 @@ function recordingParser(kind, parse, ErrorClass, code, label) {
158
158
 
159
159
  // @enforced-by guard-shape-reinlined -- shares recordingParser's shape below: the PROVENANCE.set
160
160
  function recordingWalker(kind, walkNode, decodeBytes) {
161
- return function (node) {
162
- var out = walkNode(node);
161
+ return function (node, ctx) {
162
+ var out = walkNode(node, ctx);
163
163
  if (out && typeof out === "object" && node && _isBytes(node.bytes)) {
164
164
  _weakSet(PROVENANCE, out, {
165
165
  kind: kind,
@@ -147,7 +147,7 @@ var V2_FORM = schema.seq([
147
147
  if (issuerName.names[0].tagNumber !== 4) {
148
148
  throw ctx.E("attrcert/bad-issuer-name", "the issuerName GeneralName must be a directoryName [4] (RFC 5755 sec. 4.2.3)");
149
149
  }
150
- var dirName = asn1.decode(issuerName.names[0].bytes).children[0];
150
+ var dirName = pkix.decodeNested(issuerName.names[0].bytes, ctx, "the issuerName directoryName").children[0];
151
151
  if (!dirName.children || dirName.children.length < 1) {
152
152
  throw ctx.E("attrcert/bad-issuer-name", "the issuerName directoryName must be a non-empty distinguished name (RFC 5755 sec. 4.2.3)");
153
153
  }
@@ -217,14 +217,14 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
217
217
  var attributes = acinfo.fields.attributes.value.result;
218
218
  attributes.forEach(function (a) {
219
219
  var dec = _ATTR_VALUE_DECODERS[a.type];
220
- a.decoded = a.values.map(function (v) { return dec ? dec(v) : { opaque: true, bytes: v }; });
220
+ a.decoded = a.values.map(function (v) { return dec ? dec(v, ctx) : { opaque: true, bytes: v }; });
221
221
  });
222
222
 
223
223
  var extField = acinfo.fields.extensions;
224
224
  var extensions = (extField && extField.present) ? extField.value.result : [];
225
225
  extensions.forEach(function (x) {
226
226
  var dec = _AC_EXT_DECODERS[x.oid];
227
- x.decoded = dec ? dec(x.value) : { opaque: true, bytes: x.value };
227
+ x.decoded = dec ? dec(x.value, ctx) : { opaque: true, bytes: x.value };
228
228
  });
229
229
 
230
230
  return {
@@ -398,12 +398,13 @@ function _oneGN(node, code) {
398
398
 
399
399
  // @internal
400
400
  function readGeneralName(der, code) {
401
+ // allow:bare-decode reached from the builder, not from a parse, so no parse context carries caps here.
401
402
  return _oneGN(asn1.decode(der), code || "attrcert/bad-input");
402
403
  }
403
404
 
404
405
  function _svceAuthInfo(authInfoAllowed, code) {
405
- return function (buf) {
406
- var kids = _seqChildren(buf, code, "SvceAuthInfo");
406
+ return function (buf, ctx) {
407
+ var kids = _seqChildren(buf, code, "SvceAuthInfo", ctx);
407
408
  if (kids.length < 2 || kids.length > 3) throw NS.E(code, "SvceAuthInfo must be a SEQUENCE { service, ident, authInfo? }");
408
409
  var service = _oneGN(kids[0], code);
409
410
  var ident = _oneGN(kids[1], code);
@@ -417,12 +418,12 @@ function _svceAuthInfo(authInfoAllowed, code) {
417
418
  };
418
419
  }
419
420
 
420
- function _ietfAttrSyntax(buf) {
421
+ function _ietfAttrSyntax(buf, ctx) {
421
422
  var C = "attrcert/bad-ietf-attr";
422
- var kids = _seqChildren(buf, C, "IetfAttrSyntax");
423
+ var kids = _seqChildren(buf, C, "IetfAttrSyntax", ctx);
423
424
  if (!kids.length || kids.length > 2) throw NS.E(C, "IetfAttrSyntax must be a SEQUENCE { policyAuthority [0]?, values }");
424
425
  var policyAuthority = null, i = 0;
425
- if (_isCtx(kids[0], 0)) { policyAuthority = schema.walk(_GNS_0, kids[0], NS).result; i = 1; }
426
+ if (_isCtx(kids[0], 0)) { policyAuthority = schema.walk(_GNS_0, kids[0], schema.withResources(NS, ctx)).result; i = 1; }
426
427
  var valuesNode = kids[i];
427
428
  if (!valuesNode || !_isU(valuesNode, TAGS.SEQUENCE) || !valuesNode.children || !valuesNode.children.length) {
428
429
  throw NS.E(C, "IetfAttrSyntax values must be a non-empty SEQUENCE OF value");
@@ -438,12 +439,12 @@ function _ietfAttrSyntax(buf) {
438
439
  return { policyAuthority: policyAuthority, values: values, homogeneous: Object.keys(kinds).length === 1 };
439
440
  }
440
441
 
441
- function _role(buf) {
442
+ function _role(buf, ctx) {
442
443
  var C = "attrcert/bad-role";
443
- var kids = _seqChildren(buf, C, "RoleSyntax");
444
+ var kids = _seqChildren(buf, C, "RoleSyntax", ctx);
444
445
  if (!kids.length || kids.length > 2) throw NS.E(C, "RoleSyntax must be a SEQUENCE { roleAuthority [0]?, roleName [1] }");
445
446
  var roleAuthority = null, i = 0;
446
- if (_isCtx(kids[0], 0)) { roleAuthority = schema.walk(_GNS_0, kids[0], NS).result; i = 1; }
447
+ if (_isCtx(kids[0], 0)) { roleAuthority = schema.walk(_GNS_0, kids[0], schema.withResources(NS, ctx)).result; i = 1; }
447
448
  var rn = kids[i];
448
449
  if (!_isCtx(rn, 1) || !rn.children || rn.children.length !== 1) throw NS.E(C, "RoleSyntax roleName must be an EXPLICIT [1] wrapping exactly one GeneralName");
449
450
  if (i + 1 !== kids.length) throw NS.E(C, "RoleSyntax has unexpected trailing fields");
@@ -473,9 +474,9 @@ function _securityCategory(node, C) {
473
474
  if (!_isCtx(valueNode, 1) || !valueNode.children || valueNode.children.length !== 1) throw NS.E(C, "SecurityCategory value must be an EXPLICIT [1] wrapping one element");
474
475
  return { type: type, valueBytes: Buffer.concat([valueNode.children[0].bytes]) };
475
476
  }
476
- function _clearance(buf) {
477
+ function _clearance(buf, ctx) {
477
478
  var C = "attrcert/bad-clearance";
478
- var kids = _seqChildren(buf, C, "Clearance");
479
+ var kids = _seqChildren(buf, C, "Clearance", ctx);
479
480
  if (!kids.length || kids.length > 3) throw NS.E(C, "Clearance must be a SEQUENCE { policyId, classList?, securityCategories? }");
480
481
  if (!_isU(kids[0], TAGS.OBJECT_IDENTIFIER)) throw NS.E(C, "Clearance policyId must be an OBJECT IDENTIFIER");
481
482
  var policyId; try { policyId = asn1.read.oid(kids[0]); } catch (e) { throw NS.E(C, "Clearance policyId must be an OBJECT IDENTIFIER", e); }
@@ -508,16 +509,16 @@ _ATTR_VALUE_DECODERS[_Oav("chargingIdentity")] = _ietfAttrSyntax;
508
509
  _ATTR_VALUE_DECODERS[_Oav("group")] = _ietfAttrSyntax;
509
510
 
510
511
 
511
- function _acAuditIdentity(buf) {
512
+ function _acAuditIdentity(buf, ctx) {
512
513
  var C = "attrcert/bad-audit-identity";
513
- var n = _decodeTop(buf, C, "AuditIdentity");
514
+ var n = _decodeTop(buf, C, "AuditIdentity", ctx);
514
515
  if (!_isU(n, TAGS.OCTET_STRING)) throw NS.E(C, "AuditIdentity must be an OCTET STRING");
515
516
  return Buffer.concat([asn1.read.octetString(n)]);
516
517
  }
517
518
 
518
- function _noRevAvail(buf) {
519
+ function _noRevAvail(buf, ctx) {
519
520
  var C = "attrcert/bad-no-rev-avail";
520
- var n = _decodeTop(buf, C, "NoRevAvail");
521
+ var n = _decodeTop(buf, C, "NoRevAvail", ctx);
521
522
  try { asn1.read.nullValue(n); } catch (e) { throw NS.E(C, "NoRevAvail must be a DER NULL (RFC 5755 sec. 4.3.6)", e); }
522
523
  return { noRevAvail: true };
523
524
  }
@@ -546,13 +547,13 @@ function _acDecodeTargets(node, C, allowTargetCert) {
546
547
  if (!_isU(node, TAGS.SEQUENCE) || !node.children || !node.children.length) throw NS.E(C, "Targets must be a non-empty SEQUENCE OF Target");
547
548
  return node.children.map(function (t) { return _acTarget(t, C, allowTargetCert); });
548
549
  }
549
- function _seqOfTargets(buf, C, allowTargetCert) {
550
- var kids = _seqChildren(buf, C, "SEQUENCE OF Targets");
550
+ function _seqOfTargets(buf, C, allowTargetCert, ctx) {
551
+ var kids = _seqChildren(buf, C, "SEQUENCE OF Targets", ctx);
551
552
  if (!kids.length) throw NS.E(C, "a SEQUENCE OF Targets must be non-empty");
552
553
  return kids.map(function (targetsNode) { return _acDecodeTargets(targetsNode, C, allowTargetCert); });
553
554
  }
554
- function _targetInformation(buf) { return _seqOfTargets(buf, "attrcert/bad-targets", true); }
555
- function _acProxying(buf) { return _seqOfTargets(buf, "attrcert/bad-proxy-info", false); }
555
+ function _targetInformation(buf, ctx) { return _seqOfTargets(buf, "attrcert/bad-targets", true, ctx); }
556
+ function _acProxying(buf, ctx) { return _seqOfTargets(buf, "attrcert/bad-proxy-info", false, ctx); }
556
557
 
557
558
  function _acAttrSpec(node, C) {
558
559
  if (!node.children) throw NS.E(C, "an AttrSpec must be a SEQUENCE OF OBJECT IDENTIFIER");
@@ -561,9 +562,9 @@ function _acAttrSpec(node, C) {
561
562
  try { return asn1.read.oid(o); } catch (e) { throw NS.E(C, "an AttrSpec element must be an OBJECT IDENTIFIER", e); }
562
563
  });
563
564
  }
564
- function _aaControls(buf) {
565
+ function _aaControls(buf, ctx) {
565
566
  var C = "attrcert/bad-aa-controls";
566
- var kids = _seqChildren(buf, C, "AAControls");
567
+ var kids = _seqChildren(buf, C, "AAControls", ctx);
567
568
  if (kids.length > 4) throw NS.E(C, "AAControls has too many fields");
568
569
  var out = { pathLenConstraint: null, permittedAttrs: null, excludedAttrs: null, permitUnSpecified: true }, i = 0;
569
570
  if (kids[i] && _isU(kids[i], TAGS.INTEGER)) { out.pathLenConstraint = guard.range.uint31(_readInt(kids[i], C, "pathLenConstraint"), NS.E, C, "AAControls pathLenConstraint"); i++; }
@@ -588,9 +589,18 @@ _AC_EXT_DECODERS[_Oav("acProxying")] = _acProxying;
588
589
  * through the certificate decoders, so a relying party running the sec. 6 "pointer in AC" scheme
589
590
  * reads the distribution point and the OCSP location off the parse rather than off raw bytes. */
590
591
  var _CERT_EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
591
- _AC_EXT_DECODERS[_Oav("authorityKeyIdentifier")] = _CERT_EXT_DECODERS[_Oav("authorityKeyIdentifier")];
592
- _AC_EXT_DECODERS[_Oav("authorityInfoAccess")] = _CERT_EXT_DECODERS[_Oav("authorityInfoAccess")];
593
- _AC_EXT_DECODERS[_Oav("cRLDistributionPoints")] = _CERT_EXT_DECODERS[_Oav("cRLDistributionPoints")];
592
+ /** @internal The certificate decoders close over the namespace they were built with, so a parse
593
+ * that named caps gets its own table and every decode below these three reads the caller's
594
+ * figure. A caller outside a parse passes no context and reuses the module's table. */
595
+ function _certExtDecoder(name) {
596
+ var key = _Oav(name);
597
+ return function (buf, ctx) {
598
+ return (ctx ? pkix.certExtensionDecoders(schema.withResources(NS, ctx)).byOid : _CERT_EXT_DECODERS)[key](buf);
599
+ };
600
+ }
601
+ _AC_EXT_DECODERS[_Oav("authorityKeyIdentifier")] = _certExtDecoder("authorityKeyIdentifier");
602
+ _AC_EXT_DECODERS[_Oav("authorityInfoAccess")] = _certExtDecoder("authorityInfoAccess");
603
+ _AC_EXT_DECODERS[_Oav("cRLDistributionPoints")] = _certExtDecoder("cRLDistributionPoints");
594
604
 
595
605
  // @internal
596
606
  function validateAttributeValue(type, valueDer) {
@@ -413,16 +413,16 @@ function _generalNameToDer(intVal, valueNode, ipMode, isNative) {
413
413
  if (intVal === 0 || intVal === -1 || intVal === -2 || intVal === -3) return _otherNameToDer(valueNode, intVal);
414
414
  throw _err("c509/bad-extensions", "GeneralName int " + intVal + " has no C509 sec. 8.13 registry row");
415
415
  }
416
- function _generalNameFromDer(gn, ipMode) {
416
+ function _generalNameFromDer(gn, ipMode, ctx) {
417
417
  var t = gn.tagNumber;
418
418
  if (t === 1 || t === 2 || t === 6) return [cbor.build.int(BigInt(t)), cbor.build.textString(gn.value)];
419
- if (t === 4) { var nm = _dirNameToCbor(gn.bytes); return nm == null ? null : [cbor.build.int(4n), nm]; }
419
+ if (t === 4) { var nm = _dirNameToCbor(gn.bytes, ctx); return nm == null ? null : [cbor.build.int(4n), nm]; }
420
420
  if (t === 7) {
421
421
  if (!ipMode) return [cbor.build.int(7n), cbor.build.byteString(gn.value)];
422
422
  var ip = _ncIpFromDer(gn.value); return ip == null ? null : [cbor.build.int(7n), cbor.build.byteString(ip)];
423
423
  }
424
424
  if (t === 8) return [cbor.build.int(8n), _oidCbor(gn.value)];
425
- if (t === 0) return _otherNameFromDer(gn.value);
425
+ if (t === 0) return _otherNameFromDer(gn.value, ctx);
426
426
  return null;
427
427
  }
428
428
 
@@ -434,10 +434,10 @@ function _generalNamesToDer(node, isNative) {
434
434
  for (var i = 0; i + 1 < kids.length; i += 2) out.push(_generalNameToDer(Number(_cborIntVal(kids[i], "a GeneralName type")), kids[i + 1], false, isNative));
435
435
  return out;
436
436
  }
437
- function _generalNamesFromDer(gnNodes) {
437
+ function _generalNamesFromDer(gnNodes, ctx) {
438
438
  var out = [];
439
439
  for (var i = 0; i < gnNodes.length; i++) {
440
- var pair = _generalNameFromDer(schema.walk(GN_LEAF, gnNodes[i], NS), false);
440
+ var pair = _generalNameFromDer(schema.walk(GN_LEAF, gnNodes[i], schema.withResources(NS, ctx)), false, ctx);
441
441
  if (pair == null) return null;
442
442
  out.push(pair[0]); out.push(pair[1]);
443
443
  }
@@ -470,11 +470,11 @@ function _otherNameToDer(node, intVal) {
470
470
  }
471
471
  return b.contextConstructed(0, Buffer.concat([b.oid(typeId), b.explicit(0, inner)]));
472
472
  }
473
- function _otherNameFromDer(v) {
473
+ function _otherNameFromDer(v, ctx) {
474
474
  var onInt = ON_INT_BY_OID[v.typeId];
475
475
  if (onInt === undefined) return [cbor.build.int(0n), cbor.build.array([_oidCbor(v.typeId), cbor.build.byteString(v.valueBytes)])];
476
476
  var inner;
477
- try { inner = asn1.decode(v.valueBytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed otherName value yields null, so the extension does not compact and is carried in C509 general form (raw bytes)
477
+ try { inner = pkix.decodeNested(v.valueBytes, ctx, "an otherName value"); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed otherName value yields null, so the extension does not compact and is carried in C509 general form (raw bytes)
478
478
  if (onInt === -1) {
479
479
  if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.SEQUENCE || !inner.children || inner.children.length !== 2) return null;
480
480
  var hwType, hwSerial;
@@ -492,11 +492,11 @@ function _otherNameFromDer(v) {
492
492
  return [cbor.build.int(-3n), cbor.build.byteString(mac)];
493
493
  }
494
494
 
495
- function _dirNameToCbor(gnBytes) {
496
- var node = asn1.decode(gnBytes);
495
+ function _dirNameToCbor(gnBytes, ctx) {
496
+ var node = pkix.decodeNested(gnBytes, ctx, "a directoryName value");
497
497
  if (!node.children || node.children.length !== 1) return null;
498
498
  var name;
499
- try { name = _c509NameFromDer(node.children[0].bytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed directoryName yields null, so the extension is carried in C509 general form instead
499
+ try { name = _c509NameFromDer(node.children[0].bytes, ctx); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed directoryName yields null, so the extension is carried in C509 general form instead
500
500
  try { return _encName(name, true); } catch (_e2) { return null; } // allow:swallow-unverified -- a directoryName that does not re-encode yields null, so the extension is carried in C509 general form instead
501
501
  }
502
502
 
@@ -745,13 +745,13 @@ function _subtreesToDer(node, isNative) {
745
745
  for (var i = 0; i + 1 < kids.length; i += 2) out.push(b.sequence([_generalNameToDer(Number(_cborIntVal(kids[i], "a GeneralSubtree base type")), kids[i + 1], true, isNative)]));
746
746
  return Buffer.concat(out);
747
747
  }
748
- function _subtreesFromDer(subtreeNodes) {
748
+ function _subtreesFromDer(subtreeNodes, ctx) {
749
749
  if (!subtreeNodes.length) return null;
750
750
  var out = [];
751
751
  for (var i = 0; i < subtreeNodes.length; i++) {
752
752
  var st = subtreeNodes[i];
753
753
  if (st.tagClass !== "universal" || st.tagNumber !== asn1.TAGS.SEQUENCE || !st.children || st.children.length !== 1) return null;
754
- var pair = _generalNameFromDer(schema.walk(GN_LEAF_SUBTREE, st.children[0], NS), true);
754
+ var pair = _generalNameFromDer(schema.walk(GN_LEAF_SUBTREE, st.children[0], schema.withResources(NS, ctx)), true, ctx);
755
755
  if (pair == null) return null;
756
756
  out.push(pair[0]); out.push(pair[1]);
757
757
  }
@@ -836,7 +836,7 @@ function _dpToDer(dpNode, isNative) {
836
836
  if (!_isCborNull(crlIssuer)) fields.push(b.contextConstructed(2, b.explicit(4, _reconName(_name509(crlIssuer, true, isNative)))));
837
837
  return b.sequence(fields);
838
838
  }
839
- function _dpFromDer(dp) {
839
+ function _dpFromDer(dp, ctx) {
840
840
  if (dp.tagClass !== "universal" || dp.tagNumber !== asn1.TAGS.SEQUENCE || !dp.children) return null;
841
841
  var fullNameUris = null, reasonsVal = null, crlIssuerCbor = null, lastTag = -1;
842
842
  for (var i = 0; i < dp.children.length; i++) {
@@ -862,7 +862,7 @@ function _dpFromDer(dp) {
862
862
  if (!f.children || f.children.length !== 1) return null;
863
863
  var ci = f.children[0];
864
864
  if (ci.tagClass !== "context" || ci.tagNumber !== 4 || !ci.children || ci.children.length !== 1) return null;
865
- try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes), true); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed CRL-issuer name yields null, so the distributionPoint is carried in C509 general form instead
865
+ try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes, ctx), true); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed CRL-issuer name yields null, so the distributionPoint is carried in C509 general form instead
866
866
  } else return null;
867
867
  }
868
868
  if (fullNameUris == null) return null;
@@ -1054,15 +1054,15 @@ function _extValueToDer(name, node, isNative) {
1054
1054
  }
1055
1055
  }
1056
1056
 
1057
- function _extValueFromDer(name, der) {
1057
+ function _extValueFromDer(name, der, ctx) {
1058
1058
  var node;
1059
1059
  try {
1060
- node = asn1.decode(der);
1060
+ node = pkix.decodeNested(der, ctx, "an extension value");
1061
1061
  switch (name) {
1062
1062
  case "subjectKeyIdentifier":
1063
1063
  return cbor.build.byteString(asn1.read.octetString(node));
1064
1064
  case "keyUsage": {
1065
- var bits = _keyUsageBitsFromDer(der);
1065
+ var bits = _keyUsageBitsFromDer(der, ctx);
1066
1066
  return bits == null ? null : cbor.build.uint(BigInt(bits));
1067
1067
  }
1068
1068
  case "basicConstraints": {
@@ -1081,7 +1081,7 @@ function _extValueFromDer(name, der) {
1081
1081
  if (akids.length === 3 && akids[0].tagClass === "context" && akids[0].tagNumber === 0 &&
1082
1082
  akids[1].tagClass === "context" && akids[1].tagNumber === 1 && akids[1].children &&
1083
1083
  akids[2].tagClass === "context" && akids[2].tagNumber === 2) {
1084
- var akiIssuer = _generalNamesFromDer(akids[1].children);
1084
+ var akiIssuer = _generalNamesFromDer(akids[1].children, ctx);
1085
1085
  if (akiIssuer == null) return null;
1086
1086
  var akiSerial = asn1.read.integerImplicit(akids[2], 2);
1087
1087
  if (akiSerial < 0n) return null;
@@ -1112,7 +1112,7 @@ function _extValueFromDer(name, der) {
1112
1112
  if (sanMembers.length === 1 && sanMembers[0].tagClass === "context" && sanMembers[0].tagNumber === 2) {
1113
1113
  return cbor.build.textString(schema.walk(GN_LEAF, sanMembers[0], NS).value);
1114
1114
  }
1115
- var sanItems = _generalNamesFromDer(sanMembers);
1115
+ var sanItems = _generalNamesFromDer(sanMembers, ctx);
1116
1116
  return sanItems == null ? null : cbor.build.array(sanItems);
1117
1117
  }
1118
1118
  case "nameConstraints": {
@@ -1122,7 +1122,7 @@ function _extValueFromDer(name, der) {
1122
1122
  var ncf = node.children[nci];
1123
1123
  if (ncf.tagClass !== "context" || ncf.tagNumber <= ncLast) return null;
1124
1124
  ncLast = ncf.tagNumber;
1125
- var subItems = _subtreesFromDer(ncf.children || []);
1125
+ var subItems = _subtreesFromDer(ncf.children || [], ctx);
1126
1126
  if (subItems == null) return null;
1127
1127
  if (ncf.tagNumber === 0) { permCbor = cbor.build.array(subItems); sawNc = true; }
1128
1128
  else if (ncf.tagNumber === 1) { exclCbor = cbor.build.array(subItems); sawNc = true; }
@@ -1151,7 +1151,7 @@ function _extValueFromDer(name, der) {
1151
1151
  var dps = node.children || [];
1152
1152
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || dps.length < 1) return null;
1153
1153
  var dpResults = [];
1154
- for (var dpi = 0; dpi < dps.length; dpi++) { var dpr = _dpFromDer(dps[dpi]); if (dpr == null) return null; dpResults.push(dpr); }
1154
+ for (var dpi = 0; dpi < dps.length; dpi++) { var dpr = _dpFromDer(dps[dpi], ctx); if (dpr == null) return null; dpResults.push(dpr); }
1155
1155
  if (dpResults.length === 1 && dpResults[0].oneUri != null && dpResults[0].noReasons && dpResults[0].noIssuer) return cbor.build.textString(dpResults[0].oneUri);
1156
1156
  return cbor.build.array(dpResults.map(function (r) { return r.triple; }));
1157
1157
  }
@@ -1377,14 +1377,14 @@ function _sdaFromDer(node) {
1377
1377
  * the codec represents it, and one without a codec always, since its int form carries the same
1378
1378
  * bytes. An extension the codec cannot represent keeps the generic form on every certificate type.
1379
1379
  */
1380
- function _assertSpecificExtension(dotted, valContent, isNative) {
1380
+ function _assertSpecificExtension(dotted, valContent, isNative, ctx) {
1381
1381
  var canon = EXT_NAME_BY_OID[dotted];
1382
1382
  if (!isNative || canon === undefined) return;
1383
- if (EXT_COMPACT[canon] && (!Buffer.isBuffer(valContent) || _tryCompactExtValue(canon, valContent, isNative) == null)) return;
1383
+ if (EXT_COMPACT[canon] && (!Buffer.isBuffer(valContent) || _tryCompactExtValue(canon, valContent, isNative, ctx) == null)) return;
1384
1384
  throw _err("c509/non-specific-encoding", "extension " + canon + " has a C509 registry id that carries this value, and a natively signed certificate uses only the specific CBOR encoding (draft sec. 3.7)");
1385
1385
  }
1386
- function _tryCompactExtValue(name, der, isNative) {
1387
- var compact = _extValueFromDer(name, der);
1386
+ function _tryCompactExtValue(name, der, isNative, ctx) {
1387
+ var compact = _extValueFromDer(name, der, ctx);
1388
1388
  if (compact == null) return null;
1389
1389
  var recon;
1390
1390
  try {
@@ -1395,7 +1395,7 @@ function _tryCompactExtValue(name, der, isNative) {
1395
1395
  return recon.equals(der) ? compact : null;
1396
1396
  }
1397
1397
 
1398
- function _extensions(node, isNative) {
1398
+ function _extensions(node, isNative, ctx) {
1399
1399
  if (node.majorType === 0 || node.majorType === 1) {
1400
1400
  var iv = Number(cbor.read.int(node));
1401
1401
  return [{ name: "keyUsage", oid: oid.byName("keyUsage"), critical: iv < 0, keyUsageBits: Math.abs(iv) }];
@@ -1437,7 +1437,7 @@ function _extensions(node, isNative) {
1437
1437
  if (valNode.majorType !== 2) throw _err("c509/bad-extensions", "a non-critical ~oid extension value must be a byte string");
1438
1438
  valContent = valNode.content;
1439
1439
  }
1440
- _assertSpecificExtension(extOid, valContent, isNative);
1440
+ _assertSpecificExtension(extOid, valContent, isNative, ctx);
1441
1441
  }
1442
1442
  out.push({ name: name, oid: extOid, critical: critical, value: valContent || null });
1443
1443
  }
@@ -1637,21 +1637,49 @@ function parse(input, callerOpts) {
1637
1637
  if (f[8].majorType !== 2) throw _err("c509/bad-spki", "subjectPublicKey must be a CBOR byte string");
1638
1638
  subjectPublicKey = f[8].content;
1639
1639
  }
1640
- var extensions = _extensions(f[9], type === 2);
1640
+ /** @internal A native certificate's extension values are opaque DER inside the CBOR, so reading
1641
+ * one is a second decode that restarts depth and item counting and takes the caps this door
1642
+ * was given. The latch is re-read before the result is returned. */
1643
+ var nested = pkix.nestedContext(NS, caps);
1644
+ var result;
1645
+ try {
1646
+ result = _finishParse(f, root, nested, {
1647
+ type: type, serial: serial, sHex: sHex, sigAlg: sigAlg, issuer: issuer,
1648
+ notBefore: notBefore, notAfter: notAfter, subject: subject, spkAlg: spkAlg,
1649
+ subjectPublicKey: subjectPublicKey, rsaKey: rsaKey,
1650
+ });
1651
+ } catch (e) {
1652
+ if (nested.budget.exhausted() && !(e && e.code === "c509/too-large")) throw _budgetRefusal(e);
1653
+ throw e;
1654
+ }
1655
+ if (nested.budget.exhausted()) throw _budgetRefusal(null);
1656
+ return result;
1657
+ }
1658
+
1659
+ /** @internal The refusal a cap produces, as distinct from a statement about the bytes. It is read
1660
+ * off the latch rather than off a throw, because the probe that reads an extension value absorbs
1661
+ * a failure by returning null. */
1662
+ function _budgetRefusal(cause) {
1663
+ return _err("c509/too-large", "the C509 certificate exceeds the decode budget for this parse", cause || undefined);
1664
+ }
1665
+
1666
+ function _finishParse(f, root, nested, fields) {
1667
+ var type = fields.type;
1668
+ var extensions = _extensions(f[9], type === 2, nested.ns);
1641
1669
  if (f[10].majorType !== 2) throw _err("c509/bad-signature", "issuerSignatureValue must be a CBOR byte string");
1642
1670
  var signatureValue = f[10].content;
1643
1671
 
1644
1672
  var result = {
1645
1673
  certificateType: type,
1646
- serialNumber: serial,
1647
- serialNumberHex: sHex,
1648
- signatureAlgorithm: sigAlg,
1649
- issuer: issuer,
1650
- validity: { notBefore: notBefore, notAfter: notAfter },
1651
- subject: subject,
1652
- subjectPublicKeyAlgorithm: spkAlg,
1653
- subjectPublicKey: subjectPublicKey,
1654
- rsaPublicKey: rsaKey,
1674
+ serialNumber: fields.serial,
1675
+ serialNumberHex: fields.sHex,
1676
+ signatureAlgorithm: fields.sigAlg,
1677
+ issuer: fields.issuer,
1678
+ validity: { notBefore: fields.notBefore, notAfter: fields.notAfter },
1679
+ subject: fields.subject,
1680
+ subjectPublicKeyAlgorithm: fields.spkAlg,
1681
+ subjectPublicKey: fields.subjectPublicKey,
1682
+ rsaPublicKey: fields.rsaKey,
1655
1683
  extensions: extensions,
1656
1684
  signatureValue: signatureValue,
1657
1685
  };
@@ -1660,7 +1688,6 @@ function parse(input, callerOpts) {
1660
1688
  if (type === 2) result.signedData = result._fieldBytes;
1661
1689
 
1662
1690
  if (type === 3) result.reconstructedDer = _reconstructDer(result, f[10]);
1663
-
1664
1691
  return result;
1665
1692
  }
1666
1693
 
@@ -1883,8 +1910,8 @@ function _euiFromCn(value) {
1883
1910
  if (bytes[3] === 0xff && bytes[4] === 0xfe) return Buffer.concat([bytes.subarray(0, 3), bytes.subarray(5)]);
1884
1911
  return bytes;
1885
1912
  }
1886
- function _c509NameFromDer(nameBytes) {
1887
- var node = asn1.decode(nameBytes);
1913
+ function _c509NameFromDer(nameBytes, ctx) {
1914
+ var node = pkix.decodeNested(nameBytes, ctx, "a Name value");
1888
1915
  var rdns = [];
1889
1916
  (node.children || []).forEach(function (rdnSet) {
1890
1917
  if (!rdnSet.children || rdnSet.children.length !== 1) throw _err("c509/non-invertible", "a C509 Name requires single-attribute RDNs");
@@ -1905,10 +1932,10 @@ function _c509NameFromDer(nameBytes) {
1905
1932
  });
1906
1933
  return { rdns: rdns };
1907
1934
  }
1908
- function _keyUsageBitsFromDer(extnValue) {
1935
+ function _keyUsageBitsFromDer(extnValue, ctx) {
1909
1936
  var bs;
1910
1937
  try {
1911
- bs = asn1.read.bitString(asn1.decode(extnValue));
1938
+ bs = asn1.read.bitString(pkix.decodeNested(extnValue, ctx, "a keyUsage extension value"));
1912
1939
  } catch (_e) {
1913
1940
  return null;
1914
1941
  }
@@ -1925,6 +1952,7 @@ function _magBytes(intNode) {
1925
1952
  }
1926
1953
  function _sigMagWidth(derSig) {
1927
1954
  var n;
1955
+ // allow:bare-decode on the DER -> C509 conversion, below the X.509 parse door that caps the input.
1928
1956
  try { n = asn1.decode(derSig); } catch (e) { throw _err("c509/bad-signature", "the ECDSA issuer signature is not valid DER", e); }
1929
1957
  if (n.tagNumber !== asn1.TAGS.SEQUENCE || n.tagClass !== "universal" || !n.children || n.children.length !== 2) throw _err("c509/bad-signature", "the ECDSA issuer signature must be a SEQUENCE of two INTEGERs");
1930
1958
  for (var i = 0; i < 2; i++) {
@@ -1969,12 +1997,14 @@ _derToType3 = function (input, opts) {
1969
1997
  var sigAlgName = sigCanon === undefined ? c.signatureAlgorithm.name : sigCanon;
1970
1998
  if (String(sigAlgName || "").toLowerCase().indexOf("ecdsa") !== 0) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (sigAlgName || "an unregistered algorithm"));
1971
1999
  if (c.subjectPublicKeyInfo.algorithm.oid !== oid.byName("ecPublicKey")) throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
2000
+ // allow:bare-decode on the DER -> C509 conversion, below the X.509 parse door that caps the input.
1972
2001
  var curveOid = asn1.read.oid(asn1.decode(c.subjectPublicKeyInfo.algorithm.parameters));
1973
2002
  var curveCanon = EC_CURVE_BY_OID[curveOid];
1974
2003
  var curve = curveCanon === undefined ? oid.name(curveOid) : curveCanon;
1975
2004
  var coordLen = EC_FIELD_BYTES[curve];
1976
2005
  if (!coordLen) throw _err("c509/non-invertible", "unsupported EC subject curve " + (curve || curveOid));
1977
2006
  var sigCurve = _resolveIssuerSigCurve(c, opts, sigAlgName);
2007
+ // allow:bare-decode on the DER -> C509 conversion, below the X.509 parse door that caps the input.
1978
2008
  var spkiNode = asn1.decode(c.subjectPublicKeyInfo.bytes);
1979
2009
  return {
1980
2010
  certificateType: 3,
package/lib/schema-cmc.js CHANGED
@@ -195,8 +195,8 @@ function readExtendedFailInfo(children, what) {
195
195
  return { failInfoOID: asn1.read.oid(children[0]), failInfoValueBytes: children[1].bytes };
196
196
  }
197
197
 
198
- function parseStatusInfo(valueDer, version, bodyPartID) {
199
- var root = asn1.decode(valueDer);
198
+ function parseStatusInfo(valueDer, version, bodyPartID, ctx) {
199
+ var root = pkix.decodeNested(valueDer, ctx, "a CMCStatusInfo value");
200
200
  if (root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE || !root.constructed) {
201
201
  throw E("cmc/bad-status-info", "a CMCStatusInfo value must be a SEQUENCE");
202
202
  }
@@ -447,14 +447,15 @@ var PKI_RESPONSE = schema.seq([
447
447
  * that is not one could never be checked against anything. This is asked of a CHALLENGED request only.
448
448
  * A request body carries what its sender sent, and judging it is the enrollment's business, not the
449
449
  * decoder's. */
450
- function _assertAnswerableRequest(request) {
450
+ function _assertAnswerableRequest(request, ctx) {
451
451
  if (request.arm !== "tcr" && request.arm !== "crm") return;
452
452
  /** @internal A CertReqMessages of one, which is the shape the CRMF parser takes; built outside the
453
453
  * catch below so only the parse it wraps can be what fails. */
454
454
  var payload = request.arm === "tcr" ? request.certificationRequestBytes
455
455
  : asn1.build.sequence([asn1.build.raw(request.certReqMsgBytes)]);
456
456
  try {
457
- if (request.arm === "tcr") { csr.parse(payload); } else { crmf.parse(payload); }
457
+ var caps = ctx ? ctx.caps : undefined;
458
+ if (request.arm === "tcr") { csr.parse(payload, caps); } else { crmf.parse(payload, caps); }
458
459
  } catch (e) {
459
460
  throw E("cmc/bad-pop-challenge",
460
461
  "the request an EncryptedPOP quotes is not a " +
@@ -464,13 +465,13 @@ function _assertAnswerableRequest(request) {
464
465
  }
465
466
  }
466
467
 
467
- function parseEncryptedPop(node) {
468
+ function parseEncryptedPop(node, ctx) {
468
469
  if (node.tagClass !== "universal" || node.tagNumber !== TAGS.SEQUENCE || !node.constructed ||
469
470
  node.children.length !== 5) {
470
471
  throw E("cmc/bad-pop-challenge",
471
472
  "an EncryptedPOP is SEQUENCE { request, cms, thePOPAlgID, witnessAlgID, witness } (RFC 5272 sec. 6.7)");
472
473
  }
473
- var envelope = _assertEnvelopedChallenge(node.children[1]);
474
+ var envelope = _assertEnvelopedChallenge(node.children[1], ctx);
474
475
  var request;
475
476
  try { request = parseTaggedRequest(node.children[0]); }
476
477
  catch (e) {
@@ -478,7 +479,7 @@ function parseEncryptedPop(node) {
478
479
  "an EncryptedPOP quotes the request it challenges as a TaggedRequest: " +
479
480
  ((e && e.message) || "malformed") + " (RFC 5272 sec. 6.7)", e);
480
481
  }
481
- _assertAnswerableRequest(request);
482
+ _assertAnswerableRequest(request, ctx);
482
483
  return {
483
484
  request: request,
484
485
  /** @internal The TaggedRequest exactly as it arrived. A proof of possession is a MAC over these
@@ -526,7 +527,7 @@ function parseDecryptedPop(node) {
526
527
  * on unopened because opening them needs key material this parser does not hold. Unopened is not
527
528
  * unchecked: a field that is not a ContentInfo carrying an EnvelopedData cannot be a challenge, and
528
529
  * saying so here is what keeps a control that could never be answered from reading as one. */
529
- function _assertEnvelopedChallenge(node) {
530
+ function _assertEnvelopedChallenge(node, ctx) {
530
531
  var kids = pkix.rootSequenceChildren(node, 2, 2);
531
532
  if (!kids) {
532
533
  throw E("cmc/bad-pop-challenge",
@@ -557,7 +558,7 @@ function _assertEnvelopedChallenge(node) {
557
558
  * a challenge addressed only that way is refused here, and `pki.cms.decrypt` could not open it
558
559
  * either, so it is refused where the reason can be named rather than after a failed answer. */
559
560
  var enveloped;
560
- try { enveloped = cms.walkEnvelopedData(content.children[0]); }
561
+ try { enveloped = cms.walkEnvelopedData(content.children[0], ctx); }
561
562
  catch (e) {
562
563
  throw E("cmc/bad-pop-challenge",
563
564
  "an EncryptedPOP's ContentInfo does not carry an EnvelopedData a recipient could open: " +
@@ -594,26 +595,26 @@ function _popAlgorithm(node, what) {
594
595
  }
595
596
 
596
597
  /** @internal One AttributeValue, which is what a control carrying a single structure holds. */
597
- function _onlyValue(c, what) {
598
+ function _onlyValue(c, what, ctx) {
598
599
  if (c.values.length !== 1) {
599
600
  throw E("cmc/bad-pop-challenge",
600
601
  "a " + what + " control carries exactly one AttributeValue, got " + c.values.length);
601
602
  }
602
- return asn1.decode(c.values[0]);
603
+ return pkix.decodeNested(c.values[0], ctx, what + " control value");
603
604
  }
604
605
 
605
- function _decodePopChallenges(controls) {
606
+ function _decodePopChallenges(controls, ctx) {
606
607
  for (var i = 0; i < controls.length; i++) {
607
608
  var c = controls[i];
608
609
  if (c.attrType === OID_ENCRYPTED_POP) {
609
- c.encryptedPOP = parseEncryptedPop(_onlyValue(c, "an Encrypted POP"));
610
+ c.encryptedPOP = parseEncryptedPop(_onlyValue(c, "an Encrypted POP", ctx), ctx);
610
611
  } else if (c.attrType === OID_DECRYPTED_POP) {
611
- c.decryptedPOP = parseDecryptedPop(_onlyValue(c, "a Decrypted POP"));
612
+ c.decryptedPOP = parseDecryptedPop(_onlyValue(c, "a Decrypted POP", ctx));
612
613
  }
613
614
  }
614
615
  }
615
616
 
616
- function _collectStatuses(controls) {
617
+ function _collectStatuses(controls, ctx) {
617
618
  var out = [];
618
619
  for (var i = 0; i < controls.length; i++) {
619
620
  var c = controls[i];
@@ -622,15 +623,15 @@ function _collectStatuses(controls) {
622
623
  if (c.values.length !== 1) {
623
624
  throw E("cmc/bad-status-info", "a CMC status control carries exactly one AttributeValue, got " + c.values.length);
624
625
  }
625
- out.push(parseStatusInfo(c.values[0], version, c.bodyPartID));
626
+ out.push(parseStatusInfo(c.values[0], version, c.bodyPartID, ctx));
626
627
  }
627
628
  return out;
628
629
  }
629
630
 
630
- function _finishBody(body, where) {
631
+ function _finishBody(body, where, ctx) {
631
632
  for (var i = 0; i < body.controls.length; i++) assertControlPlacement(body.controls[i], where);
632
- _decodePopChallenges(body.controls);
633
- body.statuses = _collectStatuses(body.controls);
633
+ _decodePopChallenges(body.controls, ctx);
634
+ body.statuses = _collectStatuses(body.controls, ctx);
634
635
  body.unhandled = body.controls.filter(function (c) {
635
636
  return c.attrType !== OID_STATUS_INFO && c.attrType !== OID_STATUS_INFO_V2 && c.attrName === null;
636
637
  });
@@ -674,7 +675,7 @@ function parsePkiData(input, callerOpts) {
674
675
  return pkix.runParseWith(input, _BODY_PARSE_OPTS, callerOpts, function (root, ns) {
675
676
  var body = schema.walk(PKI_DATA, root, ns).result;
676
677
  body.bytes = root.bytes;
677
- return _finishBody(body, "pkiData");
678
+ return _finishBody(body, "pkiData", ns);
678
679
  });
679
680
  }
680
681
 
@@ -706,7 +707,7 @@ function parsePkiResponse(input, callerOpts) {
706
707
  return pkix.runParseWith(input, _BODY_PARSE_OPTS, callerOpts, function (root, ns) {
707
708
  var body = schema.walk(PKI_RESPONSE, root, ns).result;
708
709
  body.bytes = root.bytes;
709
- return _finishBody(body, "pkiResponse");
710
+ return _finishBody(body, "pkiResponse", ns);
710
711
  });
711
712
  }
712
713
 
@@ -793,7 +794,9 @@ function parse(input, callerOpts) {
793
794
  throw E("cmc/no-content",
794
795
  "a Full PKI Request / Response carries its body as the encapsulated content; this SignedData is detached (RFC 5272 sec. 3.2)");
795
796
  }
796
- var body = eContentType === OID_PKI_DATA ? parsePkiData(encap.eContent) : parsePkiResponse(encap.eContent);
797
+ var body = eContentType === OID_PKI_DATA
798
+ ? parsePkiData(encap.eContent, caps)
799
+ : parsePkiResponse(encap.eContent, caps);
797
800
  body.cms = parsedCms;
798
801
  body.eContentType = eContentType;
799
802
  return body;