@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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ 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
+
26
+ ## v0.8.3 — 2026-09-25
27
+
28
+ A parser takes decode caps from its caller, and a resource refusal says so instead of reading as malformed input.
29
+
30
+ ### Added
31
+
32
+ - Every parse function takes an optional second argument of decode caps: `maxBytes`, `maxDepth` and `maxItems`. That includes `pki.schema.parse`, the format-detecting door, where the caps bound both the detection decode and the decode of whichever format matches. Each may tighten below the corresponding `pki.C.LIMITS` default and is refused with `<format>/bad-input` if it names a value above it, so a caller can bound a parse more strictly than the toolkit does and cannot grant itself more resource than the built-in refusal allows. An option outside that set is named in the refusal rather than ignored.
33
+ - A new `<format>/too-large` code on each format, reported when a decode limit refuses the input.
34
+
35
+ ### Changed
36
+
37
+ - An input refused for exceeding a decode limit reports `<format>/too-large` where it previously reported `<format>/bad-der`. The two say different things to whoever reads them: one is answered by raising a cap, the other by fixing the bytes. A caller matching on `bad-der` to detect malformed input will stop seeing oversized and over-nested inputs under that code.
38
+
39
+ ### Fixed
40
+
41
+ - A parse that exhausts its decode budget cannot report anything but a refusal, even when code inside the parse catches the failure. The budget latches on exhaustion, and each parser door re-reads that latch after its walk and before returning a result, so a `catch` between the two can absorb the exception and still not produce a value. This is the guarantee that makes the caps worth relying on: without it any of the toolkit's error-absorbing paths could turn `over budget` into an ordinary absent value and change a verdict.
42
+ - The caps a caller names are read into a fresh object, and no flag is ever written onto the object the caller passed or onto the one the decode receives. A resource option therefore cannot alter which encodings a parse accepts; the strictness of a parse is the same whatever caps are asked for.
43
+
44
+ ### Documentation
45
+
46
+ - The caps bound the decode of the input a parser is given, which is what bounds a parse overall, since everything a parse reads comes from those bytes. A structure decoded separately from inside that input keeps the built-in `pki.C.LIMITS` defaults rather than a tighter figure the caller named.
47
+
7
48
  ## v0.8.2 — 2026-09-25
8
49
 
9
50
  A distinguished name can be read back from its string form, and twelve attributes the name table published can finally be encoded.
@@ -65,4 +65,51 @@ function byteCap(buf, max, E, code, label) {
65
65
  return buf;
66
66
  }
67
67
 
68
- module.exports = _freeze({ cap: cap, depthCap: depthCap, counter: counter, byteCap: byteCap });
68
+ /** @internal Thrown when a parse exhausts its resource budget. It is not a PkiError subclass,
69
+ * so a `catch (e) { if (e instanceof PkiError) ...; throw e; }` re-throws it. A parser door
70
+ * converts it to that door's typed refusal, so the public contract is unchanged. */
71
+ function BudgetExceeded(message) {
72
+ var e = new Error(message);
73
+ e.name = "BudgetExceeded";
74
+ e.budgetExceeded = true;
75
+ return e;
76
+ }
77
+
78
+ function isBudgetExceeded(e) {
79
+ return !!(e && e.budgetExceeded === true);
80
+ }
81
+
82
+ /** @internal One budget per top-level parse, carried into every nested decode. `exhausted`
83
+ * latches, and the object is frozen, so a caller that catches the throw cannot clear it. A
84
+ * door that re-reads the latch before returning a result therefore still refuses, whatever
85
+ * code between the two caught the exception. */
86
+ function budget(limits) {
87
+ var spent = 0;
88
+ var exhausted = false;
89
+ var max = cap(limits ? limits.maxBytes : undefined, "maxBytes", constants.LIMITS.DER_MAX_BYTES);
90
+ var self = {
91
+ spend: function (n, what) {
92
+ if (!_isInteger(n) || n < 0) throw new TypeError("guard.limits.budget: spend takes a non-negative integer");
93
+ spent += n;
94
+ if (spent > max) {
95
+ exhausted = true;
96
+ throw BudgetExceeded((what || "the parse") + " exceeds the " + max + "-byte budget for this parse");
97
+ }
98
+ return n;
99
+ },
100
+ trip: function (what) {
101
+ exhausted = true;
102
+ throw BudgetExceeded((what || "the parse") + " exceeds the budget for this parse");
103
+ },
104
+ exhausted: function () { return exhausted; },
105
+ spent: function () { return spent; },
106
+ remaining: function () { return max - spent; },
107
+ max: function () { return max; },
108
+ };
109
+ return _freeze(self);
110
+ }
111
+
112
+ module.exports = _freeze({
113
+ cap: cap, depthCap: depthCap, counter: counter, byteCap: byteCap,
114
+ budget: budget, isBudgetExceeded: isBudgetExceeded,
115
+ });
@@ -144,10 +144,10 @@ var PROVENANCE = new _WeakMap();
144
144
 
145
145
  // @enforced-by guard-shape-reinlined -- shares the fromTrustedSource shape below; a
146
146
  function recordingParser(kind, parse, ErrorClass, code, label) {
147
- return function (input) {
147
+ return function (input, callerOpts) {
148
148
  var isText = typeof input === "string";
149
149
  var snap = isText ? input : bytes.snapshotSource(input, ErrorClass, code, label);
150
- var out = parse(snap);
150
+ var out = parse(snap, callerOpts);
151
151
  if (out && typeof out === "object") {
152
152
  _weakSet(PROVENANCE, out, { kind: kind, source: isText ? snap : _bufferFrom(snap),
153
153
  shape: _shapeOf(out) });
@@ -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,
package/lib/schema-all.js CHANGED
@@ -60,85 +60,85 @@ var FORMATS = [
60
60
  name: "cms",
61
61
  module: cms,
62
62
  detect: cms.matches,
63
- parse: function (input) { return cms.parse(input); },
63
+ parse: function (input, caps) { return cms.parse(input, caps); },
64
64
  },
65
65
  {
66
66
  name: "tsp",
67
67
  module: tsp,
68
68
  detect: tsp.matches,
69
- parse: function (input) { return tsp.parse(input); },
69
+ parse: function (input, caps) { return tsp.parse(input, caps); },
70
70
  },
71
71
  {
72
72
  name: "crmf",
73
73
  module: crmf,
74
74
  detect: crmf.matches,
75
- parse: function (input) { return crmf.parse(input); },
75
+ parse: function (input, caps) { return crmf.parse(input, caps); },
76
76
  },
77
77
  {
78
78
  name: "cmp",
79
79
  module: cmp,
80
80
  detect: cmp.matches,
81
- parse: function (input) { return cmp.parse(input); },
81
+ parse: function (input, caps) { return cmp.parse(input, caps); },
82
82
  },
83
83
  {
84
84
  name: "csrattrs",
85
85
  module: csrattrs,
86
86
  detect: csrattrs.matches,
87
- parse: function (input) { return csrattrs.parse(input); },
87
+ parse: function (input, caps) { return csrattrs.parse(input, caps); },
88
88
  },
89
89
  {
90
90
  name: "ocsp-request",
91
91
  module: ocsp,
92
92
  detect: ocsp.matchesRequest,
93
- parse: function (input) { return ocsp.parseRequest(input); },
93
+ parse: function (input, caps) { return ocsp.parseRequest(input, caps); },
94
94
  },
95
95
  {
96
96
  name: "ocsp-response",
97
97
  module: ocsp,
98
98
  detect: ocsp.matchesResponse,
99
- parse: function (input) { return ocsp.parseResponse(input); },
99
+ parse: function (input, caps) { return ocsp.parseResponse(input, caps); },
100
100
  },
101
101
  {
102
102
  name: "pkcs12",
103
103
  module: pkcs12,
104
104
  detect: pkcs12.matches,
105
- parse: function (input) { return pkcs12.parse(input); },
105
+ parse: function (input, caps) { return pkcs12.parse(input, caps); },
106
106
  },
107
107
  {
108
108
  name: "pkcs8",
109
109
  module: pkcs8,
110
110
  detect: pkcs8.matches,
111
- parse: function (input) { return pkcs8.parse(input); },
111
+ parse: function (input, caps) { return pkcs8.parse(input, caps); },
112
112
  },
113
113
  {
114
114
  name: "csr",
115
115
  module: csr,
116
116
  detect: csr.matches,
117
- parse: function (input) { return csr.parse(input); },
117
+ parse: function (input, caps) { return csr.parse(input, caps); },
118
118
  },
119
119
  {
120
120
  name: "attrcert",
121
121
  module: attrcert,
122
122
  detect: attrcert.matches,
123
- parse: function (input) { return attrcert.parse(input); },
123
+ parse: function (input, caps) { return attrcert.parse(input, caps); },
124
124
  },
125
125
  {
126
126
  name: "attrcert-v1",
127
127
  module: attrcert,
128
128
  detect: attrcert.matchesV1,
129
- parse: function (input) { return attrcert.parseV1(input); },
129
+ parse: function (input, caps) { return attrcert.parseV1(input, caps); },
130
130
  },
131
131
  {
132
132
  name: "crl",
133
133
  module: crl,
134
134
  detect: crl.matches,
135
- parse: function (input) { return crl.parse(input); },
135
+ parse: function (input, caps) { return crl.parse(input, caps); },
136
136
  },
137
137
  {
138
138
  name: "x509",
139
139
  module: x509,
140
140
  detect: x509.matches,
141
- parse: function (input) { return x509.parse(input); },
141
+ parse: function (input, caps) { return x509.parse(input, caps); },
142
142
  },
143
143
  ];
144
144
 
@@ -159,7 +159,7 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
159
159
 
160
160
  /**
161
161
  * @primitive pki.schema.parse
162
- * @signature pki.schema.parse(input) -> parsed
162
+ * @signature pki.schema.parse(input, caps?) -> parsed
163
163
  * @since 0.1.7
164
164
  * @status stable
165
165
  * @spec RFC 5280
@@ -171,6 +171,13 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
171
171
  * the bytes match no registered format; the underlying decode / structural
172
172
  * errors of the matched format propagate unchanged.
173
173
  *
174
+ * @opts
175
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
176
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
177
+ * above it, or an option outside this set, is refused. They bound the detection decode
178
+ * and the decode of whichever format matches, and a parse that exceeds one is refused
179
+ * with the `/too-large` code of that format.
180
+ *
174
181
  * @example
175
182
  * var pair = await pki.key.generate("Ed25519");
176
183
  * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
@@ -178,11 +185,15 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
178
185
  * { key: await pki.key.export(pair.privateKey) });
179
186
  * var parsed = pki.schema.parse(der); // cert -> the pki.schema.x509 shape
180
187
  */
181
- function parse(input) {
188
+ function parse(input, callerOpts) {
189
+ /** @internal The caps are validated once here and the snapshot is what the chosen format
190
+ * receives, so the detection decode and the format's own decode are held to the same
191
+ * figures a caller named, read once. */
192
+ var caps = pkix.parseCaps(callerOpts, ENTRY);
182
193
  var der = pkix.coerceToDer(input, ENTRY);
183
- var root = pkix.decodeRoot(der, ENTRY);
194
+ var root = pkix.decodeRoot(der, ENTRY, caps);
184
195
  for (var i = 0; i < FORMATS.length; i++) {
185
- if (FORMATS[i].detect(root)) return FORMATS[i].parse(der);
196
+ if (FORMATS[i].detect(root)) return FORMATS[i].parse(der, caps);
186
197
  }
187
198
  throw new SchemaError("schema/unknown-format", "input does not match any registered PKI format (" + all().join(", ") + ")");
188
199
  }
@@ -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 {
@@ -248,7 +248,7 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
248
248
 
249
249
  /**
250
250
  * @primitive pki.schema.attrcert.parse
251
- * @signature pki.schema.attrcert.parse(input) -> attributeCertificate
251
+ * @signature pki.schema.attrcert.parse(input, caps?) -> attributeCertificate
252
252
  * @since 0.1.14
253
253
  * @status stable
254
254
  * @spec RFC 5755, X.509
@@ -279,25 +279,32 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
279
279
  * var ac = pki.schema.attrcert.parse(der);
280
280
  * ac.attributes[0].name; // "role"
281
281
  * ac.validity.notAfterTime;// Date
282
+ * @opts
283
+ * - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
284
+ * defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
285
+ * above it, or an option outside this set, is refused. A parse that exceeds one is
286
+ * refused with the `/too-large` code of this format.
282
287
  */
283
- var PARSE_OPTS = intrinsic.assign(intrinsic.create(null), { pemLabel: "ATTRIBUTE CERTIFICATE", PemError: PemError, ErrorClass: AttrCertError, prefix: "attrcert", what: "attribute certificate" });
288
+ var PARSE_OPTS = intrinsic.assign(intrinsic.create(null), { pemLabel: "ATTRIBUTE CERTIFICATE", PemError: PemError, ErrorClass: AttrCertError, prefix: "attrcert", what: "attribute certificate", ns: NS });
284
289
 
285
290
  function _legacyV1Error() {
286
291
  return new AttrCertError("attrcert/legacy-v1-not-supported", "AttributeCertificateV1 (X.509-1997) is obsolete and not parsed by this build (RFC 5755 sec. 1)");
287
292
  }
288
293
 
289
- function _parseAc(input) {
290
- var root = pkix.decodeRoot(pkix.coerceToDer(input, PARSE_OPTS), PARSE_OPTS);
291
- if (matchesV1(root)) throw _legacyV1Error();
292
- return schema.walk(ATTRIBUTE_CERTIFICATE, root, NS).result;
294
+ function _parseAc(input, callerOpts) {
295
+ return pkix.runParseWith(input, PARSE_OPTS, callerOpts, function (root, ns) {
296
+ if (matchesV1(root)) throw _legacyV1Error();
297
+ return schema.walk(ATTRIBUTE_CERTIFICATE, root, ns).result;
298
+ });
293
299
  }
294
300
 
295
301
  var parse = guard.parsed.recordingParser("attributeCertificate", _parseAc,
296
302
  AttrCertError, "attrcert/bad-input", "an attribute certificate");
297
303
 
298
- function parseV1(input) {
299
- pkix.decodeRoot(pkix.coerceToDer(input, PARSE_OPTS), PARSE_OPTS);
300
- throw _legacyV1Error();
304
+ function parseV1(input, callerOpts) {
305
+ return pkix.runParseWith(input, PARSE_OPTS, callerOpts, function () {
306
+ throw _legacyV1Error();
307
+ });
301
308
  }
302
309
 
303
310
  /**
@@ -391,12 +398,13 @@ function _oneGN(node, code) {
391
398
 
392
399
  // @internal
393
400
  function readGeneralName(der, code) {
401
+ // allow:bare-decode reached from the builder, not from a parse, so no parse context carries caps here.
394
402
  return _oneGN(asn1.decode(der), code || "attrcert/bad-input");
395
403
  }
396
404
 
397
405
  function _svceAuthInfo(authInfoAllowed, code) {
398
- return function (buf) {
399
- var kids = _seqChildren(buf, code, "SvceAuthInfo");
406
+ return function (buf, ctx) {
407
+ var kids = _seqChildren(buf, code, "SvceAuthInfo", ctx);
400
408
  if (kids.length < 2 || kids.length > 3) throw NS.E(code, "SvceAuthInfo must be a SEQUENCE { service, ident, authInfo? }");
401
409
  var service = _oneGN(kids[0], code);
402
410
  var ident = _oneGN(kids[1], code);
@@ -410,12 +418,12 @@ function _svceAuthInfo(authInfoAllowed, code) {
410
418
  };
411
419
  }
412
420
 
413
- function _ietfAttrSyntax(buf) {
421
+ function _ietfAttrSyntax(buf, ctx) {
414
422
  var C = "attrcert/bad-ietf-attr";
415
- var kids = _seqChildren(buf, C, "IetfAttrSyntax");
423
+ var kids = _seqChildren(buf, C, "IetfAttrSyntax", ctx);
416
424
  if (!kids.length || kids.length > 2) throw NS.E(C, "IetfAttrSyntax must be a SEQUENCE { policyAuthority [0]?, values }");
417
425
  var policyAuthority = null, i = 0;
418
- 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; }
419
427
  var valuesNode = kids[i];
420
428
  if (!valuesNode || !_isU(valuesNode, TAGS.SEQUENCE) || !valuesNode.children || !valuesNode.children.length) {
421
429
  throw NS.E(C, "IetfAttrSyntax values must be a non-empty SEQUENCE OF value");
@@ -431,12 +439,12 @@ function _ietfAttrSyntax(buf) {
431
439
  return { policyAuthority: policyAuthority, values: values, homogeneous: Object.keys(kinds).length === 1 };
432
440
  }
433
441
 
434
- function _role(buf) {
442
+ function _role(buf, ctx) {
435
443
  var C = "attrcert/bad-role";
436
- var kids = _seqChildren(buf, C, "RoleSyntax");
444
+ var kids = _seqChildren(buf, C, "RoleSyntax", ctx);
437
445
  if (!kids.length || kids.length > 2) throw NS.E(C, "RoleSyntax must be a SEQUENCE { roleAuthority [0]?, roleName [1] }");
438
446
  var roleAuthority = null, i = 0;
439
- 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; }
440
448
  var rn = kids[i];
441
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");
442
450
  if (i + 1 !== kids.length) throw NS.E(C, "RoleSyntax has unexpected trailing fields");
@@ -466,9 +474,9 @@ function _securityCategory(node, C) {
466
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");
467
475
  return { type: type, valueBytes: Buffer.concat([valueNode.children[0].bytes]) };
468
476
  }
469
- function _clearance(buf) {
477
+ function _clearance(buf, ctx) {
470
478
  var C = "attrcert/bad-clearance";
471
- var kids = _seqChildren(buf, C, "Clearance");
479
+ var kids = _seqChildren(buf, C, "Clearance", ctx);
472
480
  if (!kids.length || kids.length > 3) throw NS.E(C, "Clearance must be a SEQUENCE { policyId, classList?, securityCategories? }");
473
481
  if (!_isU(kids[0], TAGS.OBJECT_IDENTIFIER)) throw NS.E(C, "Clearance policyId must be an OBJECT IDENTIFIER");
474
482
  var policyId; try { policyId = asn1.read.oid(kids[0]); } catch (e) { throw NS.E(C, "Clearance policyId must be an OBJECT IDENTIFIER", e); }
@@ -501,16 +509,16 @@ _ATTR_VALUE_DECODERS[_Oav("chargingIdentity")] = _ietfAttrSyntax;
501
509
  _ATTR_VALUE_DECODERS[_Oav("group")] = _ietfAttrSyntax;
502
510
 
503
511
 
504
- function _acAuditIdentity(buf) {
512
+ function _acAuditIdentity(buf, ctx) {
505
513
  var C = "attrcert/bad-audit-identity";
506
- var n = _decodeTop(buf, C, "AuditIdentity");
514
+ var n = _decodeTop(buf, C, "AuditIdentity", ctx);
507
515
  if (!_isU(n, TAGS.OCTET_STRING)) throw NS.E(C, "AuditIdentity must be an OCTET STRING");
508
516
  return Buffer.concat([asn1.read.octetString(n)]);
509
517
  }
510
518
 
511
- function _noRevAvail(buf) {
519
+ function _noRevAvail(buf, ctx) {
512
520
  var C = "attrcert/bad-no-rev-avail";
513
- var n = _decodeTop(buf, C, "NoRevAvail");
521
+ var n = _decodeTop(buf, C, "NoRevAvail", ctx);
514
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); }
515
523
  return { noRevAvail: true };
516
524
  }
@@ -539,13 +547,13 @@ function _acDecodeTargets(node, C, allowTargetCert) {
539
547
  if (!_isU(node, TAGS.SEQUENCE) || !node.children || !node.children.length) throw NS.E(C, "Targets must be a non-empty SEQUENCE OF Target");
540
548
  return node.children.map(function (t) { return _acTarget(t, C, allowTargetCert); });
541
549
  }
542
- function _seqOfTargets(buf, C, allowTargetCert) {
543
- 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);
544
552
  if (!kids.length) throw NS.E(C, "a SEQUENCE OF Targets must be non-empty");
545
553
  return kids.map(function (targetsNode) { return _acDecodeTargets(targetsNode, C, allowTargetCert); });
546
554
  }
547
- function _targetInformation(buf) { return _seqOfTargets(buf, "attrcert/bad-targets", true); }
548
- 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); }
549
557
 
550
558
  function _acAttrSpec(node, C) {
551
559
  if (!node.children) throw NS.E(C, "an AttrSpec must be a SEQUENCE OF OBJECT IDENTIFIER");
@@ -554,9 +562,9 @@ function _acAttrSpec(node, C) {
554
562
  try { return asn1.read.oid(o); } catch (e) { throw NS.E(C, "an AttrSpec element must be an OBJECT IDENTIFIER", e); }
555
563
  });
556
564
  }
557
- function _aaControls(buf) {
565
+ function _aaControls(buf, ctx) {
558
566
  var C = "attrcert/bad-aa-controls";
559
- var kids = _seqChildren(buf, C, "AAControls");
567
+ var kids = _seqChildren(buf, C, "AAControls", ctx);
560
568
  if (kids.length > 4) throw NS.E(C, "AAControls has too many fields");
561
569
  var out = { pathLenConstraint: null, permittedAttrs: null, excludedAttrs: null, permitUnSpecified: true }, i = 0;
562
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++; }
@@ -581,9 +589,18 @@ _AC_EXT_DECODERS[_Oav("acProxying")] = _acProxying;
581
589
  * through the certificate decoders, so a relying party running the sec. 6 "pointer in AC" scheme
582
590
  * reads the distribution point and the OCSP location off the parse rather than off raw bytes. */
583
591
  var _CERT_EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
584
- _AC_EXT_DECODERS[_Oav("authorityKeyIdentifier")] = _CERT_EXT_DECODERS[_Oav("authorityKeyIdentifier")];
585
- _AC_EXT_DECODERS[_Oav("authorityInfoAccess")] = _CERT_EXT_DECODERS[_Oav("authorityInfoAccess")];
586
- _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");
587
604
 
588
605
  // @internal
589
606
  function validateAttributeValue(type, valueDer) {