@blamejs/pki 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -1
- package/MIGRATING.md +31 -0
- package/README.md +3 -3
- package/lib/attrcert-sign.js +11 -7
- package/lib/cmp-session.js +23 -10
- package/lib/cmp-verify.js +12 -4
- package/lib/cms-decrypt.js +71 -30
- package/lib/cms-encrypt.js +7 -11
- package/lib/crl-sign.js +170 -24
- package/lib/est.js +77 -1
- package/lib/guard-all.js +6 -0
- package/lib/guard-encoding.js +35 -6
- package/lib/guard-identifier.js +27 -1
- package/lib/guard-json.js +44 -8
- package/lib/guard-name.js +31 -8
- package/lib/guard-parsed.js +443 -0
- package/lib/hpke.js +36 -3
- package/lib/jose.js +8 -0
- package/lib/lint.js +20 -4
- package/lib/merkle.js +9 -0
- package/lib/ocsp.js +38 -8
- package/lib/path-validate.js +201 -72
- package/lib/pkcs12-build.js +34 -7
- package/lib/pki-build.js +12 -1
- package/lib/schema-crl.js +7 -1
- package/lib/schema-ocsp.js +6 -1
- package/lib/schema-pkcs12.js +7 -2
- package/lib/schema-pkix.js +76 -0
- package/lib/schema-x509.js +14 -1
- package/lib/sign-scheme.js +22 -5
- package/lib/smime.js +47 -0
- package/lib/trust.js +121 -10
- package/lib/tsp-sign.js +40 -19
- package/lib/validator-cose.js +86 -1
- package/lib/validator-tpm.js +8 -3
- package/lib/webauthn-mds.js +19 -29
- package/lib/webauthn.js +40 -18
- package/lib/x509-sign.js +6 -2
- package/package.json +5 -1
- package/sbom.cdx.json +6 -6
package/lib/merkle.js
CHANGED
|
@@ -188,8 +188,14 @@ function emptyRootHash() {
|
|
|
188
188
|
* var lh = pki.merkle.leafHash(Buffer.from([0]));
|
|
189
189
|
* pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
|
|
190
190
|
*/
|
|
191
|
+
// Every field of this verb is REQUIRED, so a misspelling is the one input that reads as an omission
|
|
192
|
+
// rather than as a value -- and an omitted required field is caught, while a misspelled one leaves
|
|
193
|
+
// the caller believing they supplied a proof about a leaf they never named.
|
|
194
|
+
var _INCLUSION_KEYS = { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 };
|
|
191
195
|
function verifyInclusion(opts) {
|
|
192
196
|
opts = opts || {};
|
|
197
|
+
guard.identifier.assertKnownKeys(opts, _INCLUSION_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
198
|
+
"merkle/bad-input", "unknown verifyInclusion option ");
|
|
193
199
|
var leafIndex = _coerceCoord(opts.leafIndex, "leafIndex");
|
|
194
200
|
var treeSize = _coerceCoord(opts.treeSize, "treeSize");
|
|
195
201
|
if (treeSize === 0n) throw new MerkleError("merkle/empty-tree", "an empty tree has no leaves to include");
|
|
@@ -254,8 +260,11 @@ function verifyInclusion(opts) {
|
|
|
254
260
|
* var r = pki.merkle.leafHash(Buffer.from([0]));
|
|
255
261
|
* pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
|
|
256
262
|
*/
|
|
263
|
+
var _CONSISTENCY_KEYS = { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 };
|
|
257
264
|
function verifyConsistency(opts) {
|
|
258
265
|
opts = opts || {};
|
|
266
|
+
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
267
|
+
"merkle/bad-input", "unknown verifyConsistency option ");
|
|
259
268
|
var oldSize = _coerceCoord(opts.oldSize, "oldSize");
|
|
260
269
|
var newSize = _coerceCoord(opts.newSize, "newSize");
|
|
261
270
|
var oldRoot = _node32(opts.oldRoot, "oldRoot");
|
package/lib/ocsp.js
CHANGED
|
@@ -57,14 +57,31 @@ var OID_EXTENDED_REVOKE = O("ocspExtendedRevoke");
|
|
|
57
57
|
|
|
58
58
|
function _digest(wcHash, buf) { return subtle.digest(wcHash, buf).then(function (h) { return Buffer.from(h); }); }
|
|
59
59
|
function _certOf(arg, what) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
var label = what || "a certificate";
|
|
61
|
+
// Re-derived from the bytes its parser read, like every other certificate door. A CertID is an
|
|
62
|
+
// IDENTITY -- the issuer's name and key hashed, plus the serial -- so a certificate that names one
|
|
63
|
+
// identity while carrying another's signed bytes would have this verb ask about, or answer for,
|
|
64
|
+
// a certificate nobody issued.
|
|
65
|
+
return guard.parsed.acceptDerived(arg, "certificate", function (bytes) {
|
|
66
|
+
var der = bytes;
|
|
67
|
+
if (typeof bytes === "string") {
|
|
68
|
+
try { der = x509.pemDecode(bytes); } catch (e) { throw _err("ocsp/bad-input", label + " PEM could not be decoded", e); }
|
|
69
|
+
}
|
|
70
|
+
try { return x509.parse(der); } catch (e) { throw _err("ocsp/bad-input", label + " is not a well-formed X.509 certificate", e); }
|
|
71
|
+
}, _err, "ocsp/bad-input", label);
|
|
72
|
+
}
|
|
73
|
+
// The response, always parsed from the bytes the caller handed over. See verify's own comment for
|
|
74
|
+
// why an object cannot be accepted here: the three parts of a signature check would come from three
|
|
75
|
+
// independently-chosen properties. The claim is detected on any of the parsed-response fields, so a
|
|
76
|
+
// caller who passes one is told what happened rather than getting a byte-parse fault about its type.
|
|
77
|
+
var _RESPONSE_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
|
|
78
|
+
function _responseFromBytes(response) {
|
|
79
|
+
return guard.parsed.fromTrustedSource(response, "ocspResponse", _RESPONSE_CLAIM, function (bytes) {
|
|
80
|
+
return ocspSchema.parseResponse(_toDer(bytes, "the OCSP response"));
|
|
81
|
+
}, _err, "ocsp/bad-input",
|
|
82
|
+
"the OCSP response must be its DER bytes, a PEM string, or an unmodified pki.schema.ocsp.parseResponse result: the signature, the algorithm that verifies it and the bytes it covers are separate properties of a parsed object, so a REBUILT response (Object.assign, spread, a JSON round-trip) could have the three describe different responses and is refused");
|
|
67
83
|
}
|
|
84
|
+
|
|
68
85
|
function _toDer(input, what) {
|
|
69
86
|
if (Buffer.isBuffer(input)) return input;
|
|
70
87
|
if (input instanceof Uint8Array) return Buffer.from(input);
|
|
@@ -456,13 +473,26 @@ function verify(response, opts) {
|
|
|
456
473
|
if (opts.cert == null || opts.issuer == null) return Promise.reject(_err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
|
|
457
474
|
var parsed, cert, issuerCert, time;
|
|
458
475
|
try {
|
|
459
|
-
|
|
476
|
+
// The response is parsed from BYTES, always. A claimed-parsed response carries the signature,
|
|
477
|
+
// the algorithm that verifies it and the byte range it covers as three independent properties,
|
|
478
|
+
// so an object could pair one structure's tbsResponseDataBytes with another's signature -- and a
|
|
479
|
+
// signature the issuing CA made over a certificate it issued would verify as a ResponseData
|
|
480
|
+
// signature, returning status "good" for a certificate the responder never spoke about.
|
|
481
|
+
// Parsing here binds all three to one byte string. pki.schema.ocsp.parseResponse remains the
|
|
482
|
+
// parse-only route for a caller who wants the structure without a verdict.
|
|
483
|
+
parsed = _responseFromBytes(response);
|
|
460
484
|
cert = _certOf(opts.cert, "the target certificate");
|
|
461
485
|
issuerCert = _certOf(opts.issuer, "the issuer certificate");
|
|
462
486
|
// The time drives the currency + responder-cert validity windows; an invalid Date fails closed
|
|
463
487
|
// via _asDate (a NaN compares false against every bound, silently disabling both), never defaults.
|
|
464
488
|
time = opts.time == null ? new Date() : _asDate(opts.time);
|
|
465
489
|
} catch (e) { return Promise.reject(e); }
|
|
490
|
+
// The object parsed HERE goes to the verdict verb, not the caller's argument again. It carries the
|
|
491
|
+
// parser's record, so the verdict verb re-derives from the same recorded bytes -- one snapshot,
|
|
492
|
+
// read by both. Passing the caller's argument a second time would take a SECOND snapshot of it,
|
|
493
|
+
// and a shared-memory view can differ between the two: the nonce compared below would belong to
|
|
494
|
+
// one response and the signature verified to another, which is the split this whole mechanism
|
|
495
|
+
// exists to close.
|
|
466
496
|
return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time, { historicalMode: opts.historicalMode === true }).then(function (verdict) {
|
|
467
497
|
// A client that sent no nonce still gets the field, as null. Leaving it absent would make
|
|
468
498
|
// "not requested" indistinguishable from "the field is not there yet" for a consumer reading
|
package/lib/path-validate.js
CHANGED
|
@@ -1127,13 +1127,35 @@ function validateCriticalExtensionStructure(cert) {
|
|
|
1127
1127
|
* The value-carrying options (`time`, `maxPathCerts`, `maxPolicyNodes`, the
|
|
1128
1128
|
* subtree seeds, `userInitialPolicySet`, `requiredEku`) are validated at the
|
|
1129
1129
|
* entry point -- a mis-shaped value throws `path/bad-input` rather than
|
|
1130
|
-
* silently not applying. Returns `{ valid,
|
|
1131
|
-
* results, workingPublicKey, workingPublicKeyAlgorithm,
|
|
1130
|
+
* silently not applying. Returns `{ valid, revocationChecked, anchorConstraints,
|
|
1131
|
+
* path, results, workingPublicKey, workingPublicKeyAlgorithm,
|
|
1132
1132
|
* workingPublicKeyParameters, validPolicyTree }` where `results[i].checks`
|
|
1133
1133
|
* carries a per-check reason code (`path/*`) for every step. Pure and
|
|
1134
1134
|
* re-entrant -- no input object is mutated. An empty path or a missing anchor
|
|
1135
1135
|
* throws a typed `PathError`.
|
|
1136
1136
|
*
|
|
1137
|
+
* `valid` alone cannot say whether revocation was ever established, so
|
|
1138
|
+
* `revocationChecked` answers separately, taking the WEAKEST outcome on the
|
|
1139
|
+
* path: `false` when no `revocationChecker` was supplied, `"determined"` when
|
|
1140
|
+
* every certificate got an explicit good or revoked answer, `"waived"` when
|
|
1141
|
+
* `softFail` turned an undetermined one into a pass, and `"undetermined"` when
|
|
1142
|
+
* one could not be answered at all and the path fails for it. The
|
|
1143
|
+
* per-certificate `revocation` check carries the `status` it was decided on and
|
|
1144
|
+
* marks a waiver, so "checked, good" is distinguishable from "could not check,
|
|
1145
|
+
* and you waived it" -- which a stored verdict is re-read to settle. A checker
|
|
1146
|
+
* that THROWS is a fault in the checker rather than a status it reported, so it
|
|
1147
|
+
* fails the path as `path/revocation-checker-error` carrying the fault whatever
|
|
1148
|
+
* `softFail` says -- `softFail` opts into an undetermined ANSWER, and the
|
|
1149
|
+
* built-in checkers report one as `{ status: "unknown" }` rather than throwing.
|
|
1150
|
+
*
|
|
1151
|
+
* `anchorConstraints` reports what the anchor's own trust metadata decided:
|
|
1152
|
+
* the `checkedPurpose` it was judged under, and whether the `distrustAfter`
|
|
1153
|
+
* date and the `purposes` delegator map each applied. That metadata is keyed BY
|
|
1154
|
+
* key purpose, so an anchor carrying it while `opts.checkPurpose` is absent is a
|
|
1155
|
+
* configuration fault (`path/bad-input`) rather than a constraint that silently
|
|
1156
|
+
* does nothing -- a root distrusted years ago must not quietly validate a
|
|
1157
|
+
* current leaf.
|
|
1158
|
+
*
|
|
1137
1159
|
* @example
|
|
1138
1160
|
* var pair = await pki.key.generate("Ed25519");
|
|
1139
1161
|
* var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
@@ -1156,7 +1178,10 @@ async function validate(path, opts) {
|
|
|
1156
1178
|
// crypto amplification from an oversized path). Entry-point tier: throw.
|
|
1157
1179
|
var maxCerts = guard.limits.cap(opts.maxPathCerts, "validate: opts.maxPathCerts", constants.LIMITS.PATH_MAX_CERTS, { E: E, code: "path/bad-input", min: 1 });
|
|
1158
1180
|
if (path.length > maxCerts) throw E("path/bad-input", "validate: the certification path has " + path.length + " certificates, exceeding the maxPathCerts limit (" + maxCerts + ")");
|
|
1159
|
-
|
|
1181
|
+
// Through the same door `build` uses, and for the same reason: every certificate this walk decides
|
|
1182
|
+
// over is re-derived from the bytes its parser read (see coerceCert), so a rebuilt object cannot
|
|
1183
|
+
// present a genuine signed range alongside substituted fields.
|
|
1184
|
+
var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
|
|
1160
1185
|
var n = certs.length;
|
|
1161
1186
|
if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
|
|
1162
1187
|
if (!opts.trustAnchor) throw E("path/bad-input", "validate: a trustAnchor is required");
|
|
@@ -1204,6 +1229,22 @@ async function validate(path, opts) {
|
|
|
1204
1229
|
// then fails the path closed instead of silently skipping the step.
|
|
1205
1230
|
var requireRevocation = opts.requireRevocation === true;
|
|
1206
1231
|
var failed = false;
|
|
1232
|
+
// Whether the revocation rule RAN, and whether any certificate's answer had to be waived. The
|
|
1233
|
+
// verdict reports the two separately because they are different claims: a caller re-reading a
|
|
1234
|
+
// stored result needs to tell "every certificate was determined not revoked" from "the step was
|
|
1235
|
+
// skipped" from "the step could not conclude and you had asked for that to pass".
|
|
1236
|
+
var revocationRan = false, revocationWaived = false, revocationUndetermined = false;
|
|
1237
|
+
// Which of the anchor's purpose-scoped constraints actually decided anything, reported so a
|
|
1238
|
+
// verdict can be re-read to tell an anchor that was judged from one that carried nothing to judge.
|
|
1239
|
+
var anchorDistrustApplied = false, anchorPurposeApplied = false;
|
|
1240
|
+
// A trust anchor carrying purpose-scoped metadata, validated with no purpose to select by, is a
|
|
1241
|
+
// configuration fault. The constraint is KEYED by purpose -- there is no way to apply
|
|
1242
|
+
// `distrustAfter.serverAuth` without being told the validation is about serverAuth -- so without
|
|
1243
|
+
// one the caller's stated intent would be discarded silently, and a root distrusted years ago
|
|
1244
|
+
// would validate a current leaf. An anchor carrying no such metadata is unaffected.
|
|
1245
|
+
if (!checkPurpose && opts.trustAnchor && _hasPurposeScopedMetadata(opts.trustAnchor)) {
|
|
1246
|
+
throw E("path/bad-input", "validate: the trust anchor carries purpose-scoped metadata (distrustAfter / purposes), which is keyed by key purpose -- supply opts.checkPurpose to say which purpose this validation is for, or the constraint cannot be applied");
|
|
1247
|
+
}
|
|
1207
1248
|
|
|
1208
1249
|
for (var idx = 0; idx < n; idx++) {
|
|
1209
1250
|
var i = idx + 1;
|
|
@@ -1274,16 +1315,48 @@ async function validate(path, opts) {
|
|
|
1274
1315
|
// 6.1.3(a)(3) revocation.
|
|
1275
1316
|
if (revocationChecker) {
|
|
1276
1317
|
var issuerCert = idx > 0 ? certs[idx - 1] : null; // the anchor issues cert[1]
|
|
1277
|
-
var rv;
|
|
1318
|
+
var rv, rvError = null;
|
|
1319
|
+
// A checker that THROWS -- or whose promise rejects -- is not a checker reporting "unknown".
|
|
1320
|
+
// Laundering the two together made a broken checker indistinguishable from a working one that
|
|
1321
|
+
// could not reach the responder, and under softFail both became a pass. The fault is carried
|
|
1322
|
+
// onto the check so an operator can tell their own bug from a network condition, and it fails
|
|
1323
|
+
// the path whatever softFail says (see the branch below).
|
|
1278
1324
|
try { rv = await revocationChecker.check(cert, { workingIssuerName: state.workingIssuerName, workingPublicKey: state.workingPublicKey, workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm, issuerCert: issuerCert }, { time: opts.time, historicalMode: opts.historicalMode === true }); }
|
|
1279
|
-
catch (
|
|
1325
|
+
catch (e) { rv = { status: "error" }; rvError = e; }
|
|
1280
1326
|
// ONLY an explicit "good" is a determined non-revocation; "revoked" fails;
|
|
1281
1327
|
// every other value ("unknown", an OCSP tryLater/unauthorized, a typo, a
|
|
1282
1328
|
// missing status) is undetermined and fails closed unless softFail.
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1329
|
+
//
|
|
1330
|
+
// The two `ok: true` outcomes are NOT the same claim and no longer the same object. "checked,
|
|
1331
|
+
// and it said good" and "could not check, and you waived it" read identically from a bare
|
|
1332
|
+
// boolean, which is the whole reason a stored verdict cannot be re-read to answer whether
|
|
1333
|
+
// revocation was ever established. Each entry now names the status it was decided on, and a
|
|
1334
|
+
// waiver marks itself.
|
|
1335
|
+
var rvStatus = (rv && typeof rv.status === "string") ? rv.status : "unknown";
|
|
1336
|
+
if (rv && rv.status === "good") { checks.push({ name: "revocation", ok: true, status: "good" }); }
|
|
1337
|
+
else if (rv && rv.status === "revoked") { checks.push({ name: "revocation", ok: false, status: "revoked", code: "path/revoked" }); failed = true; }
|
|
1338
|
+
else if (rvError) {
|
|
1339
|
+
// A checker that threw is a FAULT in the checker, not a status it could not reach, and
|
|
1340
|
+
// softFail is the caller opting into an undetermined ANSWER. The built-in CRL and OCSP
|
|
1341
|
+
// checkers return `{status:"unknown"}` for every unreachable or unverifiable condition and
|
|
1342
|
+
// never throw, so a throw here is the caller's own bug -- waiving it would pass the
|
|
1343
|
+
// certificate with no revocation result at all, which is the outcome softFail is asked for
|
|
1344
|
+
// and this is not.
|
|
1345
|
+
checks.push({ name: "revocation", ok: false, status: "error", code: "path/revocation-checker-error", error: rvError });
|
|
1346
|
+
revocationUndetermined = true;
|
|
1347
|
+
failed = true;
|
|
1348
|
+
}
|
|
1349
|
+
else if (softFail) {
|
|
1350
|
+
// No `error` slot here or below: a fault took the branch above, so anything reaching these
|
|
1351
|
+
// two is a status the checker actually reported.
|
|
1352
|
+
checks.push({ name: "revocation", ok: true, status: rvStatus, waived: true });
|
|
1353
|
+
revocationWaived = true;
|
|
1354
|
+
} else {
|
|
1355
|
+
checks.push({ name: "revocation", ok: false, status: rvStatus, code: "path/revocation-undetermined" });
|
|
1356
|
+
revocationUndetermined = true;
|
|
1357
|
+
failed = true;
|
|
1358
|
+
}
|
|
1359
|
+
revocationRan = true;
|
|
1287
1360
|
} else if (requireRevocation) {
|
|
1288
1361
|
// No checker was supplied but the caller demands a revocation determination:
|
|
1289
1362
|
// the step cannot be performed, so fail closed (never silently skip).
|
|
@@ -1357,6 +1430,7 @@ async function validate(path, opts) {
|
|
|
1357
1430
|
// before the comparison; an absent (undefined/null) date is no restriction.
|
|
1358
1431
|
var distrustDate = assertAnchorConstraints(ta, checkPurpose);
|
|
1359
1432
|
if (distrustDate != null) {
|
|
1433
|
+
anchorDistrustApplied = true;
|
|
1360
1434
|
// STRICTLY > : a leaf whose notBefore == the distrust date stays trusted
|
|
1361
1435
|
// (Mozilla certverifier isDistrustedCertificateChain: endEntityNotBefore
|
|
1362
1436
|
// <= distrustAfterTime -> not distrusted; the end-of-day ...235959Z
|
|
@@ -1365,8 +1439,11 @@ async function validate(path, opts) {
|
|
|
1365
1439
|
checks.push({ name: "distrustAfter", ok: false, code: "path/distrusted-after" }); failed = true;
|
|
1366
1440
|
}
|
|
1367
1441
|
}
|
|
1368
|
-
if (checkPurpose && ta.purposes
|
|
1369
|
-
|
|
1442
|
+
if (checkPurpose && ta.purposes) {
|
|
1443
|
+
anchorPurposeApplied = true;
|
|
1444
|
+
if (ta.purposes[checkPurpose] !== true) {
|
|
1445
|
+
checks.push({ name: "purposeTrust", ok: false, code: "path/purpose-not-trusted" }); failed = true;
|
|
1446
|
+
}
|
|
1370
1447
|
}
|
|
1371
1448
|
updateWorkingKey(state, cert); // 6.1.5(c),(d) -- key AND algorithm AND parameters
|
|
1372
1449
|
}
|
|
@@ -1404,6 +1481,20 @@ async function validate(path, opts) {
|
|
|
1404
1481
|
|
|
1405
1482
|
return {
|
|
1406
1483
|
valid: !failed,
|
|
1484
|
+
// What the revocation rule and the anchor's own trust metadata actually decided. `valid` alone
|
|
1485
|
+
// cannot answer either, and both are questions a stored verdict is re-read to settle: was this
|
|
1486
|
+
// certificate ever established as un-revoked, and was the anchor's distrust date consulted.
|
|
1487
|
+
// The WEAKEST outcome on the path, not the fact that a checker ran: a certificate nobody could
|
|
1488
|
+
// answer for leaves revocation unestablished however many others answered, and deriving the word
|
|
1489
|
+
// from "a checker ran" put that run on the same value as one that established every answer.
|
|
1490
|
+
revocationChecked: !revocationRan ? false
|
|
1491
|
+
: revocationUndetermined ? "undetermined"
|
|
1492
|
+
: revocationWaived ? "waived" : "determined",
|
|
1493
|
+
anchorConstraints: {
|
|
1494
|
+
checkedPurpose: checkPurpose || null,
|
|
1495
|
+
distrustAfterApplied: anchorDistrustApplied,
|
|
1496
|
+
purposeTrustApplied: anchorPurposeApplied,
|
|
1497
|
+
},
|
|
1407
1498
|
path: certs,
|
|
1408
1499
|
results: state.results,
|
|
1409
1500
|
workingPublicKey: state.workingPublicKey,
|
|
@@ -1446,24 +1537,10 @@ var OID_AUTHORITY_KEY_ID = oid.byName("authorityKeyIdentifier");
|
|
|
1446
1537
|
var OID_CRL_NUMBER = oid.byName("cRLNumber");
|
|
1447
1538
|
var OID_FRESHEST_CRL = oid.byName("freshestCRL");
|
|
1448
1539
|
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
1452
|
-
|
|
1453
|
-
// through the engine so the trailing-field grammar (strictly-ascending tags, each
|
|
1454
|
-
// at most once) and the DER BOOLEAN value rules are the shared enforcement, not a
|
|
1455
|
-
// hand-walk; a present DEFAULT-FALSE flag encoding FALSE is the omitted default
|
|
1456
|
-
// (X.690 sec. 11.5) and rejects at the leaf-value level below.
|
|
1457
|
-
var IDP_SCHEMA = schema.seq([
|
|
1458
|
-
schema.trailing([
|
|
1459
|
-
{ tag: 0, name: "distributionPoint", schema: schema.any() },
|
|
1460
|
-
{ tag: 1, name: "onlyContainsUserCerts", schema: schema.implicitBoolean(1) },
|
|
1461
|
-
{ tag: 2, name: "onlyContainsCACerts", schema: schema.implicitBoolean(2) },
|
|
1462
|
-
{ tag: 3, name: "onlySomeReasons", schema: schema.implicitBitString(3) },
|
|
1463
|
-
{ tag: 4, name: "indirectCRL", schema: schema.implicitBoolean(4) },
|
|
1464
|
-
{ tag: 5, name: "onlyContainsAttributeCerts", schema: schema.implicitBoolean(5) },
|
|
1465
|
-
], { minTag: 0, maxTag: 5, unexpectedCode: "path/bad-idp", orderCode: "path/bad-idp" }),
|
|
1466
|
-
], { assert: "sequence", code: "path/bad-idp", what: "IssuingDistributionPoint" });
|
|
1540
|
+
// The RFC 5280 sec. 5.2.5 IssuingDistributionPoint grammar, shared with the CRL
|
|
1541
|
+
// verbs (pkix.issuingDistributionPoint) so the scope this validator reads and the
|
|
1542
|
+
// scope pki.crl.isRevoked refuses to answer past are read by the same rules.
|
|
1543
|
+
var IDP_SCHEMA = pkix.issuingDistributionPoint("path/bad-idp");
|
|
1467
1544
|
|
|
1468
1545
|
// RFC 5280 sec. 6.3.2(a): the legal members of reasons_mask are exactly the eight
|
|
1469
1546
|
// named ReasonFlags bits, 1..8 (`unused` bit 0 is not a reason, and `unspecified`
|
|
@@ -1782,7 +1859,16 @@ function selectDelta(candidates) {
|
|
|
1782
1859
|
* typeof checker.check; // "function"
|
|
1783
1860
|
*/
|
|
1784
1861
|
function crlChecker(crls, opts) {
|
|
1785
|
-
var parsed = (crls || []).map(function (c) {
|
|
1862
|
+
var parsed = (crls || []).map(function (c, ci) {
|
|
1863
|
+
// Re-derived from the bytes the parser read, exactly as a certificate is. A CRL's answer is a
|
|
1864
|
+
// verdict, and its signature covers a byte range while the revocation list and the scope
|
|
1865
|
+
// extensions are separate properties of the parsed object: keep a correctly signed CRL's
|
|
1866
|
+
// `tbsBytes` and signature, empty `revokedCertificates`, and the signature still verifies while
|
|
1867
|
+
// a revoked certificate reports good. The scope fields fail the same way -- an emptied
|
|
1868
|
+
// `crlExtensions` turns a scope-restricted CRL into one that answers for everything.
|
|
1869
|
+
return guard.parsed.acceptDerived(c, "crl", crl.parse, E, "path/bad-input",
|
|
1870
|
+
"crlChecker: crls[" + ci + "]");
|
|
1871
|
+
});
|
|
1786
1872
|
// RFC 5280 sec. 6.3.1(b): use-deltas is an INPUT to the algorithm. Default ON --
|
|
1787
1873
|
// a caller holding a delta wants it used -- and turning it off never makes a
|
|
1788
1874
|
// verdict weaker, only less determined.
|
|
@@ -2222,7 +2308,10 @@ cmsVerify.setEngine({ build: build, validate: validate, toAnchor: toAnchor,
|
|
|
2222
2308
|
* typeof checker.check; // "function"
|
|
2223
2309
|
*/
|
|
2224
2310
|
function ocspChecker(responses) {
|
|
2225
|
-
|
|
2311
|
+
// The same door verifyOcspResponse uses. This checker's responses reach the identical three-part
|
|
2312
|
+
// signature check, so accepting a parsed object here on a truthy responseStatus would leave the
|
|
2313
|
+
// door shut on one of the two ways into that check and open on the other.
|
|
2314
|
+
var parsed = (responses || []).map(function (r) { return _ocspFromBytes(r); });
|
|
2226
2315
|
return {
|
|
2227
2316
|
check: async function (cert, issuer, ctx) {
|
|
2228
2317
|
var time = ctx.time;
|
|
@@ -2272,14 +2361,14 @@ function ocspChecker(responses) {
|
|
|
2272
2361
|
|
|
2273
2362
|
/**
|
|
2274
2363
|
* @primitive pki.path.verifyOcspResponse
|
|
2275
|
-
* @signature pki.path.verifyOcspResponse(
|
|
2364
|
+
* @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, reason }>
|
|
2276
2365
|
* @since 0.2.22
|
|
2277
2366
|
* @status stable
|
|
2278
2367
|
* @spec RFC 6960
|
|
2279
2368
|
* @related pki.ocsp.verify, pki.path.ocspChecker
|
|
2280
2369
|
*
|
|
2281
|
-
* Verify a single
|
|
2282
|
-
* already-parsed issuer certificate at `time` -- the lower-level primitive
|
|
2370
|
+
* Verify a single OCSP response for one certificate
|
|
2371
|
+
* against its already-parsed issuer certificate at `time` -- the lower-level primitive
|
|
2283
2372
|
* `pki.ocsp.verify` composes after parsing its inputs (most callers want that
|
|
2284
2373
|
* ergonomic entry, which also handles DER/PEM decoding and request-nonce
|
|
2285
2374
|
* matching). It runs the EXACT SAME gates the path validator's `ocspChecker`
|
|
@@ -2300,6 +2389,16 @@ function ocspChecker(responses) {
|
|
|
2300
2389
|
* `time` either way. `time` must be a valid `Date`. A malformed response's parse
|
|
2301
2390
|
* fault surfaces as the parser's typed `ocsp/*` / `asn1/*` error.
|
|
2302
2391
|
*
|
|
2392
|
+
* The response is its DER bytes, a PEM string, or an unmodified
|
|
2393
|
+
* `pki.schema.ocsp.parseResponse` result. A REBUILT parsed response is refused. A
|
|
2394
|
+
* signature check has three parts -- the signature, the algorithm that verifies it,
|
|
2395
|
+
* and the bytes it covers -- and on a parsed object all three are separate properties:
|
|
2396
|
+
* a genuine CA signature over a certificate that CA issued, relabelled, verifies as a
|
|
2397
|
+
* ResponseData signature for a response that never existed. The parser marks what it
|
|
2398
|
+
* returns, so those three are known to have been derived together from one byte
|
|
2399
|
+
* string; `Object.assign`, spread and a JSON round-trip all drop the mark, which is
|
|
2400
|
+
* exactly how such an object is assembled.
|
|
2401
|
+
*
|
|
2303
2402
|
* @example
|
|
2304
2403
|
* var ca = await pki.key.generate("Ed25519");
|
|
2305
2404
|
* var caKey = await pki.key.export(ca.privateKey);
|
|
@@ -2315,11 +2414,49 @@ function ocspChecker(responses) {
|
|
|
2315
2414
|
* { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
|
|
2316
2415
|
* { cert: caDer, key: caKey });
|
|
2317
2416
|
* var cert = pki.schema.x509.parse(leafDer), issuerCert = pki.schema.x509.parse(caDer);
|
|
2318
|
-
* var
|
|
2319
|
-
* var v = await pki.path.verifyOcspResponse(resp, cert, issuerCert, new Date());
|
|
2417
|
+
* var v = await pki.path.verifyOcspResponse(der, cert, issuerCert, new Date());
|
|
2320
2418
|
* v.status; // "good" | "revoked" | "unknown"
|
|
2321
2419
|
*/
|
|
2322
|
-
function verifyOcspResponse(
|
|
2420
|
+
function verifyOcspResponse(response, cert, issuerCert, time, opts) {
|
|
2421
|
+
var parsedResponse, subject, issuer;
|
|
2422
|
+
try {
|
|
2423
|
+
parsedResponse = _ocspFromBytes(response);
|
|
2424
|
+
// The two certificates go through the same door as the response, for the same reason: this
|
|
2425
|
+
// verdict is about a certificate's IDENTITY -- its serial and its issuer's name and key are
|
|
2426
|
+
// what the CertID is matched against, and the issuer's key is what authorizes a delegated
|
|
2427
|
+
// responder. A caller-assembled certificate could name one identity while carrying another's
|
|
2428
|
+
// signed bytes, so both are re-derived from the bytes their parser read.
|
|
2429
|
+
subject = coerceCert(cert, "the certificate");
|
|
2430
|
+
issuer = coerceCert(issuerCert, "the issuer certificate");
|
|
2431
|
+
} catch (e) { return Promise.reject(e); }
|
|
2432
|
+
return _verifyOcspParsed(parsedResponse, subject, issuer, time, opts);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// The response, always parsed from the bytes the caller handed over.
|
|
2436
|
+
//
|
|
2437
|
+
// A signature check has three parts -- the signature, the algorithm that verifies it, and the byte
|
|
2438
|
+
// range it covers -- and on a claimed-parsed response all three are separate properties of one
|
|
2439
|
+
// caller-supplied object. Pair a real CA's signature over a certificate IT issued with that
|
|
2440
|
+
// certificate's own tbsBytes and algorithm, label them `signature` / `tbsResponseDataBytes` /
|
|
2441
|
+
// `signatureAlgorithm`, and the check verifies: a genuine signature, over the bytes it was made
|
|
2442
|
+
// over, under the right key -- for a structure the responder never produced. Parsing binds the three
|
|
2443
|
+
// to one byte string, which is the only thing that makes the verdict about a response at all.
|
|
2444
|
+
var _OCSP_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
|
|
2445
|
+
function _ocspFromBytes(response) {
|
|
2446
|
+
return guard.parsed.fromTrustedSource(response, "ocspResponse", _OCSP_CLAIM, function (bytes) {
|
|
2447
|
+
if (!Buffer.isBuffer(bytes) && !(bytes instanceof Uint8Array) && typeof bytes !== "string") {
|
|
2448
|
+
throw E("path/bad-input", "verifyOcspResponse: the response must be an OCSP response DER Buffer, a PEM string, or a pki.schema.ocsp.parseResponse result");
|
|
2449
|
+
}
|
|
2450
|
+
return ocsp.parseResponse(bytes);
|
|
2451
|
+
}, E, "path/bad-input",
|
|
2452
|
+
"verifyOcspResponse: the response must be its DER bytes, a PEM string, or an unmodified pki.schema.ocsp.parseResponse result: the signature, the algorithm that verifies it and the bytes it covers are separate properties of a parsed object, so a REBUILT response (Object.assign, spread, a JSON round-trip) could have the three describe different responses and is refused");
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// The verification over a response this module has ALREADY derived from bytes -- reached only
|
|
2456
|
+
// through the door above and through ocspChecker, which parses the responder's own reply. Not
|
|
2457
|
+
// exported: this module's export object IS pki.path, so anything on it is public surface and frozen
|
|
2458
|
+
// by the API snapshot, whatever a comment beside it claims.
|
|
2459
|
+
function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
2323
2460
|
opts = opts || {};
|
|
2324
2461
|
// The currency + responder-cert validity windows compare against `time`; a missing or invalid
|
|
2325
2462
|
// check date must fail closed (a NaN compares false against every bound), never silently pass.
|
|
@@ -2377,41 +2514,22 @@ function nameMatchSoft(rdnsA, rdnsB) {
|
|
|
2377
2514
|
catch (_e) { return false; }
|
|
2378
2515
|
}
|
|
2379
2516
|
|
|
2380
|
-
// A
|
|
2381
|
-
// the
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
//
|
|
2385
|
-
// every
|
|
2386
|
-
//
|
|
2387
|
-
//
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
!!o.subjectPublicKeyInfo && Buffer.isBuffer(o.subjectPublicKeyInfo.bytes) &&
|
|
2397
|
-
!!o.subjectPublicKeyInfo.algorithm && typeof o.subjectPublicKeyInfo.algorithm.oid === "string" &&
|
|
2398
|
-
!!o.subjectPublicKeyInfo.publicKey && Buffer.isBuffer(o.subjectPublicKeyInfo.publicKey.bytes) &&
|
|
2399
|
-
typeof o.subjectPublicKeyInfo.publicKey.unusedBits === "number" &&
|
|
2400
|
-
Array.isArray(o.extensions) && o.extensions.every(_isExtensionEntry);
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
function coerceCert(input) {
|
|
2404
|
-
// An already-parsed certificate is passed through; a DER Buffer / PEM string is
|
|
2405
|
-
// parsed (and its typed error normalized by the caller). A claimed-parsed object
|
|
2406
|
-
// (a truthy tbsBytes) must carry the COMPLETE parsed-certificate shape build and
|
|
2407
|
-
// the validate hand-off dereference -- a bare { tbsBytes } object, or any partial
|
|
2408
|
-
// shape, fails closed here as a typed PathError rather than a raw TypeError deeper
|
|
2409
|
-
// in the walk or inside validate.
|
|
2410
|
-
if (input && typeof input === "object" && !Buffer.isBuffer(input) && input.tbsBytes !== undefined) {
|
|
2411
|
-
if (!_isParsedCert(input)) throw E("path/bad-input", "build: an input has tbsBytes but is not a well-formed parsed certificate");
|
|
2412
|
-
return input;
|
|
2413
|
-
}
|
|
2414
|
-
return x509.parse(input);
|
|
2517
|
+
// A certificate reaching a VERDICT is re-derived from the bytes the parser read, never trusted as
|
|
2518
|
+
// the object it arrives as. Completeness -- every field present with the right type -- is not enough
|
|
2519
|
+
// here, because a certificate's meaning is one signature over one byte range while a parsed
|
|
2520
|
+
// certificate presents that range, the signature, and the fields the range encodes as separate
|
|
2521
|
+
// properties. Keep a real CA certificate's `tbsBytes` and signature and substitute only its
|
|
2522
|
+
// `subjectPublicKeyInfo`, and every completeness rule passes, this walk verifies the ORIGINAL signed
|
|
2523
|
+
// range, and then uses the substituted key to check the next certificate -- a forged chain built out
|
|
2524
|
+
// of a genuine certificate. Emptying `extensions` is the same move against basicConstraints,
|
|
2525
|
+
// keyUsage, the name constraints and the unknown-critical rule.
|
|
2526
|
+
//
|
|
2527
|
+
// So the door takes the parser's record instead: a certificate from pki.schema.x509.parse re-parses
|
|
2528
|
+
// from the bytes it was read from, and anything done to that object since is discarded. One a caller
|
|
2529
|
+
// assembled has no record and is refused rather than silently believed.
|
|
2530
|
+
function coerceCert(input, label) {
|
|
2531
|
+
return guard.parsed.acceptDerived(input, "certificate", x509.parse, E, "path/bad-input",
|
|
2532
|
+
label || "a certificate");
|
|
2415
2533
|
}
|
|
2416
2534
|
|
|
2417
2535
|
// A trust-store entry is either a ready anchor tuple { name, publicKey,
|
|
@@ -2437,6 +2555,17 @@ function coerceCert(input) {
|
|
|
2437
2555
|
// exposed for the same reason: a caller that may never reach the walk -- pki.cms.verify when no
|
|
2438
2556
|
// signer verified -- has to be able to reject a malformed anchor at ITS entry point, through this
|
|
2439
2557
|
// same definition, so configuration validity never depends on the message.
|
|
2558
|
+
// Does this anchor carry trust metadata that only a named key purpose can unlock? A non-empty
|
|
2559
|
+
// `distrustAfter` or `purposes` map is such metadata -- both are indexed BY purpose, so both are
|
|
2560
|
+
// inert without one. An EMPTY map states no constraint and is not a reason to refuse.
|
|
2561
|
+
function _hasPurposeScopedMetadata(ta) {
|
|
2562
|
+
if (!ta || typeof ta !== "object") return false;
|
|
2563
|
+
return ["distrustAfter", "purposes"].some(function (k) {
|
|
2564
|
+
var m = ta[k];
|
|
2565
|
+
return !!m && typeof m === "object" && Object.keys(m).length > 0;
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2440
2569
|
function assertAnchorConstraints(ta, checkPurpose) {
|
|
2441
2570
|
var d = (checkPurpose && ta && ta.distrustAfter) ? ta.distrustAfter[checkPurpose] : null;
|
|
2442
2571
|
if (d == null) return null;
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -296,6 +296,24 @@ function _buildBag(bag, opts, depth) {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
// A pre-encoded DER value (one well-formed TLV, no trailing bytes) supplied verbatim (secretValue).
|
|
299
|
+
// The store, always parsed from the BYTES the caller handed over.
|
|
300
|
+
//
|
|
301
|
+
// Both verbs that use this decide integrity, and integrity is the one operation that must not read
|
|
302
|
+
// its inputs from two independently-chosen properties. Accepting a claimed-parsed store let
|
|
303
|
+
// `macedBytes` -- the range the MAC is verified over -- and `safeBags` / `encryptedSafes` -- the
|
|
304
|
+
// content returned as verified -- come from different sources: verify store A's MAC, hand back store
|
|
305
|
+
// B's bags. Parsing here binds them, because both are then derived from one byte string.
|
|
306
|
+
//
|
|
307
|
+
// `pki.schema.pkcs12.parse` remains the parse-only route for a caller who wants the structure
|
|
308
|
+
// without an integrity decision.
|
|
309
|
+
var _STORE_CLAIM = ["integrityMode", "mac", "macedBytes"];
|
|
310
|
+
function _storeFromBytes(pfx) {
|
|
311
|
+
return guard.parsed.fromTrustedSource(pfx, "pkcs12Store", _STORE_CLAIM, function (bytes) {
|
|
312
|
+
return schemaPkcs12.parse(_coerceDer(bytes, "pfx"));
|
|
313
|
+
}, _err, "pkcs12/bad-input",
|
|
314
|
+
"pfx must be the store's DER bytes, a PEM string, or an unmodified pki.schema.pkcs12.parse result: the MAC is verified over a byte range carried on the object and the bags returned as verified are a separate property of it, so a REBUILT store (Object.assign, spread, a JSON round-trip) could have the two describe different stores and is refused");
|
|
315
|
+
}
|
|
316
|
+
|
|
299
317
|
function _reqDer(input, label) {
|
|
300
318
|
if (input == null) throw _err("pkcs12/bad-input", label + " is required");
|
|
301
319
|
var der = _bytes(input, label);
|
|
@@ -522,8 +540,10 @@ async function build(spec, opts) {
|
|
|
522
540
|
* @defends pkcs12-mac-forgery (CWE-347)
|
|
523
541
|
* @related pki.pkcs12.build, pki.schema.pkcs12.parse
|
|
524
542
|
*
|
|
525
|
-
* Verify a password-integrity PKCS#12 store's MAC. `pfx` is
|
|
526
|
-
* `
|
|
543
|
+
* Verify a password-integrity PKCS#12 store's MAC. `pfx` is the store's DER `Buffer`, a PEM string, or an
|
|
544
|
+
* unmodified `pki.schema.pkcs12.parse` result. A REBUILT parsed store is refused: the MAC is verified over
|
|
545
|
+
* a byte range the object carries, and the parser's mark is what says that range and the store it describes
|
|
546
|
+
* came from one place. `Object.assign`, spread and a JSON round-trip drop the mark. The password is BMPString+NULL encoded (RFC 7292 App. B.1), the MAC is
|
|
527
547
|
* recomputed over the store's exact AuthenticatedSafe byte range (`macedBytes`) using the store's own MAC
|
|
528
548
|
* parameters -- the classic Appendix B (ID=3) HMAC or the RFC 9579 PBMAC1 -- and constant-time-compared to
|
|
529
549
|
* the stored MAC value. Returns `true` / `false` for the password match; throws `Pkcs12Error` on a MAC-less
|
|
@@ -538,8 +558,14 @@ async function build(spec, opts) {
|
|
|
538
558
|
* var ok = await pki.pkcs12.verifyMac(p12, 'changeit');
|
|
539
559
|
*/
|
|
540
560
|
async function verifyMac(pfx, password, opts) {
|
|
561
|
+
return _verifyMacOfStore(_storeFromBytes(pfx), password, opts);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// The MAC computation over a store this module has ALREADY parsed from bytes. Separate from the
|
|
565
|
+
// public verb so `open` can verify the store it just parsed without going back through the door --
|
|
566
|
+
// the door's job is to establish that the store came from the caller's bytes, and by here it has.
|
|
567
|
+
async function _verifyMacOfStore(m, password, opts) {
|
|
541
568
|
opts = opts || {};
|
|
542
|
-
var m = (pfx && pfx.integrityMode !== undefined && pfx.mac !== undefined) ? pfx : schemaPkcs12.parse(_coerceDer(pfx, "pfx"));
|
|
543
569
|
if (m.integrityMode !== "password" || !m.mac) throw _err("pkcs12/bad-input", "the store carries no password MAC (integrityMode " + m.integrityMode + ")");
|
|
544
570
|
var expected = m.mac.macValue;
|
|
545
571
|
var computed;
|
|
@@ -600,8 +626,9 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
|
600
626
|
* (public-key privacy) safe with `opts.recipientKey` (RFC 7292 sec. 3.1, via `pki.cms.decrypt`) -- returning a
|
|
601
627
|
* structured bundle `{ integrityMode, macVerified, signers, keys, certs, crls, secrets }` -- each private key as PKCS#8
|
|
602
628
|
* `PrivateKeyInfo` DER (re-validated), each certificate / CRL / secret as raw DER, all carrying their
|
|
603
|
-
* `friendlyName` / `localKeyId` for pairing. `pfx` is
|
|
604
|
-
* `pki.schema.pkcs12.parse` result
|
|
629
|
+
* `friendlyName` / `localKeyId` for pairing. `pfx` is the store's DER `Buffer`, a PEM string, or an
|
|
630
|
+
* unmodified `pki.schema.pkcs12.parse` result; a REBUILT parsed store is refused, since the bytes whose
|
|
631
|
+
* integrity is checked and the bags returned as checked are separate properties of it.
|
|
605
632
|
*
|
|
606
633
|
* A MAC-less store is refused (`pkcs12/no-integrity`) unless `opts.allowUnauthenticated` is set. A public-key
|
|
607
634
|
* integrity store is verified through `pki.cms.verify` before any bag is trusted; a signature failure is
|
|
@@ -635,7 +662,7 @@ async function open(pfx, password, opts) {
|
|
|
635
662
|
if (opts.maxIterations != null && (typeof opts.maxIterations !== "number" || !isFinite(opts.maxIterations) || opts.maxIterations < 1 || Math.floor(opts.maxIterations) !== opts.maxIterations)) {
|
|
636
663
|
throw _err("pkcs12/bad-input", "maxIterations must be a positive integer");
|
|
637
664
|
}
|
|
638
|
-
var m = (pfx
|
|
665
|
+
var m = _storeFromBytes(pfx);
|
|
639
666
|
var macVerified = false;
|
|
640
667
|
var signers = null;
|
|
641
668
|
if (m.integrityMode === "public-key") {
|
|
@@ -649,7 +676,7 @@ async function open(pfx, password, opts) {
|
|
|
649
676
|
if (!res.valid) throw _err("pkcs12/signature-invalid", "the PKCS#12 SignedData signature did not verify (an untrusted or tampered store)");
|
|
650
677
|
signers = res.signers;
|
|
651
678
|
} else if (m.integrityMode === "password") {
|
|
652
|
-
macVerified = await
|
|
679
|
+
macVerified = await _verifyMacOfStore(m, password, opts);
|
|
653
680
|
if (!macVerified) throw _err("pkcs12/mac-mismatch", "the PKCS#12 MAC did not verify (wrong password or a tampered store)");
|
|
654
681
|
} else if (!opts.allowUnauthenticated) {
|
|
655
682
|
throw _err("pkcs12/no-integrity", "the store carries no integrity MAC (integrityMode " + m.integrityMode + "); set opts.allowUnauthenticated to open it anyway");
|
package/lib/pki-build.js
CHANGED
|
@@ -395,4 +395,15 @@ function tbsNameField(cert, which) {
|
|
|
395
395
|
return tbs.children[(hasVersion ? 1 : 0) + (which === "subject" ? 4 : 2)].bytes;
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
|
|
398
|
+
// The serialNumber of a parsed X.509 certificate, read from the SIGNED bytes rather than off the
|
|
399
|
+
// object. Same tbs layout as above, and the same reason: where a producer binds an identity to an
|
|
400
|
+
// existing certificate, that identity is issuer AND serial together. Deriving one from the bytes
|
|
401
|
+
// and reading the other from the object lets the halves name different certificates -- a Holder
|
|
402
|
+
// whose issuer is genuine and whose serial is whatever the caller wrote.
|
|
403
|
+
function tbsSerialNumber(cert) {
|
|
404
|
+
var tbs = asn1.decode(cert.tbsBytes);
|
|
405
|
+
var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
|
|
406
|
+
return asn1.read.integer(tbs.children[hasVersion ? 1 : 0]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
module.exports = { makeBuilder: makeBuilder, KU_BIT: KU_BIT, tbsNameField: tbsNameField, tbsSerialNumber: tbsSerialNumber };
|
package/lib/schema-crl.js
CHANGED
|
@@ -217,7 +217,13 @@ var CERTIFICATE_LIST = pkix.signedEnvelope(NS, TBS_CERTLIST, {
|
|
|
217
217
|
* var crl = pki.schema.crl.parse(der);
|
|
218
218
|
* crl.revokedCertificates[0].serialNumberHex; // -> "0a3f"
|
|
219
219
|
*/
|
|
220
|
-
|
|
220
|
+
// Recording, for the same reason the certificate parser is: a CRL is one signature over one byte
|
|
221
|
+
// range, and a parsed CRL presents that range and the revocation list it encodes as separate
|
|
222
|
+
// properties. Keep a correctly signed CRL's `tbsBytes` and signature and empty `revokedCertificates`
|
|
223
|
+
// and the signature still verifies while the revocation answer comes from the edited list -- a
|
|
224
|
+
// revoked certificate reported as good. The scope fields (`crlExtensions`, an IDP, a delta
|
|
225
|
+
// indicator) fail the same way. The verdict verbs re-parse from what is recorded here.
|
|
226
|
+
var parse = pkix.makeRecordingParser({ pemLabel: "X509 CRL", PemError: PemError, ErrorClass: CrlError, prefix: "crl", what: "CRL", topSchema: CERTIFICATE_LIST, ns: NS }, "crl");
|
|
221
227
|
|
|
222
228
|
/**
|
|
223
229
|
* @primitive pki.schema.crl.pemDecode
|