@blamejs/pki 0.8.3 → 0.8.5

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,38 @@ 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.5 — 2026-09-25
8
+
9
+ A key this process cannot export can sign, and every verb that signs proves its signature first.
10
+
11
+ ### Added
12
+
13
+ - A signer `{ algorithm, publicKey, sign }` is accepted wherever a signing verb takes a private key: `pki.x509.sign`, `pki.crl.sign`, `pki.csr.sign`, `pki.cms.sign` and its countersignatures, `pki.attrcert.sign`, `pki.cmp.build`, `pki.crmf.build`, `pki.ocsp.buildRequest`, `pki.ocsp.sign` and `pki.tsp.sign`. `sign(bytes)` is handed the bytes WebCrypto would be handed and returns the bytes WebCrypto would return, or a promise of them; for ECDSA that is the fixed-width `r || s` of RFC 9053 sec. 2.1, and a DER `SEQUENCE` is refused by name rather than encoded a second time. The private half never reaches the toolkit.
14
+ - A signer's declared `algorithm` is held to the algorithm of the key the artifact certifies, and its declared `publicKey` to the key the signature is verified against, both before the callback is asked to sign. The caller's object is read once, in the synchronous prologue, so an accessor cannot answer one way to the check and another to the use. For an ECDSA key, whose curve is named but whose digest is chosen per signature, the digest the signer declares is checked on its own, before any remote signing request. The bytes the callback is handed are a copy in an allocation of its own, so a callback that rewrites them rewrites only its own copy and cannot reach the bytes the artifact carries. The bytes it returns are copied before use. Hold a client or any mutable state in the callback's closure: a signing verb snapshots its options, so the signer object the callback sees is a copy of the one you passed. A composite ML-DSA signature is refused by name, since it is produced from both component private keys and one callback cannot do that.
15
+
16
+ ### Fixed
17
+
18
+ - `pki.ocsp.sign`, `pki.ocsp.buildRequest` and `pki.cms.sign`'s countersignature path verify the signature they produced against the public key the artifact declares, which the other signing verbs already did. A signer holding a key that does not match the certificate it signs under produced an OCSP response, an OCSP request or a countersignature that no relying party could validate, and the emitting side reported success.
19
+
20
+ ## v0.8.4 — 2026-09-25
21
+
22
+ A decode cap bounds every structure a parse reads, and a refusal caused by a cap is no longer reported as malformed input.
23
+
24
+ ### Added
25
+
26
+ - `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.
27
+ - `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.
28
+
29
+ ### Changed
30
+
31
+ - 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`.
32
+
33
+ ### Fixed
34
+
35
+ - 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.
36
+ - `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.
37
+ - 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.
38
+
7
39
  ## v0.8.3 — 2026-09-25
8
40
 
9
41
  A parser takes decode caps from its caller, and a resource refusal says so instead of reading as malformed input.
package/README.md CHANGED
@@ -190,6 +190,33 @@ var ok = await subtle.verify({ name: "ML-DSA-65" }, kp.publicKey, sig, data); /
190
190
  // same call shape, and every key it exports is OpenSSL/NSS-interoperable.
191
191
  ```
192
192
 
193
+ ### Sign with a key this process cannot export
194
+
195
+ Every signing verb takes a signer object wherever it takes a private key, for a
196
+ key held in a hardware security module, a cloud key management service, a PKCS#11
197
+ token, a PIV slot or a Trusted Platform Module. The private half never reaches
198
+ the toolkit.
199
+
200
+ ```js
201
+ var signer = {
202
+ algorithm: { name: "ECDSA", namedCurve: "P-256", hash: { name: "SHA-256" } },
203
+ publicKey: spkiDer, // the SPKI DER of the public half
204
+ sign: function (bytes) { return kms.sign(keyId, bytes); }, // bytes or a promise of them
205
+ };
206
+
207
+ var cert = await pki.x509.sign(spec, { key: signer, cert: caCert });
208
+ ```
209
+
210
+ `sign(bytes)` is handed the bytes WebCrypto would be handed and returns the bytes
211
+ WebCrypto would return: for ECDSA the fixed-width `r || s` of RFC 9053 §2.1, not a
212
+ DER `SEQUENCE`. `algorithm` is held to the certificate's key algorithm, `publicKey`
213
+ is held to the key the signature is verified against, and the signature is
214
+ verified before the artifact is returned. The bytes handed to the callback are a
215
+ copy, so rewriting them rewrites nothing the artifact carries. Hold the client and
216
+ any mutable state in the callback's closure, as the example does: a signing verb
217
+ snapshots its options, so the signer object the callback sees is a copy of the one
218
+ you passed.
219
+
193
220
  ## What ships today
194
221
 
195
222
  Everything below is callable now; nothing is a stub. The whole documented
package/lib/cms-sign.js CHANGED
@@ -214,7 +214,10 @@ function _keyOnlyCertStandIn(spkiDer) {
214
214
  if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
215
215
  throw _err("cms/bad-input", "a key-only signer's spki is not a SubjectPublicKeyInfo", e);
216
216
  }
217
- return { subjectPublicKeyInfo: { algorithm: alg } };
217
+ /** @internal The SPKI bytes travel with the stand-in, because the key this signature is verified
218
+ * against is what a signer's declared publicKey is held to, and this form names that key by SPKI
219
+ * rather than by certificate. */
220
+ return { subjectPublicKeyInfo: { bytes: spkiDer, algorithm: alg } };
218
221
  }
219
222
 
220
223
  function _keyOnlyKeyId(so) {
@@ -432,11 +435,18 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
432
435
  return _buildSignedAttrs(pairs.concat(extra));
433
436
  });
434
437
  }).then(function (attrs) {
435
- return signScheme.signOverTbs(scheme, soKey, attrs ? attrs.setOf : targetSigOctets, _signE).then(function (sig) {
436
- var fields = [b.integer(BigInt(sidv.version)), sidv.sid, scheme.digestAlgId];
437
- if (attrs) fields.push(attrs.wire);
438
- fields.push(scheme.sigAlgId, b.octetString(sig));
439
- return { value: b.sequence(fields), certDer: certDer };
438
+ var preimage = attrs ? attrs.setOf : targetSigOctets;
439
+ return signScheme.signOverTbs(scheme, soKey, preimage, _signE).then(function (sig) {
440
+ /** @internal The countersignature is held to the same proof the signature is: it verifies
441
+ * against the public key the countersigner's certificate declares. */
442
+ return guard.async.deferred(function () {
443
+ return _b.assertSignatureVerifies(preimage, sig, cert.subjectPublicKeyInfo.bytes, scheme);
444
+ }).then(function () {
445
+ var fields = [b.integer(BigInt(sidv.version)), sidv.sid, scheme.digestAlgId];
446
+ if (attrs) fields.push(attrs.wire);
447
+ fields.push(scheme.sigAlgId, b.octetString(sig));
448
+ return { value: b.sequence(fields), certDer: certDer };
449
+ });
440
450
  });
441
451
  });
442
452
  }
package/lib/cms-verify.js CHANGED
@@ -721,7 +721,8 @@ function _addCert(out, der) {
721
721
  * S/MIME signed mail, RFC 3161 timestamp tokens, and code signing rest on, and exactly what
722
722
  * `pki.cms.verify` consumes and OpenSSL `cms -verify` validates. Each `signers[i]` is
723
723
  * `{ cert, key, digestAlgorithm?, pss? }`: `cert` the signer certificate (PEM or DER), `key`
724
- * its private key (a WebCrypto `CryptoKey` or a PKCS#8 DER `Buffer` / PEM string; for a composite
724
+ * its private key (a WebCrypto `CryptoKey`, a PKCS#8 DER `Buffer` / PEM string, or a signer
725
+ * `{ algorithm, publicKey, sign }` for a key held outside this process; for a composite
725
726
  * ML-DSA signer, the two component keys `{ mldsa, trad }`, each PKCS#8). The
726
727
  * signature covers the RFC 5652 sec. 5.4 preimage: with signed attributes (the default) the
727
728
  * message-digest attribute is bound to the content digest and the signature is over the
package/lib/csr-sign.js CHANGED
@@ -77,7 +77,8 @@ function _challengePassword(pw) {
77
77
  * `challengePassword`. An extension the issuing CA assigns (authorityKeyIdentifier, the
78
78
  * certificate-transparency pair, the CA version and previous-certificate hash, ocspNoCheck) is not a
79
79
  * request and is refused by name. `key` (or
80
- * `{ key }`) is the subject's own PKCS#8 private key / WebCrypto CryptoKey, so the request is self-signed
80
+ * `{ key }`) is the subject's own PKCS#8 private key, WebCrypto CryptoKey, or signer
81
+ * `{ algorithm, publicKey, sign }`, so the request is self-signed
81
82
  * to prove possession of the private half of `subjectPublicKey`, and that proof is verified before the
82
83
  * request is returned. The signature algorithm is resolved from the subject key (RSA PKCS#1 v1.5 or PSS,
83
84
  * ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm). Returns DER, or a PEM `CERTIFICATE REQUEST` with
@@ -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/ocsp.js CHANGED
@@ -45,6 +45,9 @@ var b = asn1.build;
45
45
  function O(name) { return oid.byName(name); }
46
46
  function _err(code, message, cause) { return new OcspError(code, message, cause); }
47
47
  function _signE(kind, message, cause) { return new OcspError("ocsp/" + kind, message, cause); }
48
+ /** @internal Only the signature self-check is taken from the shared builder here; the name and
49
+ * SPKI schemas it also serves belong to the certificate verbs, and this module reads neither. */
50
+ var _b = pkiBuild.makeBuilder({ ErrorClass: OcspError, prefix: "ocsp", O: O, NS: null, NAME_SCHEMA: null, SPKI_SCHEMA: null, EXT_DECODERS: {} });
48
51
 
49
52
  var HASH_WC = Object.assign(Object.create(null), { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" });
50
53
  var REASON_CODE = Object.create(null);
@@ -214,8 +217,12 @@ function _buildRequest(query, opts) {
214
217
  if (signerCert === null) return _emitReq(b.sequence([tbsRequest]), opts);
215
218
  var scheme = signScheme.resolveSignScheme(signerCert, { combinedRsaSig: true }, true, _signE);
216
219
  return signScheme.signOverTbs(scheme, opts.signer.key, tbsRequest, _signE).then(function (sig) {
217
- var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
218
- return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
220
+ /** @internal The request is held to the same proof a certificate is: the signature verifies
221
+ * against the public key the certificate it carries declares. */
222
+ return Promise.resolve(_b.assertSignatureVerifies(tbsRequest, sig, signerCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
223
+ var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
224
+ return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
225
+ });
219
226
  });
220
227
  });
221
228
  }
@@ -349,11 +356,15 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
349
356
  var responseDataDer = b.sequence(rdChildren);
350
357
  var scheme = signScheme.resolveSignScheme(respCert, { combinedRsaSig: true }, true, _signE);
351
358
  return signScheme.signOverTbs(scheme, responderKey, responseDataDer, _signE).then(function (sig) {
352
- var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
353
- if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
354
- var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
355
- var der = b.sequence([b.enumerated(0n), b.explicit(0, responseBytes)]);
356
- return opts.pem ? ocspSchema.pemEncode(der, "OCSP RESPONSE") : der;
359
+ /** @internal The response is held to the same proof a certificate is: the signature verifies
360
+ * against the public key the responder certificate declares. */
361
+ return Promise.resolve(_b.assertSignatureVerifies(responseDataDer, sig, respCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
362
+ var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
363
+ if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
364
+ var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
365
+ var der = b.sequence([b.enumerated(0n), b.explicit(0, responseBytes)]);
366
+ return opts.pem ? ocspSchema.pemEncode(der, "OCSP RESPONSE") : der;
367
+ });
357
368
  });
358
369
  });
359
370
  });
package/lib/pki-build.js CHANGED
@@ -1290,7 +1290,7 @@ function makeBuilder(ctx) {
1290
1290
  var keyOid;
1291
1291
  try { keyOid = asn1.read.oid(alg.children[0]); }
1292
1292
  catch (e) { throw E("bad-input", "the signing key SPKI algorithm is not an OID", e); }
1293
- return { subjectPublicKeyInfo: { algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
1293
+ return { subjectPublicKeyInfo: { bytes: spkiDer, algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
1294
1294
  }
1295
1295
 
1296
1296
  function assertSignatureVerifies(preimage, sig, spki, scheme) {
@@ -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) {