@blamejs/pki 0.8.1 → 0.8.3
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 +47 -0
- package/README.md +9 -9
- package/lib/asn1-der.js +1 -1
- package/lib/cbor-det.js +1 -1
- package/lib/guard-limits.js +48 -1
- package/lib/guard-name.js +124 -1
- package/lib/guard-parsed.js +2 -2
- package/lib/guard-text.js +1 -1
- package/lib/oid.js +8 -3
- package/lib/pki-build.js +63 -6
- package/lib/schema-all.js +29 -18
- package/lib/schema-attrcert.js +16 -9
- package/lib/schema-c509.js +20 -4
- package/lib/schema-cmc.js +52 -16
- package/lib/schema-cmp.js +6 -1
- package/lib/schema-cms.js +6 -1
- package/lib/schema-crl.js +6 -1
- package/lib/schema-crmf.js +6 -1
- package/lib/schema-csr.js +6 -1
- package/lib/schema-csrattrs.js +6 -1
- package/lib/schema-ocsp.js +12 -2
- package/lib/schema-pkcs12.js +6 -1
- package/lib/schema-pkcs8.js +12 -2
- package/lib/schema-pkix.js +153 -6
- package/lib/schema-smime.js +12 -2
- package/lib/schema-tsp.js +38 -6
- package/lib/schema-x509.js +6 -1
- package/lib/x509-sign.js +100 -1
- package/package.json +4 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-pkcs12.js
CHANGED
|
@@ -396,7 +396,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
396
396
|
|
|
397
397
|
/**
|
|
398
398
|
* @primitive pki.schema.pkcs12.parse
|
|
399
|
-
* @signature pki.schema.pkcs12.parse(input) -> pfx
|
|
399
|
+
* @signature pki.schema.pkcs12.parse(input, caps?) -> pfx
|
|
400
400
|
* @since 0.1.18
|
|
401
401
|
* @status stable
|
|
402
402
|
* @spec RFC 7292, RFC 9579
|
|
@@ -441,6 +441,11 @@ function _buildBag(rec, state, ctx) {
|
|
|
441
441
|
* var der = await pki.pkcs12.build({ key: key, cert: cert }, { password: "changeit" });
|
|
442
442
|
* var store = pki.schema.pkcs12.parse(der);
|
|
443
443
|
* store.safeBags.map(function (b) { return b.type; });
|
|
444
|
+
* @opts
|
|
445
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
446
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
447
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
448
|
+
* refused with the `/too-large` code of this format.
|
|
444
449
|
*/
|
|
445
450
|
var parse = pkix.makeRecordingParser({
|
|
446
451
|
pemLabel: "PKCS12", PemError: PemError, ErrorClass: Pkcs12Error,
|
package/lib/schema-pkcs8.js
CHANGED
|
@@ -88,7 +88,7 @@ var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* @primitive pki.schema.pkcs8.parse
|
|
91
|
-
* @signature pki.schema.pkcs8.parse(input) -> privateKey
|
|
91
|
+
* @signature pki.schema.pkcs8.parse(input, caps?) -> privateKey
|
|
92
92
|
* @since 0.1.9
|
|
93
93
|
* @status stable
|
|
94
94
|
* @spec RFC 5208, RFC 5958
|
|
@@ -107,12 +107,17 @@ var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
|
|
|
107
107
|
* var key = pki.schema.pkcs8.parse(der);
|
|
108
108
|
* key.privateKeyAlgorithm.oid; // -> "1.3.101.112" (Ed25519)
|
|
109
109
|
* key.privateKey; // -> Buffer (the inner key encoding)
|
|
110
|
+
* @opts
|
|
111
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
112
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
113
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
114
|
+
* refused with the `/too-large` code of this format.
|
|
110
115
|
*/
|
|
111
116
|
var parse = pkix.makeParser({ pemLabel: "PRIVATE KEY", PemError: PemError, ErrorClass: Pkcs8Error, prefix: "pkcs8", what: "private key", topSchema: PRIVATE_KEY_INFO, ns: NS });
|
|
112
117
|
|
|
113
118
|
/**
|
|
114
119
|
* @primitive pki.schema.pkcs8.parseEncrypted
|
|
115
|
-
* @signature pki.schema.pkcs8.parseEncrypted(input) -> encrypted
|
|
120
|
+
* @signature pki.schema.pkcs8.parseEncrypted(input, caps?) -> encrypted
|
|
116
121
|
* @since 0.1.9
|
|
117
122
|
* @status stable
|
|
118
123
|
* @spec RFC 5958, RFC 5208
|
|
@@ -128,6 +133,11 @@ var parse = pkix.makeParser({ pemLabel: "PRIVATE KEY", PemError: PemError, Error
|
|
|
128
133
|
* var der = await pki.key.encrypt(await pki.key.export(pair.privateKey), "s3cr3t");
|
|
129
134
|
* var enc = pki.schema.pkcs8.parseEncrypted(der);
|
|
130
135
|
* enc.encryptionAlgorithm.oid; // -> "1.2.840.113549.1.5.13" (PBES2)
|
|
136
|
+
* @opts
|
|
137
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
138
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
139
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
140
|
+
* refused with the `/too-large` code of this format.
|
|
131
141
|
*/
|
|
132
142
|
var parseEncrypted = pkix.makeParser({ pemLabel: "ENCRYPTED PRIVATE KEY", PemError: PemError, ErrorClass: Pkcs8Error, prefix: "pkcs8", what: "encrypted private key", topSchema: ENCRYPTED_PRIVATE_KEY_INFO, ns: NS });
|
|
133
143
|
|
package/lib/schema-pkix.js
CHANGED
|
@@ -219,15 +219,155 @@ function _isPemArmor(buf) {
|
|
|
219
219
|
return true;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
/** @internal The codec's resource refusals, as distinct from its malformed-input refusals. A
|
|
223
|
+
* caller who tightened a cap has to be able to tell "your bytes are wrong" from "your cap is
|
|
224
|
+
* too small", and only the second is answered by raising the cap. */
|
|
225
|
+
var RESOURCE_REFUSALS = intrinsic.assign(intrinsic.create(null), {
|
|
226
|
+
"asn1/too-large": 1, "asn1/too-deep": 1, "asn1/too-many-items": 1,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
function decodeRoot(der, opts, caps, budget) {
|
|
230
|
+
try { return asn1.decode(der, _decodeOptions(opts, caps)); }
|
|
224
231
|
catch (e) {
|
|
232
|
+
/** @internal `rawDecodeErrors` keeps the codec's own code for a malformed body, which some
|
|
233
|
+
* callers pin. A resource refusal is still translated, that code being new. */
|
|
234
|
+
if (opts.rawDecodeErrors && !(e && RESOURCE_REFUSALS[e.code])) throw e;
|
|
235
|
+
if (e && RESOURCE_REFUSALS[e.code]) {
|
|
236
|
+
if (budget) {
|
|
237
|
+
try {
|
|
238
|
+
budget.trip(opts.what || "the input");
|
|
239
|
+
} catch (_b) {
|
|
240
|
+
/* allow:swallow-unverified the latch is the point here; the refusal below carries the cause */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
throw new opts.ErrorClass(opts.prefix + "/too-large",
|
|
244
|
+
(opts.what || "the input") + " exceeds the decode budget for this parse: " + (e.message || _String(e)), e);
|
|
245
|
+
}
|
|
225
246
|
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || _String(e)), e);
|
|
226
247
|
}
|
|
227
248
|
}
|
|
228
249
|
|
|
229
|
-
|
|
230
|
-
|
|
250
|
+
/** @internal A fresh options object per decode. The strictness a format declares and the caps
|
|
251
|
+
* a caller asked for are combined here and nowhere else, so neither can be written onto the
|
|
252
|
+
* other's object and outlive this call. */
|
|
253
|
+
function _decodeOptions(opts, caps) {
|
|
254
|
+
if (!caps) return opts.ber ? { ber: true } : undefined;
|
|
255
|
+
var out = { maxBytes: caps.maxBytes, maxDepth: caps.maxDepth, maxItems: caps.maxItems };
|
|
256
|
+
if (opts.ber) out.ber = true;
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** @internal The decode caps a caller may name, each paired with the `C.LIMITS` default that
|
|
261
|
+
* is also its ceiling. A cap may tighten and may not loosen: the defaults are the resource
|
|
262
|
+
* the toolkit is willing to spend, and a caller raising one past them would be granting
|
|
263
|
+
* themselves more than the built-in refusal allows. */
|
|
264
|
+
var PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
|
|
265
|
+
maxBytes: "DER_MAX_BYTES",
|
|
266
|
+
maxDepth: "DER_MAX_DEPTH",
|
|
267
|
+
maxItems: "DER_MAX_ITEMS",
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/** @internal A format that reads something other than DER declares its own ceilings through
|
|
271
|
+
* `opts.capLimits`, so the bound a caller is held to is the one their parser actually uses. */
|
|
272
|
+
var CBOR_PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
|
|
273
|
+
maxBytes: "CBOR_MAX_BYTES",
|
|
274
|
+
maxDepth: "CBOR_MAX_DEPTH",
|
|
275
|
+
maxItems: "CBOR_MAX_ITEMS",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
var CBOR_RESOURCE_REFUSALS = intrinsic.assign(intrinsic.create(null), {
|
|
279
|
+
"cbor/too-large": 1, "cbor/too-deep": 1, "cbor/too-many-items": 1,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
function isCborResourceRefusal(e) {
|
|
283
|
+
return !!(e && typeof e.code === "string" && CBOR_RESOURCE_REFUSALS[e.code] === 1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function cborParseOpts(ErrorClass, prefix, what) {
|
|
287
|
+
var out = intrinsic.create(null);
|
|
288
|
+
out.ErrorClass = ErrorClass;
|
|
289
|
+
out.prefix = prefix;
|
|
290
|
+
out.what = what;
|
|
291
|
+
out.capLimits = CBOR_PARSE_CAPS;
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** @internal A parser door's options, held on a prototype-less object so a computed lookup on
|
|
296
|
+
* it answers from what the format declared. Every hand-rolled door builds its options here
|
|
297
|
+
* rather than as a literal in its own module. */
|
|
298
|
+
function parseOpts(spec) {
|
|
299
|
+
var out = intrinsic.create(null);
|
|
300
|
+
_forEach(intrinsic.keys(spec), function (k) { out[k] = spec[k]; });
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** @internal Reads the caller's caps into a FRESH object. Nothing is written back onto what
|
|
305
|
+
* the caller passed, and nothing else is ever written onto the returned one, so a resource
|
|
306
|
+
* option cannot become a strictness flag on a shared object. */
|
|
307
|
+
function parseCaps(callerOpts, opts) {
|
|
308
|
+
var E = function (message) { return new opts.ErrorClass(opts.prefix + "/bad-input", message); };
|
|
309
|
+
if (callerOpts === undefined || callerOpts === null) callerOpts = _emptyOptions;
|
|
310
|
+
if (typeof callerOpts !== "object" || intrinsic.isArray(callerOpts) || guard.bytes.isByteSource(callerOpts)) {
|
|
311
|
+
throw E("parse options must be an object of decode caps");
|
|
312
|
+
}
|
|
313
|
+
var capLimits = opts.capLimits || PARSE_CAPS;
|
|
314
|
+
guard.identifier.assertKnownKeys(callerOpts, capLimits,
|
|
315
|
+
function (code, message) { return new opts.ErrorClass(code, message); },
|
|
316
|
+
opts.prefix + "/bad-input", "parse does not take the option ");
|
|
317
|
+
var caps = intrinsic.create(null);
|
|
318
|
+
_forEach(intrinsic.keys(capLimits), function (key) {
|
|
319
|
+
var ceiling = constants.LIMITS[capLimits[key]];
|
|
320
|
+
var asked = callerOpts[key];
|
|
321
|
+
if (asked === undefined) { caps[key] = ceiling; return; }
|
|
322
|
+
if (!intrinsic.isInteger(asked) || asked < 0) {
|
|
323
|
+
throw E(key + " must be a non-negative integer no greater than " + ceiling);
|
|
324
|
+
}
|
|
325
|
+
if (asked > ceiling) {
|
|
326
|
+
throw E(key + " " + asked + " is above the built-in ceiling " + ceiling + "; a cap may only tighten");
|
|
327
|
+
}
|
|
328
|
+
caps[key] = asked;
|
|
329
|
+
});
|
|
330
|
+
return caps;
|
|
331
|
+
}
|
|
332
|
+
var _emptyOptions = intrinsic.freeze(intrinsic.create(null));
|
|
333
|
+
|
|
334
|
+
/** @internal The door every format parser opens, whether it composes the shared factory or
|
|
335
|
+
* hand-rolls its walk. `build(rootNode, ns)` does the format's own work; everything around it
|
|
336
|
+
* is the part that must not be re-implemented per parser, because each re-implementation is
|
|
337
|
+
* another place the caller's caps can go missing or the latch can go unread. */
|
|
338
|
+
function runParseWith(input, opts, callerOpts, build) {
|
|
339
|
+
var caps = parseCaps(callerOpts, opts);
|
|
340
|
+
var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
|
|
341
|
+
var ns = _nsWithBudget(opts.ns, budget);
|
|
342
|
+
var result;
|
|
343
|
+
try {
|
|
344
|
+
result = build(decodeRoot(coerceToDer(input, opts), opts, caps, budget), ns, caps, budget);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
if (guard.limits.isBudgetExceeded(e)) throw _budgetRefusal(opts, e);
|
|
347
|
+
throw e;
|
|
348
|
+
}
|
|
349
|
+
if (budget.exhausted()) throw _budgetRefusal(opts, null);
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function runParse(input, opts, callerOpts) {
|
|
354
|
+
return runParseWith(input, opts, callerOpts, function (root, ns) {
|
|
355
|
+
return schema.walk(opts.topSchema, root, ns).result;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** @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 refuses. */
|
|
361
|
+
function _budgetRefusal(opts, cause) {
|
|
362
|
+
return new opts.ErrorClass(opts.prefix + "/too-large",
|
|
363
|
+
(opts.what || "the input") + " exceeds the decode budget for this parse", cause || undefined);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function _nsWithBudget(ns, budget) {
|
|
367
|
+
var out = intrinsic.create(null);
|
|
368
|
+
_forEach(intrinsic.keys(ns), function (k) { out[k] = ns[k]; });
|
|
369
|
+
out.budget = budget;
|
|
370
|
+
return out;
|
|
231
371
|
}
|
|
232
372
|
|
|
233
373
|
var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
@@ -403,7 +543,8 @@ function name(ns, opts) {
|
|
|
403
543
|
_forEach(rdnItem.value.items, function (atvItem) {
|
|
404
544
|
var a = atvItem.value.result;
|
|
405
545
|
_push(atvs, a);
|
|
406
|
-
var label = (a.name && _hasOwn(DN_SHORT, a.name) && DN_SHORT[a.name]) ||
|
|
546
|
+
var label = (a.name && _hasOwn(DN_SHORT, a.name) && DN_SHORT[a.name]) ||
|
|
547
|
+
(guard.name.isAttributeDescriptor(a.name) ? a.name : a.type);
|
|
407
548
|
_push(atvParts, label + "=" + _dnDisplayValue(a.value));
|
|
408
549
|
});
|
|
409
550
|
_push(rdns, atvs);
|
|
@@ -1355,7 +1496,7 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
1355
1496
|
}
|
|
1356
1497
|
|
|
1357
1498
|
function makeParser(opts) {
|
|
1358
|
-
return function (input) { return runParse(input, opts); };
|
|
1499
|
+
return function (input, callerOpts) { return runParse(input, opts, callerOpts); };
|
|
1359
1500
|
}
|
|
1360
1501
|
|
|
1361
1502
|
function makeRecordingParser(opts, kind) {
|
|
@@ -1429,6 +1570,12 @@ module.exports = {
|
|
|
1429
1570
|
isPemArmor: _isPemArmor,
|
|
1430
1571
|
decodeRoot: decodeRoot,
|
|
1431
1572
|
runParse: runParse,
|
|
1573
|
+
runParseWith: runParseWith,
|
|
1574
|
+
parseCaps: parseCaps,
|
|
1575
|
+
CBOR_PARSE_CAPS: CBOR_PARSE_CAPS,
|
|
1576
|
+
isCborResourceRefusal: isCborResourceRefusal,
|
|
1577
|
+
cborParseOpts: cborParseOpts,
|
|
1578
|
+
parseOpts: parseOpts,
|
|
1432
1579
|
makeNS: makeNS,
|
|
1433
1580
|
versionReader: versionReader,
|
|
1434
1581
|
DN_SHORT: DN_SHORT,
|
package/lib/schema-smime.js
CHANGED
|
@@ -187,7 +187,7 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* @primitive pki.schema.smime.parseSigningCertificate
|
|
190
|
-
* @signature pki.schema.smime.parseSigningCertificate(der) -> { certs, policies }
|
|
190
|
+
* @signature pki.schema.smime.parseSigningCertificate(der, caps?) -> { certs, policies }
|
|
191
191
|
* @since 0.1.22
|
|
192
192
|
* @status stable
|
|
193
193
|
* @spec RFC 5035, RFC 2634
|
|
@@ -207,6 +207,11 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
|
|
|
207
207
|
* var av = b.sequence([b.sequence([essCertId])]); // SigningCertificate { certs }
|
|
208
208
|
* var sc = pki.schema.smime.parseSigningCertificate(av);
|
|
209
209
|
* sc.certs[0].hashAlgorithm.name; // "sha1" (implied)
|
|
210
|
+
* @opts
|
|
211
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
212
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
213
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
214
|
+
* refused with the `/too-large` code of this format.
|
|
210
215
|
*/
|
|
211
216
|
var parseSigningCertificate = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SigningCertificate", topSchema: SIGNING_CERTIFICATE, ns: NS });
|
|
212
217
|
|
|
@@ -236,7 +241,7 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
|
|
|
236
241
|
|
|
237
242
|
/**
|
|
238
243
|
* @primitive pki.schema.smime.parseSmimeCapabilities
|
|
239
|
-
* @signature pki.schema.smime.parseSmimeCapabilities(der) -> { capabilities }
|
|
244
|
+
* @signature pki.schema.smime.parseSmimeCapabilities(der, caps?) -> { capabilities }
|
|
240
245
|
* @since 0.1.22
|
|
241
246
|
* @status stable
|
|
242
247
|
* @spec RFC 8551
|
|
@@ -253,6 +258,11 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
|
|
|
253
258
|
* var cap = b.sequence([b.oid(pki.oid.byName("aes256-CBC"))]);
|
|
254
259
|
* var caps = pki.schema.smime.parseSmimeCapabilities(b.sequence([cap]));
|
|
255
260
|
* caps.capabilities[0].name; // "aes256-CBC"
|
|
261
|
+
* @opts
|
|
262
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
263
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
264
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
265
|
+
* refused with the `/too-large` code of this format.
|
|
256
266
|
*/
|
|
257
267
|
var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SMIMECapabilities", topSchema: SMIME_CAPABILITIES, ns: NS });
|
|
258
268
|
|
package/lib/schema-tsp.js
CHANGED
|
@@ -46,6 +46,8 @@ var PemError = frameworkError.PemError;
|
|
|
46
46
|
|
|
47
47
|
var NS = pkix.makeNS("tsp", TspError, oid);
|
|
48
48
|
|
|
49
|
+
var _TOKEN_PARSE_OPTS = pkix.parseOpts({ ErrorClass: TspError, prefix: "tsp", what: "the timeStampToken" });
|
|
50
|
+
|
|
49
51
|
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
50
52
|
var EXTENSION = pkix.extension(NS);
|
|
51
53
|
|
|
@@ -250,7 +252,7 @@ var TIME_STAMP_REQ = schema.seq([
|
|
|
250
252
|
|
|
251
253
|
/**
|
|
252
254
|
* @primitive pki.schema.tsp.parseRequest
|
|
253
|
-
* @signature pki.schema.tsp.parseRequest(input) -> timeStampReq
|
|
255
|
+
* @signature pki.schema.tsp.parseRequest(input, caps?) -> timeStampReq
|
|
254
256
|
* @since 0.2.19
|
|
255
257
|
* @status stable
|
|
256
258
|
* @spec RFC 3161
|
|
@@ -268,12 +270,17 @@ var TIME_STAMP_REQ = schema.seq([
|
|
|
268
270
|
* var req = pki.schema.tsp.parseRequest(der);
|
|
269
271
|
* req.certReq; // -> boolean
|
|
270
272
|
* req.messageImprint.hashedMessage; // -> Buffer (the raw digest)
|
|
273
|
+
* @opts
|
|
274
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
275
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
276
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
277
|
+
* refused with the `/too-large` code of this format.
|
|
271
278
|
*/
|
|
272
279
|
var parseRequest = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: TspError, prefix: "tsp", what: "TimeStampReq", topSchema: TIME_STAMP_REQ, ns: NS });
|
|
273
280
|
|
|
274
281
|
/**
|
|
275
282
|
* @primitive pki.schema.tsp.parseTstInfo
|
|
276
|
-
* @signature pki.schema.tsp.parseTstInfo(input) -> tstInfo
|
|
283
|
+
* @signature pki.schema.tsp.parseTstInfo(input, caps?) -> tstInfo
|
|
277
284
|
* @since 0.1.13
|
|
278
285
|
* @status stable
|
|
279
286
|
* @spec RFC 3161
|
|
@@ -299,12 +306,17 @@ var parseRequest = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorCl
|
|
|
299
306
|
* var tst = pki.schema.tsp.parseTstInfo(der);
|
|
300
307
|
* tst.genTime; // -> Date
|
|
301
308
|
* tst.messageImprint.hashedMessage; // -> Buffer (the raw digest)
|
|
309
|
+
* @opts
|
|
310
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
311
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
312
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
313
|
+
* refused with the `/too-large` code of this format.
|
|
302
314
|
*/
|
|
303
315
|
var parseTstInfo = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: TspError, prefix: "tsp", what: "TSTInfo", topSchema: TST_INFO, ns: NS });
|
|
304
316
|
|
|
305
317
|
/**
|
|
306
318
|
* @primitive pki.schema.tsp.parse
|
|
307
|
-
* @signature pki.schema.tsp.parse(input) -> timeStampResp
|
|
319
|
+
* @signature pki.schema.tsp.parse(input, caps?) -> timeStampResp
|
|
308
320
|
* @since 0.1.13
|
|
309
321
|
* @status stable
|
|
310
322
|
* @spec RFC 3161
|
|
@@ -329,12 +341,17 @@ var parseTstInfo = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorCl
|
|
|
329
341
|
* var res = pki.schema.tsp.parse(der);
|
|
330
342
|
* res.status; // -> 0 (granted)
|
|
331
343
|
* res.timeStampToken.tstInfo.genTime; // -> Date (a granted token is decoded)
|
|
344
|
+
* @opts
|
|
345
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
346
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
347
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
348
|
+
* refused with the `/too-large` code of this format.
|
|
332
349
|
*/
|
|
333
350
|
var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: TspError, prefix: "tsp", what: "TimeStampResp", topSchema: TIME_STAMP_RESP, ns: NS });
|
|
334
351
|
|
|
335
352
|
/**
|
|
336
353
|
* @primitive pki.schema.tsp.parseToken
|
|
337
|
-
* @signature pki.schema.tsp.parseToken(input) -> tstInfo
|
|
354
|
+
* @signature pki.schema.tsp.parseToken(input, caps?) -> tstInfo
|
|
338
355
|
* @since 0.1.13
|
|
339
356
|
* @status stable
|
|
340
357
|
* @spec RFC 3161, RFC 5652
|
|
@@ -364,13 +381,28 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
|
|
|
364
381
|
* var token = pki.schema.tsp.parseToken(tokenDer);
|
|
365
382
|
* token.tstInfo.genTime; // -> Date
|
|
366
383
|
* token.signerInfo.sid; // -> the TSA signer identifier
|
|
384
|
+
* @opts
|
|
385
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
386
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
387
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
388
|
+
* refused with the `/too-large` code of this format.
|
|
367
389
|
*/
|
|
368
|
-
function parseToken(input) {
|
|
390
|
+
function parseToken(input, callerOpts) {
|
|
391
|
+
/** @internal The caller's options are this door's to answer for, so they are read here rather
|
|
392
|
+
* than inside the CMS parser: a complaint about an option must not come back wearing the
|
|
393
|
+
* delegate's verdict about the content. The snapshot travels onward, because handing the
|
|
394
|
+
* caller's own object to the CMS parser would read each cap a second time. */
|
|
395
|
+
var caps = pkix.parseCaps(callerOpts, _TOKEN_PARSE_OPTS);
|
|
369
396
|
var der = pkix.coerceToDer(input, { pemLabel: null, PemError: PemError, ErrorClass: TspError, prefix: "tsp" });
|
|
370
397
|
var signed;
|
|
371
|
-
try { signed = cms.parse(der); }
|
|
398
|
+
try { signed = cms.parse(der, caps); }
|
|
372
399
|
catch (e) {
|
|
373
400
|
if (e instanceof TspError) throw e;
|
|
401
|
+
/** @internal A resource refusal says the cap was too small, not that the token is malformed,
|
|
402
|
+
* so it keeps that meaning rather than becoming a statement about the bytes. */
|
|
403
|
+
if (e && typeof e.code === "string" && e.code.indexOf("/too-large") !== -1) {
|
|
404
|
+
throw new TspError("tsp/too-large", "the timeStampToken exceeds the decode budget for this parse: " + (e.message || ""), e);
|
|
405
|
+
}
|
|
374
406
|
throw new TspError("tsp/bad-token", "the timeStampToken did not decode as a CMS SignedData: " + ((e && e.message) || String(e)), e);
|
|
375
407
|
}
|
|
376
408
|
if (signed.contentTypeName !== "signedData") {
|
package/lib/schema-x509.js
CHANGED
|
@@ -165,7 +165,7 @@ var CERTIFICATE = pkix.signedEnvelope(NS, CERTIFICATE_TBS, {
|
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* @primitive pki.schema.x509.parse
|
|
168
|
-
* @signature pki.schema.x509.parse(input) -> certificate
|
|
168
|
+
* @signature pki.schema.x509.parse(input, caps?) -> certificate
|
|
169
169
|
* @since 0.1.7
|
|
170
170
|
* @status stable
|
|
171
171
|
* @spec RFC 5280, X.509
|
|
@@ -194,6 +194,11 @@ var CERTIFICATE = pkix.signedEnvelope(NS, CERTIFICATE_TBS, {
|
|
|
194
194
|
* cert.subject.dn; // "CN=example.com, O=Example"
|
|
195
195
|
* cert.validity.notAfter; // Date
|
|
196
196
|
* cert.signatureAlgorithm.name; // "Ed25519" (the algorithm the issuer signed with)
|
|
197
|
+
* @opts
|
|
198
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
199
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
200
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
201
|
+
* refused with the `/too-large` code of this format.
|
|
197
202
|
*/
|
|
198
203
|
var parse = pkix.makeRecordingParser({ pemLabel: "CERTIFICATE", PemError: PemError, ErrorClass: CertificateError, prefix: "x509", what: "certificate", topSchema: CERTIFICATE, ns: NS }, "certificate");
|
|
199
204
|
|
package/lib/x509-sign.js
CHANGED
|
@@ -23,6 +23,9 @@ var signScheme = require("./sign-scheme");
|
|
|
23
23
|
var guard = require("./guard-all");
|
|
24
24
|
var intrinsic = require("./guard-intrinsic");
|
|
25
25
|
var _isArray = intrinsic.isArray, _bufferEquals = intrinsic.bufferEquals;
|
|
26
|
+
var _dnCharCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
27
|
+
var _dnKeys = intrinsic.keys;
|
|
28
|
+
var _dnHasOwn = intrinsic.hasOwn;
|
|
26
29
|
var frameworkError = require("./framework-error");
|
|
27
30
|
var schema = require("./schema-engine");
|
|
28
31
|
var pkix = require("./schema-pkix");
|
|
@@ -788,4 +791,100 @@ function _extension(name, value, opts) {
|
|
|
788
791
|
return _ext(row.oid, critical, valueDer);
|
|
789
792
|
}
|
|
790
793
|
|
|
791
|
-
|
|
794
|
+
/**
|
|
795
|
+
* @primitive pki.x509.parseDn
|
|
796
|
+
* @signature pki.x509.parseDn(dn) -> { rdns, dn, bytes }
|
|
797
|
+
* @since 0.8.2
|
|
798
|
+
* @status stable
|
|
799
|
+
* @spec RFC 4514
|
|
800
|
+
* @related pki.x509.sign, pki.schema.x509.parse
|
|
801
|
+
*
|
|
802
|
+
* Read a distinguished-name string into the `Name` a builder takes, using the attribute names
|
|
803
|
+
* and value escaping of RFC 4514 sec. 2.4 and sec. 3. It is the inverse of the `dn` string
|
|
804
|
+
* every parsed name carries: `bytes` is the encoded `Name`, ready to hand to `pki.x509.sign`
|
|
805
|
+
* as a `subject` or `issuer`, and `dn` is the string re-emitted from what was read, which for
|
|
806
|
+
* a string this toolkit produced is the string it was given.
|
|
807
|
+
*
|
|
808
|
+
* Components are read in the order they are written, so the first one written is the first
|
|
809
|
+
* element of the encoded `Name`. That is the order every `dn` here is emitted in, and the
|
|
810
|
+
* order `openssl x509 -subject` prints. RFC 4514 sec. 2.1 specifies the opposite order, so a
|
|
811
|
+
* string from a directory tool that follows it names the components from last to first;
|
|
812
|
+
* reverse them before parsing one.
|
|
813
|
+
*
|
|
814
|
+
* The string form does not carry the ASN.1 string type of each value, so a parsed value takes
|
|
815
|
+
* the type this toolkit encodes that attribute with: `PrintableString` for `countryName`,
|
|
816
|
+
* `IA5String` for `emailAddress`, `UTF8String` otherwise. A name written by something that
|
|
817
|
+
* chose different types will re-encode to different bytes, which is why RFC 4514 sec. 2.4
|
|
818
|
+
* defines the `#`-prefixed hex form: a value given that way is the exact DER of the
|
|
819
|
+
* `AttributeValue` and round-trips byte for byte.
|
|
820
|
+
*
|
|
821
|
+
* Malformed input throws `x509/bad-dn`. A non-string throws `x509/bad-input`.
|
|
822
|
+
*
|
|
823
|
+
* `bytes` is raw `Name` DER, the form `spec.subject` and `spec.issuer` take alongside a string
|
|
824
|
+
* or an array of RDNs, so a name read from a string can be signed without rebuilding it.
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* var name = pki.x509.parseDn("C=US, O=Example Inc, CN=leaf");
|
|
828
|
+
* name.rdns.length; // -> 3
|
|
829
|
+
* name.rdns[0][0].name; // -> "countryName"
|
|
830
|
+
* name.dn; // -> "C=US, O=Example Inc, CN=leaf"
|
|
831
|
+
* Buffer.isBuffer(name.bytes); // -> true
|
|
832
|
+
*/
|
|
833
|
+
function parseDn(dn) {
|
|
834
|
+
if (typeof dn !== "string") throw _err("x509/bad-input", "pki.x509.parseDn takes a distinguished-name string");
|
|
835
|
+
var components = guard.name.parseDnString(dn, _err, "x509/bad-dn", "the distinguished name");
|
|
836
|
+
var spec = [];
|
|
837
|
+
for (var i = 0; i < components.length; i++) {
|
|
838
|
+
var rdn = components[i], out = intrinsic.create(null);
|
|
839
|
+
for (var j = 0; j < rdn.length; j++) {
|
|
840
|
+
var atv = rdn[j];
|
|
841
|
+
var attrName;
|
|
842
|
+
try { attrName = _dnAttrName(atv.type); }
|
|
843
|
+
catch (e) {
|
|
844
|
+
if (e instanceof CertificateError && e.code === "x509/bad-dn") throw e;
|
|
845
|
+
throw _err("x509/bad-dn", "the distinguished name has an attribute type that does not resolve: " + guard.text.showValue(atv.type), e);
|
|
846
|
+
}
|
|
847
|
+
if (_dnHasOwn(out, attrName)) {
|
|
848
|
+
throw _err("x509/bad-dn", "the distinguished name repeats " + attrName + " inside one relative distinguished name");
|
|
849
|
+
}
|
|
850
|
+
out[attrName] = atv.hex === null ? _dnLiteralValue(atv.value) : "#" + atv.hex;
|
|
851
|
+
}
|
|
852
|
+
guard.list.append(spec, out);
|
|
853
|
+
}
|
|
854
|
+
var bytes, read;
|
|
855
|
+
try {
|
|
856
|
+
bytes = _b.encodeName(spec);
|
|
857
|
+
read = schema.walk(NAME_SCHEMA, asn1.decode(bytes), NS).result;
|
|
858
|
+
} catch (e) {
|
|
859
|
+
throw _err("x509/bad-dn", "the distinguished name does not encode: " + (e && e.message), e);
|
|
860
|
+
}
|
|
861
|
+
return { rdns: read.rdns, dn: read.dn, bytes: bytes };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function _dnAttrName(type) {
|
|
865
|
+
if (oid.isDottedDecimal(type)) {
|
|
866
|
+
var byOid = oid.name(type);
|
|
867
|
+
if (!byOid) throw _err("x509/bad-dn", "the distinguished name names an unregistered attribute OID " + type);
|
|
868
|
+
return byOid;
|
|
869
|
+
}
|
|
870
|
+
var wanted = guard.name.lowerAscii(type);
|
|
871
|
+
var shortNames = pkix.DN_SHORT;
|
|
872
|
+
var keys = _dnKeys(shortNames);
|
|
873
|
+
for (var i = 0; i < keys.length; i++) {
|
|
874
|
+
if (guard.name.lowerAscii(shortNames[keys[i]]) === wanted || guard.name.lowerAscii(keys[i]) === wanted) return keys[i];
|
|
875
|
+
}
|
|
876
|
+
var table = oid.all();
|
|
877
|
+
var dotted = _dnKeys(table);
|
|
878
|
+
for (var j = 0; j < dotted.length; j++) {
|
|
879
|
+
var registered = table[dotted[j]];
|
|
880
|
+
if (guard.name.lowerAscii(registered) === wanted) return registered;
|
|
881
|
+
}
|
|
882
|
+
throw _err("x509/bad-dn", "the distinguished name names an unknown attribute " + guard.text.showValue(type));
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function _dnLiteralValue(value) {
|
|
886
|
+
var first = _dnCharCodeAt(value, 0);
|
|
887
|
+
return (first === 0x23 || first === 0x5c) ? "\\" + value : value;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
module.exports = { sign: sign, randomSerial: randomSerial, extension: extension, parseDn: parseDn };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/pki",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "blamejs contributors",
|
|
@@ -68,7 +68,9 @@
|
|
|
68
68
|
"test": "node test/smoke.js",
|
|
69
69
|
"lint": "eslint --max-warnings 0 .",
|
|
70
70
|
"fuzz": "npm ci --prefix fuzz && npx --prefix fuzz jazzer fuzz/asn1-der.fuzz.js -- -max_total_time=60",
|
|
71
|
-
"gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js && node scripts/check-spelling-consistency.js && node scripts/check-engine-floor.js",
|
|
71
|
+
"gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js && node scripts/check-spelling-consistency.js && node scripts/check-engine-floor.js && node scripts/check-cve-vectors.js && node scripts/check-rfc-currency.js",
|
|
72
|
+
"check:cve-vectors": "node scripts/check-cve-vectors.js",
|
|
73
|
+
"check:rfc-currency": "node scripts/check-rfc-currency.js",
|
|
72
74
|
"check:engine-floor": "node scripts/check-engine-floor.js",
|
|
73
75
|
"check:spelling": "node scripts/check-spelling-consistency.js",
|
|
74
76
|
"coverage": "c8 --include=lib/** --include=index.js --reporter=text-summary --reporter=lcov node test/smoke.js",
|
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:9e704e55-ccb2-4ecb-9370-04d6f58aa807",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-20T04:03:01.349Z",
|
|
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.3",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.3",
|
|
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.3",
|
|
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.3",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|