@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 +41 -0
- package/lib/guard-limits.js +48 -1
- package/lib/guard-parsed.js +4 -4
- package/lib/schema-all.js +29 -18
- package/lib/schema-attrcert.js +52 -35
- package/lib/schema-c509.js +92 -46
- package/lib/schema-cmc.js +75 -36
- package/lib/schema-cmp.js +14 -9
- package/lib/schema-cms.js +45 -29
- package/lib/schema-crl.js +17 -12
- package/lib/schema-crmf.js +17 -7
- package/lib/schema-csr.js +6 -1
- package/lib/schema-csrattrs.js +14 -7
- package/lib/schema-engine.js +65 -2
- package/lib/schema-ocsp.js +29 -18
- package/lib/schema-pkcs12.js +21 -12
- package/lib/schema-pkcs8.js +21 -4
- package/lib/schema-pkix.js +183 -10
- package/lib/schema-smime.js +12 -2
- package/lib/schema-tsp.js +54 -9
- package/lib/schema-x509.js +6 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
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.
|
package/lib/guard-limits.js
CHANGED
|
@@ -65,4 +65,51 @@ function byteCap(buf, max, E, code, label) {
|
|
|
65
65
|
return buf;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
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
|
+
});
|
package/lib/guard-parsed.js
CHANGED
|
@@ -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
|
}
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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.
|
|
300
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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) {
|