@blamejs/pki 0.5.4 → 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 +22 -0
- package/MIGRATING.md +31 -0
- 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/crl-sign.js +19 -7
- 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 +88 -44
- 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 +14 -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/validator-cose.js +86 -1
- package/lib/validator-tpm.js +8 -3
- package/lib/webauthn-mds.js +10 -18
- 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/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
|
@@ -1178,7 +1178,10 @@ async function validate(path, opts) {
|
|
|
1178
1178
|
// crypto amplification from an oversized path). Entry-point tier: throw.
|
|
1179
1179
|
var maxCerts = guard.limits.cap(opts.maxPathCerts, "validate: opts.maxPathCerts", constants.LIMITS.PATH_MAX_CERTS, { E: E, code: "path/bad-input", min: 1 });
|
|
1180
1180
|
if (path.length > maxCerts) throw E("path/bad-input", "validate: the certification path has " + path.length + " certificates, exceeding the maxPathCerts limit (" + maxCerts + ")");
|
|
1181
|
-
|
|
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 + "]"); });
|
|
1182
1185
|
var n = certs.length;
|
|
1183
1186
|
if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
|
|
1184
1187
|
if (!opts.trustAnchor) throw E("path/bad-input", "validate: a trustAnchor is required");
|
|
@@ -1856,7 +1859,16 @@ function selectDelta(candidates) {
|
|
|
1856
1859
|
* typeof checker.check; // "function"
|
|
1857
1860
|
*/
|
|
1858
1861
|
function crlChecker(crls, opts) {
|
|
1859
|
-
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
|
+
});
|
|
1860
1872
|
// RFC 5280 sec. 6.3.1(b): use-deltas is an INPUT to the algorithm. Default ON --
|
|
1861
1873
|
// a caller holding a delta wants it used -- and turning it off never makes a
|
|
1862
1874
|
// verdict weaker, only less determined.
|
|
@@ -2296,7 +2308,10 @@ cmsVerify.setEngine({ build: build, validate: validate, toAnchor: toAnchor,
|
|
|
2296
2308
|
* typeof checker.check; // "function"
|
|
2297
2309
|
*/
|
|
2298
2310
|
function ocspChecker(responses) {
|
|
2299
|
-
|
|
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); });
|
|
2300
2315
|
return {
|
|
2301
2316
|
check: async function (cert, issuer, ctx) {
|
|
2302
2317
|
var time = ctx.time;
|
|
@@ -2346,14 +2361,14 @@ function ocspChecker(responses) {
|
|
|
2346
2361
|
|
|
2347
2362
|
/**
|
|
2348
2363
|
* @primitive pki.path.verifyOcspResponse
|
|
2349
|
-
* @signature pki.path.verifyOcspResponse(
|
|
2364
|
+
* @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, reason }>
|
|
2350
2365
|
* @since 0.2.22
|
|
2351
2366
|
* @status stable
|
|
2352
2367
|
* @spec RFC 6960
|
|
2353
2368
|
* @related pki.ocsp.verify, pki.path.ocspChecker
|
|
2354
2369
|
*
|
|
2355
|
-
* Verify a single
|
|
2356
|
-
* 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
|
|
2357
2372
|
* `pki.ocsp.verify` composes after parsing its inputs (most callers want that
|
|
2358
2373
|
* ergonomic entry, which also handles DER/PEM decoding and request-nonce
|
|
2359
2374
|
* matching). It runs the EXACT SAME gates the path validator's `ocspChecker`
|
|
@@ -2374,6 +2389,16 @@ function ocspChecker(responses) {
|
|
|
2374
2389
|
* `time` either way. `time` must be a valid `Date`. A malformed response's parse
|
|
2375
2390
|
* fault surfaces as the parser's typed `ocsp/*` / `asn1/*` error.
|
|
2376
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
|
+
*
|
|
2377
2402
|
* @example
|
|
2378
2403
|
* var ca = await pki.key.generate("Ed25519");
|
|
2379
2404
|
* var caKey = await pki.key.export(ca.privateKey);
|
|
@@ -2389,11 +2414,49 @@ function ocspChecker(responses) {
|
|
|
2389
2414
|
* { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
|
|
2390
2415
|
* { cert: caDer, key: caKey });
|
|
2391
2416
|
* var cert = pki.schema.x509.parse(leafDer), issuerCert = pki.schema.x509.parse(caDer);
|
|
2392
|
-
* var
|
|
2393
|
-
* var v = await pki.path.verifyOcspResponse(resp, cert, issuerCert, new Date());
|
|
2417
|
+
* var v = await pki.path.verifyOcspResponse(der, cert, issuerCert, new Date());
|
|
2394
2418
|
* v.status; // "good" | "revoked" | "unknown"
|
|
2395
2419
|
*/
|
|
2396
|
-
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) {
|
|
2397
2460
|
opts = opts || {};
|
|
2398
2461
|
// The currency + responder-cert validity windows compare against `time`; a missing or invalid
|
|
2399
2462
|
// check date must fail closed (a NaN compares false against every bound), never silently pass.
|
|
@@ -2451,41 +2514,22 @@ function nameMatchSoft(rdnsA, rdnsB) {
|
|
|
2451
2514
|
catch (_e) { return false; }
|
|
2452
2515
|
}
|
|
2453
2516
|
|
|
2454
|
-
// A
|
|
2455
|
-
// the
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
//
|
|
2459
|
-
// every
|
|
2460
|
-
//
|
|
2461
|
-
//
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
!!o.subjectPublicKeyInfo && Buffer.isBuffer(o.subjectPublicKeyInfo.bytes) &&
|
|
2471
|
-
!!o.subjectPublicKeyInfo.algorithm && typeof o.subjectPublicKeyInfo.algorithm.oid === "string" &&
|
|
2472
|
-
!!o.subjectPublicKeyInfo.publicKey && Buffer.isBuffer(o.subjectPublicKeyInfo.publicKey.bytes) &&
|
|
2473
|
-
typeof o.subjectPublicKeyInfo.publicKey.unusedBits === "number" &&
|
|
2474
|
-
Array.isArray(o.extensions) && o.extensions.every(_isExtensionEntry);
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
function coerceCert(input) {
|
|
2478
|
-
// An already-parsed certificate is passed through; a DER Buffer / PEM string is
|
|
2479
|
-
// parsed (and its typed error normalized by the caller). A claimed-parsed object
|
|
2480
|
-
// (a truthy tbsBytes) must carry the COMPLETE parsed-certificate shape build and
|
|
2481
|
-
// the validate hand-off dereference -- a bare { tbsBytes } object, or any partial
|
|
2482
|
-
// shape, fails closed here as a typed PathError rather than a raw TypeError deeper
|
|
2483
|
-
// in the walk or inside validate.
|
|
2484
|
-
if (input && typeof input === "object" && !Buffer.isBuffer(input) && input.tbsBytes !== undefined) {
|
|
2485
|
-
if (!_isParsedCert(input)) throw E("path/bad-input", "build: an input has tbsBytes but is not a well-formed parsed certificate");
|
|
2486
|
-
return input;
|
|
2487
|
-
}
|
|
2488
|
-
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");
|
|
2489
2533
|
}
|
|
2490
2534
|
|
|
2491
2535
|
// A trust-store entry is either a ready anchor tuple { name, publicKey,
|
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
|
package/lib/schema-ocsp.js
CHANGED
|
@@ -517,7 +517,12 @@ var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemErro
|
|
|
517
517
|
* res.responseStatus.name; // -> "successful"
|
|
518
518
|
* res.basicResponse.responses[0].certStatus.type; // -> "good" | "revoked" | "unknown"
|
|
519
519
|
*/
|
|
520
|
-
|
|
520
|
+
// The result records the input it was derived from. A verdict verb reads the
|
|
521
|
+
// signature, the algorithm that verifies it and the bytes it covers off this object as
|
|
522
|
+
// three separate properties, and re-derives all three from the recorded input rather
|
|
523
|
+
// than trusting the object it was handed -- so an object rebuilt or edited after
|
|
524
|
+
// parsing cannot make the three describe different responses.
|
|
525
|
+
var parseResponse = pkix.makeRecordingParser({ pemLabel: "OCSP RESPONSE", PemError: PemError, ErrorClass: OcspError, prefix: "ocsp", what: "OCSP response", topSchema: OCSP_RESPONSE, ns: NS }, "ocspResponse");
|
|
521
526
|
|
|
522
527
|
/**
|
|
523
528
|
* @primitive pki.schema.ocsp.pemDecode
|
package/lib/schema-pkcs12.js
CHANGED
|
@@ -523,10 +523,15 @@ function _buildBag(rec, state, ctx) {
|
|
|
523
523
|
* var store = pki.schema.pkcs12.parse(der);
|
|
524
524
|
* store.safeBags.map(function (b) { return b.type; });
|
|
525
525
|
*/
|
|
526
|
-
|
|
526
|
+
// The result records the input it was derived from. pki.pkcs12.verifyMac and open read
|
|
527
|
+
// the MACed byte range and the bags returned as verified off this object as two
|
|
528
|
+
// separate properties, and re-derive both from the recorded input rather than trusting
|
|
529
|
+
// the object -- so "verify this" and "return that" cannot come to name different
|
|
530
|
+
// stores, whether by rebuilding the object or by editing it after parsing.
|
|
531
|
+
var parse = pkix.makeRecordingParser({
|
|
527
532
|
pemLabel: "PKCS12", PemError: PemError, ErrorClass: Pkcs12Error,
|
|
528
533
|
prefix: "pkcs12", what: "PFX", topSchema: PFX, ns: NS, ber: true,
|
|
529
|
-
});
|
|
534
|
+
}, "pkcs12Store");
|
|
530
535
|
|
|
531
536
|
/**
|
|
532
537
|
* @primitive pki.schema.pkcs12.pemDecode
|
package/lib/schema-pkix.js
CHANGED
|
@@ -1556,6 +1556,19 @@ function makeParser(opts) {
|
|
|
1556
1556
|
return function (input) { return runParse(input, opts); };
|
|
1557
1557
|
}
|
|
1558
1558
|
|
|
1559
|
+
// makeRecordingParser(opts, kind) -> a parser that also records the bytes it read.
|
|
1560
|
+
//
|
|
1561
|
+
// The pairing is here rather than repeated per format because it is one decision, not four: a
|
|
1562
|
+
// structure whose parse feeds a VERDICT needs its fields bound to the byte string they came from,
|
|
1563
|
+
// since a parsed structure presents the signed range, the signature and the fields that range
|
|
1564
|
+
// encodes as separate properties -- and a caller can hand back a genuine range beside substituted
|
|
1565
|
+
// fields. `kind` is the tag the matching door re-derives under, so a certificate cannot be presented
|
|
1566
|
+
// where a CRL is expected. The record lives in guard-parsed, off the object, unreachable through it.
|
|
1567
|
+
function makeRecordingParser(opts, kind) {
|
|
1568
|
+
return guard.parsed.recordingParser(kind, makeParser(opts), opts.ErrorClass,
|
|
1569
|
+
opts.prefix + "/bad-input", "a " + opts.what);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1559
1572
|
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
|
|
1560
1573
|
// SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
|
|
1561
1574
|
// signatureValue BIT STRING } shared by Certificate, CertificateList and
|
|
@@ -1633,6 +1646,7 @@ module.exports = {
|
|
|
1633
1646
|
pbmac1Params: pbmac1Params,
|
|
1634
1647
|
spki: spki,
|
|
1635
1648
|
makeParser: makeParser,
|
|
1649
|
+
makeRecordingParser: makeRecordingParser,
|
|
1636
1650
|
signedEnvelopeTbs: signedEnvelopeTbs,
|
|
1637
1651
|
rootSequenceChildren: rootSequenceChildren,
|
|
1638
1652
|
assertPolicyQualifiers: assertPolicyQualifiers,
|
package/lib/schema-x509.js
CHANGED
|
@@ -231,7 +231,20 @@ var CERTIFICATE = pkix.signedEnvelope(NS, CERTIFICATE_TBS, {
|
|
|
231
231
|
* cert.validity.notAfter; // Date
|
|
232
232
|
* cert.signatureAlgorithm.name; // "Ed25519" (the algorithm the issuer signed with)
|
|
233
233
|
*/
|
|
234
|
-
|
|
234
|
+
// The parser RECORDS the bytes it read, off the returned object, so a verb that computes a verdict
|
|
235
|
+
// can re-derive from them instead of trusting the object it was handed.
|
|
236
|
+
//
|
|
237
|
+
// Completeness alone cannot carry a certificate's meaning. A certificate is one signature over one
|
|
238
|
+
// byte range, but a parsed certificate presents that range (`tbsBytes`), the signature, and every
|
|
239
|
+
// field the range encodes as separate properties. Keep a real CA certificate's signed bytes and
|
|
240
|
+
// signature and replace only `subjectPublicKeyInfo`, and the signature check still passes over the
|
|
241
|
+
// original range while the substituted key is what gets used to verify the next certificate in the
|
|
242
|
+
// chain -- a forged chain out of a genuine certificate. Emptying `extensions` is the same move
|
|
243
|
+
// against basicConstraints, keyUsage, name constraints, and the unknown-critical rule.
|
|
244
|
+
//
|
|
245
|
+
// Recording the source is what makes the object safe to accept: the verdict verbs parse it again
|
|
246
|
+
// from these bytes, so anything done to the object afterwards is discarded rather than believed.
|
|
247
|
+
var parse = pkix.makeRecordingParser({ pemLabel: "CERTIFICATE", PemError: PemError, ErrorClass: CertificateError, prefix: "x509", what: "certificate", topSchema: CERTIFICATE, ns: NS }, "certificate");
|
|
235
248
|
|
|
236
249
|
// matches(root): does the decoded DER look like a Certificate? A CSR and a CRL
|
|
237
250
|
// share the outer SEQUENCE-of-3 envelope, so the discriminator is inside the
|
package/lib/sign-scheme.js
CHANGED
|
@@ -22,6 +22,7 @@ var pkcs8 = require("./schema-pkcs8");
|
|
|
22
22
|
var webcrypto = require("./webcrypto");
|
|
23
23
|
var subtle = webcrypto.webcrypto.subtle;
|
|
24
24
|
var validator = require("./validator-all");
|
|
25
|
+
var guard = require("./guard-all");
|
|
25
26
|
var compositeSig = require("./composite-sig");
|
|
26
27
|
var b = asn1.build;
|
|
27
28
|
function O(name) { return oid.byName(name); }
|
|
@@ -213,12 +214,28 @@ function _importKey(key, imp, E) {
|
|
|
213
214
|
// signs with. One whose material this process cannot reach is refused with that as the reason.
|
|
214
215
|
return webcrypto.adoptKey(key, imp, ["sign"], E, "bad-input");
|
|
215
216
|
}
|
|
216
|
-
|
|
217
|
+
// A Uint8Array is copied here and a PEM string is decoded here, so both leave a SECOND copy of a
|
|
218
|
+
// private key that nothing else can reach -- wiped once the engine has imported it, which is the
|
|
219
|
+
// only thing that reads it. A caller's own Buffer is passed through untouched: they hold a live
|
|
220
|
+
// reference and will use it again, so clearing it would destroy their key.
|
|
221
|
+
var der, owned = false;
|
|
217
222
|
if (Buffer.isBuffer(key)) der = key;
|
|
218
|
-
else if (key instanceof Uint8Array) der = Buffer.from(key);
|
|
219
|
-
else if (typeof key === "string") {
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
else if (key instanceof Uint8Array) { der = Buffer.from(key); owned = true; }
|
|
224
|
+
else if (typeof key === "string") {
|
|
225
|
+
try { der = pkcs8.pemDecode(key); }
|
|
226
|
+
catch (e) { throw E("bad-input", "the signer PEM private key could not be decoded", e); }
|
|
227
|
+
owned = true;
|
|
228
|
+
} else throw E("bad-input", "a signer key must be a CryptoKey, a PKCS#8 DER Buffer, or a PKCS#8 PEM string");
|
|
229
|
+
var imported = subtle.importKey("pkcs8", der, imp, false, ["sign"]);
|
|
230
|
+
if (!owned) return imported;
|
|
231
|
+
// Wiped on the reject path too: a malformed key is not a way to leave the copy in memory.
|
|
232
|
+
return imported.then(function (k) {
|
|
233
|
+
guard.secret.zeroize(der, E, "bad-input", "the signer private-key copy");
|
|
234
|
+
return k;
|
|
235
|
+
}, function (e) {
|
|
236
|
+
guard.secret.zeroize(der, E, "bad-input", "the signer private-key copy");
|
|
237
|
+
throw e;
|
|
238
|
+
});
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
// signOverTbs(scheme, key, signedBytes, E) -> Promise<Buffer> the raw signature over signedBytes.
|
package/lib/smime.js
CHANGED
|
@@ -44,6 +44,43 @@ var SmimeError = frameworkError.SmimeError;
|
|
|
44
44
|
|
|
45
45
|
function _err(code, msg, cause) { return new SmimeError(code, msg, cause); }
|
|
46
46
|
|
|
47
|
+
// ---- the option surface each verb accepts -----------------------------------
|
|
48
|
+
//
|
|
49
|
+
// A misspelled option is the one input that reads as an omission rather than as a value: nothing is
|
|
50
|
+
// out of range, nothing fails to parse, and the caller who asked for something stricter silently
|
|
51
|
+
// gets the looser default. `protectHeaders` misspelled sends the headers a caller meant to protect
|
|
52
|
+
// as ordinary display copies; `strictMicalg` misspelled accepts the mismatch it was set to reject;
|
|
53
|
+
// `entity` misspelled wraps a caller's complete MIME entity inside another one.
|
|
54
|
+
//
|
|
55
|
+
// The tables are per VERB, not per module, because the surfaces genuinely differ -- `form` means
|
|
56
|
+
// something on sign and nothing on encrypt -- and a merged table would accept each verb's options at
|
|
57
|
+
// every other one, which is the same silence in a wider form. Each is the keys that verb's body and
|
|
58
|
+
// the helpers it hands `opts` to actually read: the value goes through _entityBytes, _cmsSignOpts or
|
|
59
|
+
// _cmsEncryptOpts as readily as it is read here, so a table built from the verb's own lines alone
|
|
60
|
+
// would refuse options that work.
|
|
61
|
+
var SIGN_OPTS = {
|
|
62
|
+
form: 1, entity: 1, contentType: 1, signingTime: 1, protectHeaders: 1, headers: 1, hcp: 1,
|
|
63
|
+
sid: 1, signedAttributes: 1, additionalSignedAttributes: 1,
|
|
64
|
+
};
|
|
65
|
+
var VERIFY_OPTS = {
|
|
66
|
+
certs: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, strictMicalg: 1,
|
|
67
|
+
legacyHeaderProtection: 1,
|
|
68
|
+
};
|
|
69
|
+
var ENCRYPT_OPTS = {
|
|
70
|
+
entity: 1, contentType: 1, protectHeaders: 1, headers: 1, hcp: 1,
|
|
71
|
+
contentEncryptionAlgorithm: 1, oaepHash: 1, keyIdentifier: 1, ukm: 1,
|
|
72
|
+
};
|
|
73
|
+
var DECRYPT_OPTS = { recipientIndex: 1, maxIterations: 1, strictSmimeType: 1, legacyHeaderProtection: 1 };
|
|
74
|
+
var COMPRESS_OPTS = { entity: 1, contentType: 1, level: 1 };
|
|
75
|
+
var DECOMPRESS_OPTS = { maxOutputBytes: 1 };
|
|
76
|
+
|
|
77
|
+
function _knownOpts(opts, known, verb) {
|
|
78
|
+
guard.identifier.assertKnownKeys(opts, known, _err, "smime/bad-input", function (k) {
|
|
79
|
+
return "unknown option " + JSON.stringify(k) + " for pki.smime." + verb + " -- accepted: " +
|
|
80
|
+
Object.keys(known).sort().join(", ");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
// RFC 8551 uses application/pkcs7-<kind>; OpenSSL's legacy `smime` command emits the PKCS#7
|
|
48
85
|
// application/x-pkcs7-<kind>. Accept both on the RECEIVE side (we always EMIT the RFC 8551 form).
|
|
49
86
|
function _isPkcs7(type, kind) {
|
|
@@ -520,6 +557,10 @@ function _base64Body(der) {
|
|
|
520
557
|
* @opts signingTime a `Date` for the CMS signing-time attribute, or false to omit it.
|
|
521
558
|
* @opts protectHeaders enable RFC 9788 header protection (`hp="clear"`) -- inline `opts.headers` on the signed payload + the outer display headers.
|
|
522
559
|
* @opts headers the Non-Structural fields to protect + display: an object `{ Name: value }` or an array `[{ name, value }]` (Subject / From / To / Date / ...); used with `protectHeaders`.
|
|
560
|
+
* @opts hcp the Header Confidentiality Policy applied to the OUTER display copies: `"hcp_baseline"` (default) or `"hcp_no_confidentiality"`. A signed message's payload is not encrypted, so this governs presentation, not secrecy; used with `protectHeaders`.
|
|
561
|
+
* @opts sid forwarded to cms.sign: the SignerIdentifier form, `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
|
|
562
|
+
* @opts signedAttributes forwarded: `false` omits the signed-attributes set entirely (a bare-signature SignedData).
|
|
563
|
+
* @opts additionalSignedAttributes forwarded: extra signed attributes to carry, as `[{ oid, values }]`.
|
|
523
564
|
* @example
|
|
524
565
|
* var pair = await pki.key.generate("Ed25519");
|
|
525
566
|
* var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
|
|
@@ -529,6 +570,7 @@ function _base64Body(der) {
|
|
|
529
570
|
*/
|
|
530
571
|
async function sign(content, signers, opts) {
|
|
531
572
|
opts = opts || {};
|
|
573
|
+
_knownOpts(opts, SIGN_OPTS, "sign");
|
|
532
574
|
// RFC 9788 header protection (opts.protectHeaders): the inner Cryptographic Payload gains hp="clear" +
|
|
533
575
|
// the inlined protected fields, and the outer frame carries the display copies. Off by default => the
|
|
534
576
|
// shipped path, byte-for-byte.
|
|
@@ -624,6 +666,7 @@ function _capped(msg) {
|
|
|
624
666
|
*/
|
|
625
667
|
async function verify(message, opts) {
|
|
626
668
|
opts = opts || {};
|
|
669
|
+
_knownOpts(opts, VERIFY_OPTS, "verify");
|
|
627
670
|
var ent = mime.parse(message, SmimeError, "smime/bad-mime");
|
|
628
671
|
var ct = ent.contentType;
|
|
629
672
|
// Forwarded, not re-decided here. This verb documents itself as pki.cms.verify's verdict plus the
|
|
@@ -762,6 +805,7 @@ function _cmsEncryptOpts(opts) {
|
|
|
762
805
|
*/
|
|
763
806
|
async function encrypt(content, recipients, opts) {
|
|
764
807
|
opts = opts || {};
|
|
808
|
+
_knownOpts(opts, ENCRYPT_OPTS, "encrypt");
|
|
765
809
|
// RFC 9788 header protection (opts.protectHeaders): the inner payload carries hp="cipher" + the REAL
|
|
766
810
|
// headers (inside the ciphertext); the outer frame carries only the Header-Confidentiality-Policy-processed
|
|
767
811
|
// display copies (hcp_baseline obscures Subject to [...], removes Comments/Keywords). Off => shipped path.
|
|
@@ -824,6 +868,7 @@ async function encrypt(content, recipients, opts) {
|
|
|
824
868
|
*/
|
|
825
869
|
async function decrypt(message, keyMaterial, opts) {
|
|
826
870
|
opts = opts || {};
|
|
871
|
+
_knownOpts(opts, DECRYPT_OPTS, "decrypt");
|
|
827
872
|
var ent = mime.parse(message, SmimeError, "smime/bad-mime");
|
|
828
873
|
var ct = ent.contentType;
|
|
829
874
|
if (!_isPkcs7(ct.type, "mime")) throw _err("smime/unsupported-type", "not an encrypted S/MIME message (Content-Type " + JSON.stringify(ct.type) + ")");
|
|
@@ -870,6 +915,7 @@ async function decrypt(message, keyMaterial, opts) {
|
|
|
870
915
|
*/
|
|
871
916
|
async function compress(content, opts) {
|
|
872
917
|
opts = opts || {};
|
|
918
|
+
_knownOpts(opts, COMPRESS_OPTS, "compress");
|
|
873
919
|
var entity = _entityBytes(content, opts);
|
|
874
920
|
var cOpts = {};
|
|
875
921
|
if (opts.level !== undefined) cOpts.level = opts.level;
|
|
@@ -903,6 +949,7 @@ async function compress(content, opts) {
|
|
|
903
949
|
*/
|
|
904
950
|
async function decompress(message, opts) {
|
|
905
951
|
opts = opts || {};
|
|
952
|
+
_knownOpts(opts, DECOMPRESS_OPTS, "decompress");
|
|
906
953
|
var ent = mime.parse(message, SmimeError, "smime/bad-mime");
|
|
907
954
|
var ct = ent.contentType;
|
|
908
955
|
if (!_isPkcs7(ct.type, "mime")) throw _err("smime/unsupported-type", "not a compressed S/MIME message (Content-Type " + JSON.stringify(ct.type) + ")");
|