@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 +19 -0
- package/lib/guard-parsed.js +2 -2
- package/lib/schema-attrcert.js +36 -26
- package/lib/schema-c509.js +72 -42
- package/lib/schema-cmc.js +25 -22
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +39 -28
- package/lib/schema-crl.js +11 -11
- package/lib/schema-crmf.js +11 -6
- package/lib/schema-csrattrs.js +8 -6
- package/lib/schema-engine.js +65 -2
- package/lib/schema-ocsp.js +17 -16
- package/lib/schema-pkcs12.js +15 -11
- package/lib/schema-pkcs8.js +9 -2
- package/lib/schema-pkix.js +44 -17
- package/lib/schema-tsp.js +16 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-pkcs12.js
CHANGED
|
@@ -188,7 +188,8 @@ var SAFE_BAG = schema.seq([
|
|
|
188
188
|
}
|
|
189
189
|
if (a.type === OID_LOCAL_KEY_ID) {
|
|
190
190
|
if (seen[a.type] || a.values.length !== 1) throw ctx.E("pkcs12/bad-local-key-id", "localKeyId carries exactly one OCTET STRING value (PKCS#9 SINGLE VALUE)");
|
|
191
|
-
localKeyId = _octetContent(
|
|
191
|
+
localKeyId = _octetContent(pkix.decodeNested(a.values[0], ctx, "a localKeyId value", { ber: true }), ctx,
|
|
192
|
+
"a localKeyId value", "pkcs12/bad-local-key-id");
|
|
192
193
|
}
|
|
193
194
|
seen[a.type] = true;
|
|
194
195
|
}
|
|
@@ -255,7 +256,7 @@ var PFX = schema.seq([
|
|
|
255
256
|
}
|
|
256
257
|
if (authSafe.contentType === OID_SIGNED_DATA) {
|
|
257
258
|
if (macPresent) throw ctx.E("pkcs12/bad-integrity-mode", "an id-signedData authSafe (public-key integrity) must omit macData (RFC 7292 sec. 4)");
|
|
258
|
-
var signed = cms.walkSignedData(authSafe.innerNode);
|
|
259
|
+
var signed = cms.walkSignedData(authSafe.innerNode, ctx);
|
|
259
260
|
if (signed.encapContentInfo.eContentType !== OID_DATA) {
|
|
260
261
|
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must encapsulate id-data carrying the AuthenticatedSafe (RFC 7292 sec. 4.1)");
|
|
261
262
|
}
|
|
@@ -296,7 +297,7 @@ function _privacySafe(content, ctx) {
|
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
function _bmpValue(bytes, ctx) {
|
|
299
|
-
var node =
|
|
300
|
+
var node = pkix.decodeNested(bytes, ctx, "a friendlyName value", { ber: true });
|
|
300
301
|
if (!(node.tagClass === "universal" && node.tagNumber === TAGS.BMP_STRING)) {
|
|
301
302
|
throw ctx.E("pkcs12/bad-friendly-name", "a friendlyName value must be a BMPString (RFC 7292 sec. 4.2)");
|
|
302
303
|
}
|
|
@@ -310,12 +311,15 @@ function _walkAuthenticatedSafe(bytes, state, ctx) {
|
|
|
310
311
|
});
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
/** @internal Reads a decrypted SafeContents. `ctx` is the parse context of the door that produced
|
|
315
|
+
* the bytes when there is one, so its caps and budget reach the bags; the decrypt path in
|
|
316
|
+
* pki.pkcs12.open has none and takes the built-in ceilings. */
|
|
317
|
+
function walkSafeContents(der, ctx) {
|
|
318
|
+
var ns = schema.withResources(NS, ctx);
|
|
319
|
+
var walked = schema.walk(SAFE_CONTENTS, pkix.decodeNested(der, ns, "a SafeContents", { ber: true }), ns);
|
|
316
320
|
var state = { budget: { remaining: C.LIMITS.PKCS12_MAX_REDECODES }, bagDepth: 0 };
|
|
317
321
|
var bags = [];
|
|
318
|
-
for (var i = 0; i < walked.items.length; i++) bags.push(_buildBag(walked.items[i].value.result, state,
|
|
322
|
+
for (var i = 0; i < walked.items.length; i++) bags.push(_buildBag(walked.items[i].value.result, state, ns));
|
|
319
323
|
return bags;
|
|
320
324
|
}
|
|
321
325
|
|
|
@@ -333,9 +337,9 @@ function _dispatchSafes(asMatch, state, ctx) {
|
|
|
333
337
|
safeBags.push(_buildBag(contents.items[j].value.result, state, ctx));
|
|
334
338
|
}
|
|
335
339
|
} else if (el.contentType === OID_ENCRYPTED_DATA) {
|
|
336
|
-
encryptedSafes.push({ type: "encryptedData", content: _privacySafe(cms.walkEncryptedData(el.innerNode), ctx) });
|
|
340
|
+
encryptedSafes.push({ type: "encryptedData", content: _privacySafe(cms.walkEncryptedData(el.innerNode, ctx), ctx) });
|
|
337
341
|
} else if (el.contentType === OID_ENVELOPED_DATA) {
|
|
338
|
-
encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode), ctx), envelopedDataDer: el.innerNode.bytes });
|
|
342
|
+
encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode, ctx), ctx), envelopedDataDer: el.innerNode.bytes });
|
|
339
343
|
} else {
|
|
340
344
|
throw ctx.E("pkcs12/bad-safe-contentinfo-type", "an AuthenticatedSafe element must be id-data, id-encryptedData, or id-envelopedData (RFC 7292 sec. 4.1), got " +
|
|
341
345
|
(ctx.oid.name(el.contentType) || el.contentType));
|
|
@@ -351,13 +355,13 @@ function _buildBag(rec, state, ctx) {
|
|
|
351
355
|
};
|
|
352
356
|
if (rec.bagId === OID_KEY_BAG) {
|
|
353
357
|
bag.type = "keyBag";
|
|
354
|
-
bag.key = pkcs8.walkPrivateKeyInfo(rec.valueNode);
|
|
358
|
+
bag.key = pkcs8.walkPrivateKeyInfo(rec.valueNode, ctx);
|
|
355
359
|
bag.keyDer = rec.valueNode.bytes;
|
|
356
360
|
return bag;
|
|
357
361
|
}
|
|
358
362
|
if (rec.bagId === OID_SHROUDED_KEY_BAG) {
|
|
359
363
|
bag.type = "pkcs8ShroudedKeyBag";
|
|
360
|
-
bag.encrypted = pkcs8.walkEncryptedPrivateKeyInfo(rec.valueNode);
|
|
364
|
+
bag.encrypted = pkcs8.walkEncryptedPrivateKeyInfo(rec.valueNode, ctx);
|
|
361
365
|
return bag;
|
|
362
366
|
}
|
|
363
367
|
if (rec.bagId === OID_CERT_BAG) {
|
package/lib/schema-pkcs8.js
CHANGED
|
@@ -193,8 +193,15 @@ function matches(root) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
/** @internal `ctx` is the walk context of the parse that reached this key, when another format
|
|
197
|
+
* carries it. Only its caps and budget cross over: the key is still read in this namespace, so a
|
|
198
|
+
* fault in it keeps the PKCS#8 error class and code. */
|
|
199
|
+
function walkPrivateKeyInfo(node, ctx) {
|
|
200
|
+
return schema.walk(PRIVATE_KEY_INFO, node, schema.withResources(NS, ctx)).result;
|
|
201
|
+
}
|
|
202
|
+
function walkEncryptedPrivateKeyInfo(node, ctx) {
|
|
203
|
+
return schema.walk(ENCRYPTED_PRIVATE_KEY_INFO, node, schema.withResources(NS, ctx)).result;
|
|
204
|
+
}
|
|
198
205
|
|
|
199
206
|
module.exports = {
|
|
200
207
|
parse: parse,
|
package/lib/schema-pkix.js
CHANGED
|
@@ -222,17 +222,16 @@ function _isPemArmor(buf) {
|
|
|
222
222
|
/** @internal The codec's resource refusals, as distinct from its malformed-input refusals. A
|
|
223
223
|
* caller who tightened a cap has to be able to tell "your bytes are wrong" from "your cap is
|
|
224
224
|
* too small", and only the second is answered by raising the cap. */
|
|
225
|
-
var
|
|
226
|
-
"asn1/too-large": 1, "asn1/too-deep": 1, "asn1/too-many-items": 1,
|
|
227
|
-
});
|
|
225
|
+
var _isResourceRefusal = schema.isResourceRefusal;
|
|
228
226
|
|
|
229
227
|
function decodeRoot(der, opts, caps, budget) {
|
|
228
|
+
// allow:bare-decode the root decode a door performs, which is where the caller's caps enter.
|
|
230
229
|
try { return asn1.decode(der, _decodeOptions(opts, caps)); }
|
|
231
230
|
catch (e) {
|
|
232
231
|
/** @internal `rawDecodeErrors` keeps the codec's own code for a malformed body, which some
|
|
233
232
|
* callers pin. A resource refusal is still translated, that code being new. */
|
|
234
|
-
if (opts.rawDecodeErrors && !(e
|
|
235
|
-
if (e
|
|
233
|
+
if (opts.rawDecodeErrors && !_isResourceRefusal(e)) throw e;
|
|
234
|
+
if (_isResourceRefusal(e)) {
|
|
236
235
|
if (budget) {
|
|
237
236
|
try {
|
|
238
237
|
budget.trip(opts.what || "the input");
|
|
@@ -338,12 +337,18 @@ var _emptyOptions = intrinsic.freeze(intrinsic.create(null));
|
|
|
338
337
|
function runParseWith(input, opts, callerOpts, build) {
|
|
339
338
|
var caps = parseCaps(callerOpts, opts);
|
|
340
339
|
var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
|
|
341
|
-
var ns = _nsWithBudget(opts.ns, budget);
|
|
340
|
+
var ns = _nsWithBudget(opts.ns, budget, caps);
|
|
342
341
|
var result;
|
|
343
342
|
try {
|
|
344
343
|
result = build(decodeRoot(coerceToDer(input, opts), opts, caps, budget), ns, caps, budget);
|
|
345
344
|
} catch (e) {
|
|
346
345
|
if (guard.limits.isBudgetExceeded(e)) throw _budgetRefusal(opts, e);
|
|
346
|
+
/** @internal A refusal that is already this door's own resource verdict has been translated
|
|
347
|
+
* once and carries the codec's refusal as its cause; wrapping it again would put a copy of
|
|
348
|
+
* the verdict where a caller looks for the cap that was exceeded. */
|
|
349
|
+
if (budget.exhausted() && !(e && e.code === opts.prefix + "/too-large")) {
|
|
350
|
+
throw _budgetRefusal(opts, e);
|
|
351
|
+
}
|
|
347
352
|
throw e;
|
|
348
353
|
}
|
|
349
354
|
if (budget.exhausted()) throw _budgetRefusal(opts, null);
|
|
@@ -357,17 +362,31 @@ function runParse(input, opts, callerOpts) {
|
|
|
357
362
|
}
|
|
358
363
|
|
|
359
364
|
/** @internal The re-read at the door is what makes the latch worth having: a catch anywhere
|
|
360
|
-
* under the walk may absorb the exception, and this still
|
|
365
|
+
* under the walk may absorb the exception or rewrap it as a domain error, and this still
|
|
366
|
+
* refuses. The rewrapped error is carried as the cause. */
|
|
361
367
|
function _budgetRefusal(opts, cause) {
|
|
362
368
|
return new opts.ErrorClass(opts.prefix + "/too-large",
|
|
363
369
|
(opts.what || "the input") + " exceeds the decode budget for this parse", cause || undefined);
|
|
364
370
|
}
|
|
365
371
|
|
|
366
|
-
function _nsWithBudget(ns, budget) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
372
|
+
function _nsWithBudget(ns, budget, caps) {
|
|
373
|
+
return schema.withResources(ns, { budget: budget, caps: caps });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @internal The walk context a hand-rolled door builds for the structures it reads out of its
|
|
377
|
+
* own input, so the caller's caps reach them the way `runParseWith` makes them reach a factory
|
|
378
|
+
* parser's. The door re-reads `budget.exhausted()` before it returns a result. */
|
|
379
|
+
function nestedContext(ns, caps) {
|
|
380
|
+
var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
|
|
381
|
+
return { ns: _nsWithBudget(ns, budget, caps), budget: budget };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** @internal The decode a format performs on a value it has already read out of its input: an
|
|
385
|
+
* embedded OCTET STRING payload, an encapsulated protocol body. The engine owns it, because
|
|
386
|
+
* `schema.embeddedDer` performs the same decode from inside a walk and the two must apply the
|
|
387
|
+
* caller's caps and trip the parse budget the same way. This is the name format modules call. */
|
|
388
|
+
function decodeNested(bytes, ctx, label, opts) {
|
|
389
|
+
return schema.decodeNested(bytes, ctx, label, opts);
|
|
371
390
|
}
|
|
372
391
|
|
|
373
392
|
var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
@@ -489,6 +508,7 @@ function attrValueToString(ns) {
|
|
|
489
508
|
throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must be a non-empty even run of hex digits (RFC 4514 sec. 2.4)");
|
|
490
509
|
}
|
|
491
510
|
var raw = guard.encoding.hex(hex, null, ns.E, ns.prefix + "/bad-atv", "a #hex attribute value");
|
|
511
|
+
// allow:bare-decode the encoder side, checking bytes a caller wrote by hand, outside any parse.
|
|
492
512
|
try { asn1.decode(raw); }
|
|
493
513
|
catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
|
|
494
514
|
return raw;
|
|
@@ -826,7 +846,7 @@ function decodeIssuingDistributionPointValue(ns, bytes, code) {
|
|
|
826
846
|
indirect: false, onlyAttr: false, fields: null };
|
|
827
847
|
var m;
|
|
828
848
|
try {
|
|
829
|
-
m = schema.walk(issuingDistributionPoint(code),
|
|
849
|
+
m = schema.walk(issuingDistributionPoint(code), schema.decodeNested(bytes, ns, "an IssuingDistributionPoint value"), ns);
|
|
830
850
|
if (m.fields.distributionPoint.present) {
|
|
831
851
|
var wrap = m.fields.distributionPoint.node;
|
|
832
852
|
if (!wrap.children || wrap.children.length !== 1) {
|
|
@@ -928,15 +948,20 @@ function userNoticeTexts(qualifier) {
|
|
|
928
948
|
return i === kids.length ? out : [];
|
|
929
949
|
}
|
|
930
950
|
|
|
951
|
+
/** @internal The readers an extension or attribute value is decoded through. `ctx` is the walk
|
|
952
|
+
* context of the parse that reached the value, when one did: the bytes here are an OCTET STRING
|
|
953
|
+
* payload the root decode stopped at, so this decode restarts depth and item counting and needs
|
|
954
|
+
* the caller's caps handed to it. A caller outside a parse passes none and takes the built-in
|
|
955
|
+
* ceilings, which is what `ns` carries. */
|
|
931
956
|
function _decodeHelpers(ns) {
|
|
932
|
-
function decodeTop(buf, code, what) {
|
|
957
|
+
function decodeTop(buf, code, what, ctx) {
|
|
933
958
|
var n;
|
|
934
|
-
try { n =
|
|
959
|
+
try { n = schema.decodeNested(buf, ctx || ns, what); }
|
|
935
960
|
catch (e) { throw ns.E(code, "malformed " + what + " value: " + ((e && e.message) || _String(e)), e); }
|
|
936
961
|
return n;
|
|
937
962
|
}
|
|
938
|
-
function seqChildren(buf, code, what) {
|
|
939
|
-
var n = decodeTop(buf, code, what);
|
|
963
|
+
function seqChildren(buf, code, what, ctx) {
|
|
964
|
+
var n = decodeTop(buf, code, what, ctx);
|
|
940
965
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.SEQUENCE || !n.children) {
|
|
941
966
|
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
|
|
942
967
|
}
|
|
@@ -1576,6 +1601,8 @@ module.exports = {
|
|
|
1576
1601
|
isCborResourceRefusal: isCborResourceRefusal,
|
|
1577
1602
|
cborParseOpts: cborParseOpts,
|
|
1578
1603
|
parseOpts: parseOpts,
|
|
1604
|
+
decodeNested: decodeNested,
|
|
1605
|
+
nestedContext: nestedContext,
|
|
1579
1606
|
makeNS: makeNS,
|
|
1580
1607
|
versionReader: versionReader,
|
|
1581
1608
|
DN_SHORT: DN_SHORT,
|
package/lib/schema-tsp.js
CHANGED
|
@@ -204,7 +204,7 @@ var TIME_STAMP_RESP = schema.seq([
|
|
|
204
204
|
schema.optional("timeStampToken", schema.any(), { whenUniversal: [TAGS.SEQUENCE] }),
|
|
205
205
|
], {
|
|
206
206
|
assert: "sequence", arity: { min: 1 }, code: "tsp/bad-response", what: "TimeStampResp",
|
|
207
|
-
build: function (m) {
|
|
207
|
+
build: function (m, ctx) {
|
|
208
208
|
var status = m.fields.status.value.result;
|
|
209
209
|
var present = m.fields.timeStampToken.present;
|
|
210
210
|
var granted = status.status === 0 || status.status === 1;
|
|
@@ -215,7 +215,9 @@ var TIME_STAMP_RESP = schema.seq([
|
|
|
215
215
|
status: status.status,
|
|
216
216
|
statusString: status.statusString,
|
|
217
217
|
failInfo: status.failInfo,
|
|
218
|
-
|
|
218
|
+
/** @internal The token is a second parse of bytes carried inside this one, so it takes the
|
|
219
|
+
* caps this door was given rather than the built-in ceilings. */
|
|
220
|
+
timeStampToken: present ? parseToken(m.fields.timeStampToken.value.bytes, ctx ? ctx.caps : undefined) : null,
|
|
219
221
|
};
|
|
220
222
|
},
|
|
221
223
|
});
|
|
@@ -424,12 +426,23 @@ function parseToken(input, callerOpts) {
|
|
|
424
426
|
if (!hasSigningCert) {
|
|
425
427
|
throw new TspError("tsp/missing-signing-certificate", "a TimeStampToken signerInfo must carry a SigningCertificate or SigningCertificateV2 signed attribute (RFC 3161 sec. 2.4.2, RFC 5816)");
|
|
426
428
|
}
|
|
429
|
+
/** @internal The TSTInfo is decoded out of the eContent the CMS layer handed back, so it is a
|
|
430
|
+
* second decode and takes the caller's caps the way the carrier did. The latch is re-read
|
|
431
|
+
* below, because the catch here answers in this domain and would otherwise report a cap that
|
|
432
|
+
* was too small as a malformed TSTInfo. */
|
|
433
|
+
var nested = pkix.nestedContext(NS, caps);
|
|
427
434
|
var tstInfo;
|
|
428
|
-
try { tstInfo = schema.embeddedDer(TST_INFO, encap.eContent,
|
|
435
|
+
try { tstInfo = schema.embeddedDer(TST_INFO, encap.eContent, nested.ns, { code: "tsp/bad-der", what: "the encapsulated TSTInfo" }); }
|
|
429
436
|
catch (e) {
|
|
437
|
+
if (nested.budget.exhausted()) {
|
|
438
|
+
throw new TspError("tsp/too-large", "the timeStampToken exceeds the decode budget for this parse", e);
|
|
439
|
+
}
|
|
430
440
|
if (e instanceof TspError) throw e;
|
|
431
441
|
throw new TspError("tsp/bad-der", "the encapsulated TSTInfo did not decode: " + ((e && e.message) || String(e)), e);
|
|
432
442
|
}
|
|
443
|
+
if (nested.budget.exhausted()) {
|
|
444
|
+
throw new TspError("tsp/too-large", "the timeStampToken exceeds the decode budget for this parse");
|
|
445
|
+
}
|
|
433
446
|
return { tstInfo: tstInfo.result, eContent: encap.eContent, signerInfo: signed.signerInfos[0], certificates: signed.certificates };
|
|
434
447
|
}
|
|
435
448
|
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:ca3c695a-64b2-4346-9529-6a6c719eb6bb",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-20T06:26:19.862Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.4",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.4",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.4",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.4",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|