@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/lib/schema-ocsp.js
CHANGED
|
@@ -98,9 +98,9 @@ function _rawSignature(field) {
|
|
|
98
98
|
var CERTS = schema.seqOf(certificateBytes(), { assert: "sequence", code: "ocsp/bad-certs", what: "certs", max: constants.LIMITS.OCSP_MAX_CERTS, maxCode: "ocsp/too-many-certs" });
|
|
99
99
|
|
|
100
100
|
/** @internal RFC 6960 sec. 4.4.4: ArchiveCutoff ::= GeneralizedTime, carried as a singleExtension. */
|
|
101
|
-
function _readArchiveCutoff(ext) {
|
|
101
|
+
function _readArchiveCutoff(ext, ctx) {
|
|
102
102
|
var node;
|
|
103
|
-
try { node =
|
|
103
|
+
try { node = pkix.decodeNested(ext.value, ctx, "an archiveCutoff extension value"); }
|
|
104
104
|
catch (e) { throw NS.E("ocsp/bad-archive-cutoff", "the archiveCutoff extension value did not decode: " + ((e && e.message) || _String(e)), e); }
|
|
105
105
|
if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.GENERALIZED_TIME)) {
|
|
106
106
|
throw NS.E("ocsp/bad-archive-cutoff", "the archiveCutoff extension value must be a GeneralizedTime (RFC 6960 sec. 4.4.4)");
|
|
@@ -112,10 +112,10 @@ function _readArchiveCutoff(ext) {
|
|
|
112
112
|
/** @internal RFC 6960 sec. 4.4.2: CrlID ::= SEQUENCE { crlUrl [0] EXPLICIT IA5String OPTIONAL,
|
|
113
113
|
* crlNum [1] EXPLICIT INTEGER OPTIONAL, crlTime [2] EXPLICIT GeneralizedTime OPTIONAL }. Every
|
|
114
114
|
* field is optional and each may appear at most once, in tag order. */
|
|
115
|
-
function _readCrlId(ext) {
|
|
115
|
+
function _readCrlId(ext, ctx) {
|
|
116
116
|
var C = "ocsp/bad-crl-id";
|
|
117
117
|
var node;
|
|
118
|
-
try { node =
|
|
118
|
+
try { node = pkix.decodeNested(ext.value, ctx, "a crlID extension value"); }
|
|
119
119
|
catch (e) { throw NS.E(C, "the CRL references extension value did not decode: " + ((e && e.message) || _String(e)), e); }
|
|
120
120
|
if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.SEQUENCE && node.children)) {
|
|
121
121
|
throw NS.E(C, "CrlID must be a SEQUENCE (RFC 6960 sec. 4.4.2)");
|
|
@@ -154,15 +154,15 @@ function _readCrlId(ext) {
|
|
|
154
154
|
/** @internal The extensions this profile reads are decoded onto their entry beside the raw value,
|
|
155
155
|
* so a consumer gets the value by name and a malformed one is refused here rather than passed
|
|
156
156
|
* through as opaque bytes. The nonce is RFC 9654; the other two are RFC 6960 sec. 4.4. */
|
|
157
|
-
function _validateOcspExtensions(list) {
|
|
157
|
+
function _validateOcspExtensions(list, ctx) {
|
|
158
158
|
if (!list) return list;
|
|
159
159
|
for (var i = 0; i < list.length; i++) {
|
|
160
160
|
var ext = list[i];
|
|
161
|
-
if (ext.oid === OID_OCSP_ARCHIVE_CUTOFF) { ext.archiveCutoff = _readArchiveCutoff(ext); continue; }
|
|
162
|
-
if (ext.oid === OID_OCSP_CRL) { ext.crlReferences = _readCrlId(ext); continue; }
|
|
161
|
+
if (ext.oid === OID_OCSP_ARCHIVE_CUTOFF) { ext.archiveCutoff = _readArchiveCutoff(ext, ctx); continue; }
|
|
162
|
+
if (ext.oid === OID_OCSP_CRL) { ext.crlReferences = _readCrlId(ext, ctx); continue; }
|
|
163
163
|
if (ext.oid !== OID_OCSP_NONCE) continue;
|
|
164
164
|
var node;
|
|
165
|
-
try { node =
|
|
165
|
+
try { node = pkix.decodeNested(ext.value, ctx, "a nonce extension value"); }
|
|
166
166
|
catch (e) { throw NS.E("ocsp/bad-nonce", "the nonce extension value did not decode: " + ((e && e.message) || String(e)), e); }
|
|
167
167
|
if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.OCTET_STRING && node.content)) {
|
|
168
168
|
throw NS.E("ocsp/bad-nonce", "the nonce extension value must be an OCTET STRING (RFC 9654 sec. 2.1)");
|
|
@@ -244,13 +244,13 @@ var SINGLE_RESPONSE = schema.seq([
|
|
|
244
244
|
], { minTag: 0, maxTag: 1, unexpectedCode: "ocsp/bad-single-response", orderCode: "ocsp/bad-single-response" }),
|
|
245
245
|
], {
|
|
246
246
|
assert: "sequence", code: "ocsp/bad-single-response", what: "SingleResponse",
|
|
247
|
-
build: function (m) {
|
|
247
|
+
build: function (m, ctx) {
|
|
248
248
|
return {
|
|
249
249
|
certID: m.fields.certID.value.result,
|
|
250
250
|
certStatus: _shapeCertStatus(m.fields.certStatus),
|
|
251
251
|
thisUpdate: m.fields.thisUpdate.value,
|
|
252
252
|
nextUpdate: m.fields.nextUpdate.present ? m.fields.nextUpdate.value : null,
|
|
253
|
-
singleExtensions: m.fields.singleExtensions.present ? _validateOcspExtensions(m.fields.singleExtensions.value.result) : null,
|
|
253
|
+
singleExtensions: m.fields.singleExtensions.present ? _validateOcspExtensions(m.fields.singleExtensions.value.result, ctx) : null,
|
|
254
254
|
};
|
|
255
255
|
},
|
|
256
256
|
});
|
|
@@ -277,13 +277,13 @@ var RESPONSE_DATA = schema.seq([
|
|
|
277
277
|
], { minTag: 1, maxTag: 1, unexpectedCode: "ocsp/bad-response-data", orderCode: "ocsp/bad-response-data" }),
|
|
278
278
|
], {
|
|
279
279
|
assert: "sequence", code: "ocsp/bad-response-data", what: "ResponseData",
|
|
280
|
-
build: function (m) {
|
|
280
|
+
build: function (m, ctx) {
|
|
281
281
|
return {
|
|
282
282
|
version: m.fields.version.value,
|
|
283
283
|
responderID: _shapeResponderID(m.fields.responderID),
|
|
284
284
|
producedAt: m.fields.producedAt.value,
|
|
285
285
|
responses: m.fields.responses.value.items.map(function (it) { return it.value.result; }),
|
|
286
|
-
responseExtensions: m.fields.responseExtensions.present ? _validateOcspExtensions(m.fields.responseExtensions.value.result) : null,
|
|
286
|
+
responseExtensions: m.fields.responseExtensions.present ? _validateOcspExtensions(m.fields.responseExtensions.value.result, ctx) : null,
|
|
287
287
|
};
|
|
288
288
|
},
|
|
289
289
|
});
|
|
@@ -362,10 +362,10 @@ var REQUEST = schema.seq([
|
|
|
362
362
|
], { minTag: 0, maxTag: 0, unexpectedCode: "ocsp/bad-request", orderCode: "ocsp/bad-request" }),
|
|
363
363
|
], {
|
|
364
364
|
assert: "sequence", code: "ocsp/bad-request", what: "Request",
|
|
365
|
-
build: function (m) {
|
|
365
|
+
build: function (m, ctx) {
|
|
366
366
|
return {
|
|
367
367
|
certID: m.fields.reqCert.value.result,
|
|
368
|
-
singleRequestExtensions: m.fields.singleRequestExtensions.present ? _validateOcspExtensions(m.fields.singleRequestExtensions.value.result) : null,
|
|
368
|
+
singleRequestExtensions: m.fields.singleRequestExtensions.present ? _validateOcspExtensions(m.fields.singleRequestExtensions.value.result, ctx) : null,
|
|
369
369
|
};
|
|
370
370
|
},
|
|
371
371
|
});
|
|
@@ -396,13 +396,13 @@ var TBS_REQUEST = schema.seq([
|
|
|
396
396
|
], { minTag: 2, maxTag: 2, unexpectedCode: "ocsp/bad-tbs-request", orderCode: "ocsp/bad-tbs-request" }),
|
|
397
397
|
], {
|
|
398
398
|
assert: "sequence", code: "ocsp/bad-tbs-request", what: "TBSRequest",
|
|
399
|
-
build: function (m) {
|
|
399
|
+
build: function (m, ctx) {
|
|
400
400
|
var rn = m.fields.requestorName;
|
|
401
401
|
return {
|
|
402
402
|
version: m.fields.version.value,
|
|
403
403
|
requestorName: rn.present ? { bytes: rn.value.bytes, tagClass: rn.value.tagClass, tagNumber: rn.value.tagNumber, value: rn.value.value } : null,
|
|
404
404
|
requestList: m.fields.requestList.value.items.map(function (it) { return it.value.result; }),
|
|
405
|
-
requestExtensions: m.fields.requestExtensions.present ? _validateOcspExtensions(m.fields.requestExtensions.value.result) : null,
|
|
405
|
+
requestExtensions: m.fields.requestExtensions.present ? _validateOcspExtensions(m.fields.requestExtensions.value.result, ctx) : null,
|
|
406
406
|
};
|
|
407
407
|
},
|
|
408
408
|
});
|
|
@@ -431,7 +431,7 @@ var OCSP_REQUEST = schema.seq([
|
|
|
431
431
|
|
|
432
432
|
/**
|
|
433
433
|
* @primitive pki.schema.ocsp.parseRequest
|
|
434
|
-
* @signature pki.schema.ocsp.parseRequest(input) -> ocspRequest
|
|
434
|
+
* @signature pki.schema.ocsp.parseRequest(input, caps?) -> ocspRequest
|
|
435
435
|
* @since 0.1.11
|
|
436
436
|
* @status stable
|
|
437
437
|
* @spec RFC 6960
|
|
@@ -458,12 +458,17 @@ var OCSP_REQUEST = schema.seq([
|
|
|
458
458
|
* var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, {});
|
|
459
459
|
* var req = pki.schema.ocsp.parseRequest(der);
|
|
460
460
|
* req.requestList[0].certID.serialNumberHex; // -> "1332"
|
|
461
|
+
* @opts
|
|
462
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
463
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
464
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
465
|
+
* refused with the `/too-large` code of this format.
|
|
461
466
|
*/
|
|
462
467
|
var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemError, ErrorClass: OcspError, prefix: "ocsp", what: "OCSP request", topSchema: OCSP_REQUEST, ns: NS });
|
|
463
468
|
|
|
464
469
|
/**
|
|
465
470
|
* @primitive pki.schema.ocsp.parseResponse
|
|
466
|
-
* @signature pki.schema.ocsp.parseResponse(input) -> ocspResponse
|
|
471
|
+
* @signature pki.schema.ocsp.parseResponse(input, caps?) -> ocspResponse
|
|
467
472
|
* @since 0.1.11
|
|
468
473
|
* @status stable
|
|
469
474
|
* @spec RFC 6960
|
|
@@ -495,6 +500,11 @@ var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemErro
|
|
|
495
500
|
* var res = pki.schema.ocsp.parseResponse(der);
|
|
496
501
|
* res.responseStatus.name; // -> "successful"
|
|
497
502
|
* res.basicResponse.responses[0].certStatus.type; // -> "good" | "revoked" | "unknown"
|
|
503
|
+
* @opts
|
|
504
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
505
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
506
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
507
|
+
* refused with the `/too-large` code of this format.
|
|
498
508
|
*/
|
|
499
509
|
var parseResponse = pkix.makeRecordingParser({ pemLabel: "OCSP RESPONSE", PemError: PemError, ErrorClass: OcspError, prefix: "ocsp", what: "OCSP response", topSchema: OCSP_RESPONSE, ns: NS }, "ocspResponse");
|
|
500
510
|
|
|
@@ -582,6 +592,7 @@ module.exports = {
|
|
|
582
592
|
* reads. Throws the parser's own ocsp/* code for a malformed entry. */
|
|
583
593
|
readSingleExtensionsDer: function (derList) {
|
|
584
594
|
var node;
|
|
595
|
+
// allow:bare-decode reached from the signer, not from a parse, so no parse context carries caps here.
|
|
585
596
|
try { node = asn1.decode(asn1.build.sequence(intrinsic.map(derList, function (d) { return asn1.build.raw(d); }))); }
|
|
586
597
|
catch (e) { throw NS.E("ocsp/bad-single-response", "a pre-encoded singleExtension is not DER", e); }
|
|
587
598
|
return _validateOcspExtensions(schema.walk(EXTENSIONS, node, NS).result);
|
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) {
|
|
@@ -396,7 +400,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
396
400
|
|
|
397
401
|
/**
|
|
398
402
|
* @primitive pki.schema.pkcs12.parse
|
|
399
|
-
* @signature pki.schema.pkcs12.parse(input) -> pfx
|
|
403
|
+
* @signature pki.schema.pkcs12.parse(input, caps?) -> pfx
|
|
400
404
|
* @since 0.1.18
|
|
401
405
|
* @status stable
|
|
402
406
|
* @spec RFC 7292, RFC 9579
|
|
@@ -441,6 +445,11 @@ function _buildBag(rec, state, ctx) {
|
|
|
441
445
|
* var der = await pki.pkcs12.build({ key: key, cert: cert }, { password: "changeit" });
|
|
442
446
|
* var store = pki.schema.pkcs12.parse(der);
|
|
443
447
|
* store.safeBags.map(function (b) { return b.type; });
|
|
448
|
+
* @opts
|
|
449
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
450
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
|
|
451
|
+
* above it, or an option outside this set, is refused. A parse that exceeds one is
|
|
452
|
+
* refused with the `/too-large` code of this format.
|
|
444
453
|
*/
|
|
445
454
|
var parse = pkix.makeRecordingParser({
|
|
446
455
|
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
|
|
|
@@ -183,8 +193,15 @@ function matches(root) {
|
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
}
|
|
188
205
|
|
|
189
206
|
module.exports = {
|
|
190
207
|
parse: parse,
|
package/lib/schema-pkix.js
CHANGED
|
@@ -219,15 +219,174 @@ 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 _isResourceRefusal = schema.isResourceRefusal;
|
|
226
|
+
|
|
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.
|
|
229
|
+
try { return asn1.decode(der, _decodeOptions(opts, caps)); }
|
|
224
230
|
catch (e) {
|
|
231
|
+
/** @internal `rawDecodeErrors` keeps the codec's own code for a malformed body, which some
|
|
232
|
+
* callers pin. A resource refusal is still translated, that code being new. */
|
|
233
|
+
if (opts.rawDecodeErrors && !_isResourceRefusal(e)) throw e;
|
|
234
|
+
if (_isResourceRefusal(e)) {
|
|
235
|
+
if (budget) {
|
|
236
|
+
try {
|
|
237
|
+
budget.trip(opts.what || "the input");
|
|
238
|
+
} catch (_b) {
|
|
239
|
+
/* allow:swallow-unverified the latch is the point here; the refusal below carries the cause */
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
throw new opts.ErrorClass(opts.prefix + "/too-large",
|
|
243
|
+
(opts.what || "the input") + " exceeds the decode budget for this parse: " + (e.message || _String(e)), e);
|
|
244
|
+
}
|
|
225
245
|
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || _String(e)), e);
|
|
226
246
|
}
|
|
227
247
|
}
|
|
228
248
|
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
/** @internal A fresh options object per decode. The strictness a format declares and the caps
|
|
250
|
+
* a caller asked for are combined here and nowhere else, so neither can be written onto the
|
|
251
|
+
* other's object and outlive this call. */
|
|
252
|
+
function _decodeOptions(opts, caps) {
|
|
253
|
+
if (!caps) return opts.ber ? { ber: true } : undefined;
|
|
254
|
+
var out = { maxBytes: caps.maxBytes, maxDepth: caps.maxDepth, maxItems: caps.maxItems };
|
|
255
|
+
if (opts.ber) out.ber = true;
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** @internal The decode caps a caller may name, each paired with the `C.LIMITS` default that
|
|
260
|
+
* is also its ceiling. A cap may tighten and may not loosen: the defaults are the resource
|
|
261
|
+
* the toolkit is willing to spend, and a caller raising one past them would be granting
|
|
262
|
+
* themselves more than the built-in refusal allows. */
|
|
263
|
+
var PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
|
|
264
|
+
maxBytes: "DER_MAX_BYTES",
|
|
265
|
+
maxDepth: "DER_MAX_DEPTH",
|
|
266
|
+
maxItems: "DER_MAX_ITEMS",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/** @internal A format that reads something other than DER declares its own ceilings through
|
|
270
|
+
* `opts.capLimits`, so the bound a caller is held to is the one their parser actually uses. */
|
|
271
|
+
var CBOR_PARSE_CAPS = intrinsic.assign(intrinsic.create(null), {
|
|
272
|
+
maxBytes: "CBOR_MAX_BYTES",
|
|
273
|
+
maxDepth: "CBOR_MAX_DEPTH",
|
|
274
|
+
maxItems: "CBOR_MAX_ITEMS",
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
var CBOR_RESOURCE_REFUSALS = intrinsic.assign(intrinsic.create(null), {
|
|
278
|
+
"cbor/too-large": 1, "cbor/too-deep": 1, "cbor/too-many-items": 1,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
function isCborResourceRefusal(e) {
|
|
282
|
+
return !!(e && typeof e.code === "string" && CBOR_RESOURCE_REFUSALS[e.code] === 1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function cborParseOpts(ErrorClass, prefix, what) {
|
|
286
|
+
var out = intrinsic.create(null);
|
|
287
|
+
out.ErrorClass = ErrorClass;
|
|
288
|
+
out.prefix = prefix;
|
|
289
|
+
out.what = what;
|
|
290
|
+
out.capLimits = CBOR_PARSE_CAPS;
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** @internal A parser door's options, held on a prototype-less object so a computed lookup on
|
|
295
|
+
* it answers from what the format declared. Every hand-rolled door builds its options here
|
|
296
|
+
* rather than as a literal in its own module. */
|
|
297
|
+
function parseOpts(spec) {
|
|
298
|
+
var out = intrinsic.create(null);
|
|
299
|
+
_forEach(intrinsic.keys(spec), function (k) { out[k] = spec[k]; });
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @internal Reads the caller's caps into a FRESH object. Nothing is written back onto what
|
|
304
|
+
* the caller passed, and nothing else is ever written onto the returned one, so a resource
|
|
305
|
+
* option cannot become a strictness flag on a shared object. */
|
|
306
|
+
function parseCaps(callerOpts, opts) {
|
|
307
|
+
var E = function (message) { return new opts.ErrorClass(opts.prefix + "/bad-input", message); };
|
|
308
|
+
if (callerOpts === undefined || callerOpts === null) callerOpts = _emptyOptions;
|
|
309
|
+
if (typeof callerOpts !== "object" || intrinsic.isArray(callerOpts) || guard.bytes.isByteSource(callerOpts)) {
|
|
310
|
+
throw E("parse options must be an object of decode caps");
|
|
311
|
+
}
|
|
312
|
+
var capLimits = opts.capLimits || PARSE_CAPS;
|
|
313
|
+
guard.identifier.assertKnownKeys(callerOpts, capLimits,
|
|
314
|
+
function (code, message) { return new opts.ErrorClass(code, message); },
|
|
315
|
+
opts.prefix + "/bad-input", "parse does not take the option ");
|
|
316
|
+
var caps = intrinsic.create(null);
|
|
317
|
+
_forEach(intrinsic.keys(capLimits), function (key) {
|
|
318
|
+
var ceiling = constants.LIMITS[capLimits[key]];
|
|
319
|
+
var asked = callerOpts[key];
|
|
320
|
+
if (asked === undefined) { caps[key] = ceiling; return; }
|
|
321
|
+
if (!intrinsic.isInteger(asked) || asked < 0) {
|
|
322
|
+
throw E(key + " must be a non-negative integer no greater than " + ceiling);
|
|
323
|
+
}
|
|
324
|
+
if (asked > ceiling) {
|
|
325
|
+
throw E(key + " " + asked + " is above the built-in ceiling " + ceiling + "; a cap may only tighten");
|
|
326
|
+
}
|
|
327
|
+
caps[key] = asked;
|
|
328
|
+
});
|
|
329
|
+
return caps;
|
|
330
|
+
}
|
|
331
|
+
var _emptyOptions = intrinsic.freeze(intrinsic.create(null));
|
|
332
|
+
|
|
333
|
+
/** @internal The door every format parser opens, whether it composes the shared factory or
|
|
334
|
+
* hand-rolls its walk. `build(rootNode, ns)` does the format's own work; everything around it
|
|
335
|
+
* is the part that must not be re-implemented per parser, because each re-implementation is
|
|
336
|
+
* another place the caller's caps can go missing or the latch can go unread. */
|
|
337
|
+
function runParseWith(input, opts, callerOpts, build) {
|
|
338
|
+
var caps = parseCaps(callerOpts, opts);
|
|
339
|
+
var budget = guard.limits.budget({ maxBytes: caps.maxBytes });
|
|
340
|
+
var ns = _nsWithBudget(opts.ns, budget, caps);
|
|
341
|
+
var result;
|
|
342
|
+
try {
|
|
343
|
+
result = build(decodeRoot(coerceToDer(input, opts), opts, caps, budget), ns, caps, budget);
|
|
344
|
+
} catch (e) {
|
|
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
|
+
}
|
|
352
|
+
throw e;
|
|
353
|
+
}
|
|
354
|
+
if (budget.exhausted()) throw _budgetRefusal(opts, null);
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function runParse(input, opts, callerOpts) {
|
|
359
|
+
return runParseWith(input, opts, callerOpts, function (root, ns) {
|
|
360
|
+
return schema.walk(opts.topSchema, root, ns).result;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @internal The re-read at the door is what makes the latch worth having: a catch anywhere
|
|
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. */
|
|
367
|
+
function _budgetRefusal(opts, cause) {
|
|
368
|
+
return new opts.ErrorClass(opts.prefix + "/too-large",
|
|
369
|
+
(opts.what || "the input") + " exceeds the decode budget for this parse", cause || undefined);
|
|
370
|
+
}
|
|
371
|
+
|
|
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);
|
|
231
390
|
}
|
|
232
391
|
|
|
233
392
|
var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
@@ -349,6 +508,7 @@ function attrValueToString(ns) {
|
|
|
349
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)");
|
|
350
509
|
}
|
|
351
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.
|
|
352
512
|
try { asn1.decode(raw); }
|
|
353
513
|
catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
|
|
354
514
|
return raw;
|
|
@@ -686,7 +846,7 @@ function decodeIssuingDistributionPointValue(ns, bytes, code) {
|
|
|
686
846
|
indirect: false, onlyAttr: false, fields: null };
|
|
687
847
|
var m;
|
|
688
848
|
try {
|
|
689
|
-
m = schema.walk(issuingDistributionPoint(code),
|
|
849
|
+
m = schema.walk(issuingDistributionPoint(code), schema.decodeNested(bytes, ns, "an IssuingDistributionPoint value"), ns);
|
|
690
850
|
if (m.fields.distributionPoint.present) {
|
|
691
851
|
var wrap = m.fields.distributionPoint.node;
|
|
692
852
|
if (!wrap.children || wrap.children.length !== 1) {
|
|
@@ -788,15 +948,20 @@ function userNoticeTexts(qualifier) {
|
|
|
788
948
|
return i === kids.length ? out : [];
|
|
789
949
|
}
|
|
790
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. */
|
|
791
956
|
function _decodeHelpers(ns) {
|
|
792
|
-
function decodeTop(buf, code, what) {
|
|
957
|
+
function decodeTop(buf, code, what, ctx) {
|
|
793
958
|
var n;
|
|
794
|
-
try { n =
|
|
959
|
+
try { n = schema.decodeNested(buf, ctx || ns, what); }
|
|
795
960
|
catch (e) { throw ns.E(code, "malformed " + what + " value: " + ((e && e.message) || _String(e)), e); }
|
|
796
961
|
return n;
|
|
797
962
|
}
|
|
798
|
-
function seqChildren(buf, code, what) {
|
|
799
|
-
var n = decodeTop(buf, code, what);
|
|
963
|
+
function seqChildren(buf, code, what, ctx) {
|
|
964
|
+
var n = decodeTop(buf, code, what, ctx);
|
|
800
965
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.SEQUENCE || !n.children) {
|
|
801
966
|
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
|
|
802
967
|
}
|
|
@@ -1356,7 +1521,7 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
1356
1521
|
}
|
|
1357
1522
|
|
|
1358
1523
|
function makeParser(opts) {
|
|
1359
|
-
return function (input) { return runParse(input, opts); };
|
|
1524
|
+
return function (input, callerOpts) { return runParse(input, opts, callerOpts); };
|
|
1360
1525
|
}
|
|
1361
1526
|
|
|
1362
1527
|
function makeRecordingParser(opts, kind) {
|
|
@@ -1430,6 +1595,14 @@ module.exports = {
|
|
|
1430
1595
|
isPemArmor: _isPemArmor,
|
|
1431
1596
|
decodeRoot: decodeRoot,
|
|
1432
1597
|
runParse: runParse,
|
|
1598
|
+
runParseWith: runParseWith,
|
|
1599
|
+
parseCaps: parseCaps,
|
|
1600
|
+
CBOR_PARSE_CAPS: CBOR_PARSE_CAPS,
|
|
1601
|
+
isCborResourceRefusal: isCborResourceRefusal,
|
|
1602
|
+
cborParseOpts: cborParseOpts,
|
|
1603
|
+
parseOpts: parseOpts,
|
|
1604
|
+
decodeNested: decodeNested,
|
|
1605
|
+
nestedContext: nestedContext,
|
|
1433
1606
|
makeNS: makeNS,
|
|
1434
1607
|
versionReader: versionReader,
|
|
1435
1608
|
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
|
|