@blamejs/pki 0.9.5 → 0.9.7
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 +66 -0
- package/README.md +7 -6
- package/bin/pki.js +110 -34
- package/lib/acme.js +91 -46
- package/lib/asn1-der.js +6 -5
- package/lib/attrcert-sign.js +4 -3
- package/lib/cmp-build.js +16 -6
- package/lib/cmp-session.js +26 -3
- package/lib/cmp-verify.js +24 -2
- package/lib/cms-compress.js +3 -3
- package/lib/cms-digest.js +3 -3
- package/lib/cms-verify.js +18 -8
- package/lib/compromised-keys.js +114 -0
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +3 -3
- package/lib/crmf-sign.js +2 -0
- package/lib/csr-sign.js +5 -1
- package/lib/ct.js +16 -3
- package/lib/est.js +44 -29
- package/lib/framework-error.js +20 -7
- package/lib/guard-all.js +2 -0
- package/lib/guard-distrust.js +344 -0
- package/lib/guard-json.js +1 -1
- package/lib/guard-verdict.js +16 -3
- package/lib/http-transport.js +412 -41
- package/lib/jose.js +1 -1
- package/lib/lint.js +49 -7
- package/lib/ocsp-verify.js +9 -3
- package/lib/ocsp.js +8 -4
- package/lib/oid.js +4 -0
- package/lib/path-validate.js +131 -16
- package/lib/possession.js +6 -2
- package/lib/related-cert.js +2 -2
- package/lib/revocation-fetch.js +39 -9
- package/lib/scep.js +28 -13
- package/lib/schema-pkix.js +3 -3
- package/lib/sigstore.js +11 -6
- package/lib/smime.js +18 -13
- package/lib/tls-cert-compress.js +6 -5
- package/lib/trust.js +159 -20
- package/lib/trustanchor-build.js +16 -1
- package/lib/tsp-sign.js +7 -1
- package/lib/webauthn-mds.js +30 -8
- package/lib/webauthn.js +46 -14
- package/lib/x509-sign.js +16 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/revocation-fetch.js
CHANGED
|
@@ -398,7 +398,8 @@ async function _tryOcsp(state, cert, issuer, ctx, candidate) {
|
|
|
398
398
|
hit = got.body;
|
|
399
399
|
}
|
|
400
400
|
var v = await state.verifyOcspResponse(hit, cert, issuerCert, ctx.time,
|
|
401
|
-
{ historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined
|
|
401
|
+
{ historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined,
|
|
402
|
+
distrustedKeys: state.distrusted });
|
|
402
403
|
/** @internal `valid` on that verdict means the certificate is good, not that the response was
|
|
403
404
|
* acceptable, so reading it as acceptance would turn a `revoked` answer into an undetermined
|
|
404
405
|
* one that `opts.softFail` could then waive. Acceptance is the three conditions the gate
|
|
@@ -695,6 +696,21 @@ function _deltaUrlsFor(parsedCrl, state) {
|
|
|
695
696
|
* var checker = pki.path.fetchingChecker({ transport: pki.transport.https({}) });
|
|
696
697
|
* console.log(typeof checker.check);
|
|
697
698
|
*/
|
|
699
|
+
/** @internal The TLS policy every fetch is sent with, copied once at the door, with its distrust list
|
|
700
|
+
* typed there. A fetch fault becomes an undetermined verdict, so a malformed list that reached the
|
|
701
|
+
* transport would be reported as a responder that did not answer. */
|
|
702
|
+
function _tlsSnapshot(tls) {
|
|
703
|
+
if (tls == null) return tls;
|
|
704
|
+
guard.identifier.assertKnownKeys(tls, httpTransport.TLS_KEYS, E, "path/bad-input", "fetchingChecker: opts.tls has an unknown option ");
|
|
705
|
+
/** @internal Each setting the transport reads is taken by value, whether the bag holds it or
|
|
706
|
+
* inherits it, which is how the transport itself reads the bag. */
|
|
707
|
+
var copy = guard.identifier.pickSettings(tls, intrinsic.keys(httpTransport.TLS_KEYS));
|
|
708
|
+
if (copy.distrustedKeys != null) {
|
|
709
|
+
copy.distrustedKeys = guard.distrust.keys(copy.distrustedKeys, E, "path/bad-input", "fetchingChecker: opts.tls.distrustedKeys");
|
|
710
|
+
}
|
|
711
|
+
return copy;
|
|
712
|
+
}
|
|
713
|
+
|
|
698
714
|
function fetchingChecker(opts) {
|
|
699
715
|
opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "fetchingChecker: opts");
|
|
700
716
|
guard.identifier.assertKnownKeys(opts, CHECKER_OPTS, E, "path/bad-input",
|
|
@@ -703,6 +719,7 @@ function fetchingChecker(opts) {
|
|
|
703
719
|
* accessor twice, so the transport that passed the door is the transport that gets called. */
|
|
704
720
|
var transport = guard.identifier.assertCallable(opts.transport, E, "path/bad-input",
|
|
705
721
|
"fetchingChecker: opts.transport", TRANSPORT_SHAPE);
|
|
722
|
+
// allow:caller-option-presence-tested-against-undefined -- absence selects the permissive default, so a null must not reach it
|
|
706
723
|
var allowPlaintextHttp = opts.allowPlaintextHttp === undefined ? true : opts.allowPlaintextHttp === true;
|
|
707
724
|
var cfg = {
|
|
708
725
|
transport: transport,
|
|
@@ -717,7 +734,7 @@ function fetchingChecker(opts) {
|
|
|
717
734
|
useDeltas: guard.identifier.booleanOptionDefaultOn(opts.useDeltas, E, "path/bad-input",
|
|
718
735
|
"fetchingChecker: opts.useDeltas"),
|
|
719
736
|
nonce: _booleanOption(opts.nonce, "fetchingChecker: opts.nonce"),
|
|
720
|
-
tls: opts.tls,
|
|
737
|
+
tls: _tlsSnapshot(opts.tls),
|
|
721
738
|
maxFetches: guard.limits.cap(opts.maxFetches, "fetchingChecker: opts.maxFetches", LIMITS.REVOCATION_MAX_FETCHES,
|
|
722
739
|
{ E: E, code: "path/bad-input", min: 1, max: LIMITS.REVOCATION_MAX_FETCHES }),
|
|
723
740
|
maxPerCert: guard.limits.cap(opts.maxPerCert, "fetchingChecker: opts.maxPerCert", LIMITS.REVOCATION_MAX_PER_CERT,
|
|
@@ -757,10 +774,18 @@ function fetchingChecker(opts) {
|
|
|
757
774
|
|
|
758
775
|
return {
|
|
759
776
|
check: async function (cert, issuer, ctx) {
|
|
777
|
+
/** @internal Typed before any location is decoded or fetched: a malformed list is the
|
|
778
|
+
* caller's fault and is thrown, where a fetch fault below becomes an undetermined verdict. */
|
|
779
|
+
var distrusted = guard.distrust.keys(ctx == null ? undefined : ctx.distrustedKeys, E, "path/bad-input",
|
|
780
|
+
"fetchingChecker: ctx.distrustedKeys");
|
|
781
|
+
var issuerRefusal = guard.distrust.refusal(distrusted, issuer.workingPublicKey, "distrustedKeys");
|
|
782
|
+
if (issuerRefusal !== null) {
|
|
783
|
+
return _unknown("the issuer's key " + issuerRefusal + ", so nothing it signs is an answer", [], []);
|
|
784
|
+
}
|
|
760
785
|
var notes = [], skips = [];
|
|
761
786
|
var budget = budgetFor(ctx == null ? null : ctx.run);
|
|
762
787
|
var state = intrinsic.assign(intrinsic.create(null), cfg, {
|
|
763
|
-
notes: notes, cache: cache, crlCache: crlCache,
|
|
788
|
+
notes: notes, cache: cache, crlCache: crlCache, distrusted: distrusted,
|
|
764
789
|
deadline: budget.deadline,
|
|
765
790
|
fetches: budget.fetches,
|
|
766
791
|
urls: new intrinsic.Set(),
|
|
@@ -787,7 +812,9 @@ function fetchingChecker(opts) {
|
|
|
787
812
|
* number of retries. A responder that answers `revoked` or `good` ends it; anything else
|
|
788
813
|
* falls through, because sec. 5 of RFC 6960 makes the CRL the fallback and never an
|
|
789
814
|
* assumption of good standing. */
|
|
790
|
-
|
|
815
|
+
/** @internal Each source keeps its own last failure, so an undetermined verdict names the
|
|
816
|
+
* responder's fault and the CRL's. They usually have different causes. */
|
|
817
|
+
var ocspReason = null, crlReason = null;
|
|
791
818
|
for (var a = 0; a <= cfg.retries && ocspCands.length > 0; a++) {
|
|
792
819
|
for (var o = 0; o < ocspCands.length; o++) {
|
|
793
820
|
if (!_takeUrl(state, ocspCands[o].url)) {
|
|
@@ -797,18 +824,21 @@ function fetchingChecker(opts) {
|
|
|
797
824
|
try {
|
|
798
825
|
var got = await _tryOcsp(state, cert, issuer, ctx, ocspCands[o]);
|
|
799
826
|
if (got.status === "good" || got.status === "revoked") return _verdict(got, notes, skips);
|
|
800
|
-
|
|
801
|
-
} catch (e) {
|
|
827
|
+
ocspReason = got.reason + " reported " + got.status;
|
|
828
|
+
} catch (e) { ocspReason = _say(e); }
|
|
802
829
|
}
|
|
803
830
|
}
|
|
804
831
|
if (crlCands.length > 0) {
|
|
805
832
|
try {
|
|
806
833
|
var viaCrl = await _tryCrl(state, cert, issuer, ctx, crlCands, certDeltas);
|
|
807
834
|
if (viaCrl.status === "good" || viaCrl.status === "revoked") return _verdict(viaCrl, notes, skips);
|
|
808
|
-
|
|
809
|
-
} catch (e2) {
|
|
835
|
+
crlReason = viaCrl.reason;
|
|
836
|
+
} catch (e2) { crlReason = _say(e2); }
|
|
810
837
|
}
|
|
811
|
-
|
|
838
|
+
var why = [];
|
|
839
|
+
if (ocspReason !== null) _append(why, "OCSP: " + ocspReason);
|
|
840
|
+
if (crlReason !== null) _append(why, "CRL: " + crlReason);
|
|
841
|
+
return _unknown(why.length ? intrinsic.join(why, "; ") : "no revocation source answered", notes, skips);
|
|
812
842
|
},
|
|
813
843
|
};
|
|
814
844
|
}
|
package/lib/scep.js
CHANGED
|
@@ -689,10 +689,25 @@ function _responseLen(body) {
|
|
|
689
689
|
if (Buffer.isBuffer(body)) return guard.bytes.lengthOf(body);
|
|
690
690
|
return guard.bytes.lengthOf(guard.bytes.source(body, ScepError, "scep/bad-response", "the response body"));
|
|
691
691
|
}
|
|
692
|
-
/** @internal The settings this client forwards to its transport.
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
692
|
+
/** @internal The settings this client forwards to its transport. The distrust list is typed here, and
|
|
693
|
+
* the frozen copy is the one every hop carries. A setting not on the list is refused. */
|
|
694
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
|
|
695
|
+
var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
|
|
696
|
+
function _distrustOf(value) {
|
|
697
|
+
return guard.distrust.keys(value, _err, "scep/bad-input", "opts.tls.distrustedKeys");
|
|
698
|
+
}
|
|
699
|
+
function _tlsForRequest(tls) {
|
|
700
|
+
guard.identifier.assertKnownKeys(tls, TLS_KNOWN, _err, "scep/bad-input", "unknown opts.tls setting ");
|
|
701
|
+
var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
|
|
702
|
+
t.distrustedKeys = _distrustOf(t.distrustedKeys);
|
|
703
|
+
return t;
|
|
704
|
+
}
|
|
705
|
+
/** @internal A verb that reaches the transport only after an await copies the caller's tls at its door,
|
|
706
|
+
* with the distrust list typed there, so a later change to the caller's array does not reach the request.
|
|
707
|
+
* Each setting is read the way `_tlsForRequest` reads it, a prototype included. */
|
|
708
|
+
function _snapshotTls(tls) {
|
|
709
|
+
if (tls == null) return tls;
|
|
710
|
+
return _tlsForRequest(tls);
|
|
696
711
|
}
|
|
697
712
|
function _redirectTarget(current, location, method, allowCrossOrigin) {
|
|
698
713
|
if (location == null || _trim(String(location)) === "") throw _err("scep/bad-response", "a redirect response carried no Location header");
|
|
@@ -736,7 +751,7 @@ function _pkiRequest(method, opUrl, reqMsg, clientOpts) {
|
|
|
736
751
|
function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
737
752
|
var redirects = 0, curMethod = method, curBody = body, initialOrigin = _urlOrigin(url);
|
|
738
753
|
var proxySnap = _snapshotProxy(opts.proxy);
|
|
739
|
-
var tlsBase = _tlsForRequest(opts);
|
|
754
|
+
var tlsBase = _tlsForRequest(opts.tls);
|
|
740
755
|
/** @internal Read as the boolean it documents, on the one path every network verb here takes and
|
|
741
756
|
* before the first request. Taken by truthiness, `allowCrossOriginRedirect: "false"` ALLOWED the
|
|
742
757
|
* cross-origin redirect on an unsafe method that the check below exists to refuse. */
|
|
@@ -868,7 +883,7 @@ function _assertStrongProfile(caps, profile) {
|
|
|
868
883
|
* - `transport` -- an injected transport (defaults to `pki.transport.https`); the request is a GET with no body.
|
|
869
884
|
* - `expectSCEPStandard` -- refuse (`scep/weak-profile`) if the response does not advertise `SCEPStandard`. Takes `true`, `false`, `null` or an absent option; any other value is refused `scep/bad-input`.
|
|
870
885
|
* - `requireStrongProfile` -- refuse unless AES + SHA-256 + POSTPKIOperation are all advertised. Read the same way.
|
|
871
|
-
* - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
886
|
+
* - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
872
887
|
* @example
|
|
873
888
|
* // requires: scepTransport -- an injected transport (omit to use pki.transport.https)
|
|
874
889
|
* var caps = await pki.scep.getCACaps("http://ca.example/scep", { transport: scepTransport });
|
|
@@ -952,7 +967,7 @@ function _normalizeFingerprint(fp, algName) {
|
|
|
952
967
|
* @opts
|
|
953
968
|
* - `expectedFingerprint` -- a hex string or Buffer; a returned certificate must hash to it, or the response is refused.
|
|
954
969
|
* - `fingerprintAlgorithm` -- the hash for the fingerprint (default "sha256").
|
|
955
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
970
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
956
971
|
* @example
|
|
957
972
|
* // requires: scepCaCertTransport -- an injected transport returning a single CA certificate
|
|
958
973
|
* var ca = await pki.scep.getCACert("http://ca.example/scep", { transport: scepCaCertTransport });
|
|
@@ -1008,7 +1023,7 @@ async function getCACert(baseUrl, opts) {
|
|
|
1008
1023
|
*
|
|
1009
1024
|
* @opts
|
|
1010
1025
|
* - `caCertificate` -- the current SCEP CA certificate DER; the response is authenticated against its key (required).
|
|
1011
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1026
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1012
1027
|
* @example
|
|
1013
1028
|
* // requires: caCertDer -- the current CA certificate (from getCACert), scepTransport
|
|
1014
1029
|
* var next = await pki.scep.getNextCACert("http://ca.example/scep", { caCertificate: caCertDer, transport: scepTransport });
|
|
@@ -1114,7 +1129,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1114
1129
|
var recipientKey = _captureRecipientKey(opts.recipientKey);
|
|
1115
1130
|
var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
|
|
1116
1131
|
var clientOpts = {
|
|
1117
|
-
tls:
|
|
1132
|
+
tls: _snapshotTls(opts.tls),
|
|
1118
1133
|
maxRedirects: opts.maxRedirects,
|
|
1119
1134
|
allowCrossOriginRedirect: opts.allowCrossOriginRedirect,
|
|
1120
1135
|
timeout: opts.timeout,
|
|
@@ -1187,7 +1202,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1187
1202
|
* - `sleep` -- an injectable sleeper `(ms) -> Promise` for the Retry-After wait (default a real timer).
|
|
1188
1203
|
* - `onRetryAfter` -- an optional observer `(delaySeconds) -> void` called before each poll sleep.
|
|
1189
1204
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1190
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1205
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1191
1206
|
* @example
|
|
1192
1207
|
* // requires: scepCsr, caCertDer, clientCertDer, clientKeyPkcs8, scepTransport
|
|
1193
1208
|
* var out = await pki.scep.enroll("http://ca.example/scep", { csr: scepCsr, caCert: caCertDer,
|
|
@@ -1265,7 +1280,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1265
1280
|
var recipientKey = _captureRecipientKey(opts.recipientKey);
|
|
1266
1281
|
var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
|
|
1267
1282
|
var want = _resolveQueryTarget(opts);
|
|
1268
|
-
var clientOpts = { tls:
|
|
1283
|
+
var clientOpts = { tls: _snapshotTls(opts.tls), maxRedirects: opts.maxRedirects, allowCrossOriginRedirect: opts.allowCrossOriginRedirect, timeout: opts.timeout, maxResponseBytes: opts.maxResponseBytes, proxy: _snapshotProxy(opts.proxy) };
|
|
1269
1284
|
if (checkedTransport !== undefined) clientOpts.transport = checkedTransport;
|
|
1270
1285
|
var senderNonce = guard.crypto.randomBytes(16);
|
|
1271
1286
|
var transactionId = opts.transactionId != null ? opts.transactionId : _defaultTransactionId();
|
|
@@ -1305,7 +1320,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1305
1320
|
* - `responderCert` -- the certificate to authenticate the CA response signature against (defaults to `caCert`).
|
|
1306
1321
|
* - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
|
|
1307
1322
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1308
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1323
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1309
1324
|
* @example
|
|
1310
1325
|
* // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
|
|
1311
1326
|
* var out = await pki.scep.getCert("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
|
|
@@ -1354,7 +1369,7 @@ async function getCert(baseUrl, opts) {
|
|
|
1354
1369
|
* - `responderCert` -- the certificate to authenticate the CA response against (defaults to `caCert`).
|
|
1355
1370
|
* - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
|
|
1356
1371
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1357
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1372
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1358
1373
|
* @example
|
|
1359
1374
|
* // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
|
|
1360
1375
|
* var out = await pki.scep.getCrl("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
|
package/lib/schema-pkix.js
CHANGED
|
@@ -1557,7 +1557,7 @@ function _extensionRow(ext, opts, index, state, decoded, code, profile) {
|
|
|
1557
1557
|
row.value = ext.value;
|
|
1558
1558
|
row.scope = opts.scope;
|
|
1559
1559
|
row.index = index;
|
|
1560
|
-
row.containerIndex = opts.containerIndex
|
|
1560
|
+
row.containerIndex = opts.containerIndex == null ? null : opts.containerIndex;
|
|
1561
1561
|
row.state = state;
|
|
1562
1562
|
row.decoded = decoded;
|
|
1563
1563
|
row.code = code;
|
|
@@ -1754,7 +1754,7 @@ function extensionDoor(spec) {
|
|
|
1754
1754
|
}
|
|
1755
1755
|
|
|
1756
1756
|
function decodeExtension(ext, callerOpts) {
|
|
1757
|
-
var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope
|
|
1757
|
+
var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope == null);
|
|
1758
1758
|
var key = read.scope === undefined ? spec.defaultScope : read.scope;
|
|
1759
1759
|
if (typeof key !== "string" || !_hasOwn(spec.scopes, key)) {
|
|
1760
1760
|
throw E(badInput, "pki.schema." + spec.key + ".decodeExtension needs opts.scope naming which " +
|
|
@@ -2617,7 +2617,7 @@ function spki(ns, opts) {
|
|
|
2617
2617
|
|
|
2618
2618
|
function attribute(ns, opts) {
|
|
2619
2619
|
opts = opts || _create(null);
|
|
2620
|
-
var minValues = opts.minValues
|
|
2620
|
+
var minValues = opts.minValues == null ? 1 : opts.minValues;
|
|
2621
2621
|
return schema.seq([
|
|
2622
2622
|
schema.field("type", schema.oidLeaf()),
|
|
2623
2623
|
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: minValues, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
package/lib/sigstore.js
CHANGED
|
@@ -1244,7 +1244,7 @@ async function _verifyRekorEntry(te, rekorKeys, leafDer, contentType, arm, paylo
|
|
|
1244
1244
|
* be checked. A token that does not verify is passed over rather than refused: it is a statement from
|
|
1245
1245
|
* an authority this caller did not pin, which says nothing either way. With no token verifying, the
|
|
1246
1246
|
* caller is left with no attested instant and the verdict says so. */
|
|
1247
|
-
async function _timestampInstants(vm, sigBytes, tsaRoots) {
|
|
1247
|
+
async function _timestampInstants(vm, sigBytes, tsaRoots, distrusted) {
|
|
1248
1248
|
if (!tsaRoots) return null;
|
|
1249
1249
|
var anchors = _normTsaAnchors(tsaRoots);
|
|
1250
1250
|
if (!anchors.length) {
|
|
@@ -1271,7 +1271,7 @@ async function _timestampInstants(vm, sigBytes, tsaRoots) {
|
|
|
1271
1271
|
? _b64(t.signedTimestamp, "rfc3161Timestamps[" + i + "].signedTimestamp") : null;
|
|
1272
1272
|
if (der === null) continue;
|
|
1273
1273
|
var res;
|
|
1274
|
-
try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors }); }
|
|
1274
|
+
try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors, distrustedKeys: distrusted }); }
|
|
1275
1275
|
catch (_e) { continue; } // allow:swallow-unverified a token this caller cannot check says nothing; the next is tried and a bundle with none verifying is refused for want of an instant
|
|
1276
1276
|
if (!res || res.valid !== true || res.trusted !== true) continue;
|
|
1277
1277
|
var ms = guard.time.instantOf(res.genTime);
|
|
@@ -1402,7 +1402,7 @@ function _ctLogFor(logs, logIdBytes, timeMs) {
|
|
|
1402
1402
|
* here read the caller's array after a yield, so a caller could add an anchor while verification was
|
|
1403
1403
|
* pending and decide which CA is trusted: an empty list that grows during the timestamp check would have
|
|
1404
1404
|
* passed a chain the call was made without any anchor for. */
|
|
1405
|
-
async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
1405
|
+
async function _verifyChain(leaf, chainDers, roots, timeMs, distrusted) {
|
|
1406
1406
|
var bundleLinks = _map(_arraySlice(chainDers, 1), function (d, i) { return _parseCert(d, "bundle intermediate [" + i + "]"); });
|
|
1407
1407
|
var inWindow = _filter(roots, function (r) { return _inWindow(timeMs, r.validFor); });
|
|
1408
1408
|
if (!inWindow.length) {
|
|
@@ -1449,6 +1449,7 @@ async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
|
1449
1449
|
time: new _Date(timeMs),
|
|
1450
1450
|
historicalMode: true,
|
|
1451
1451
|
requiredEku: ["codeSigning"],
|
|
1452
|
+
distrustedKeys: distrusted,
|
|
1452
1453
|
});
|
|
1453
1454
|
} catch (e) {
|
|
1454
1455
|
if (e && e.code === "path/no-path") {
|
|
@@ -1557,7 +1558,7 @@ function _fulcioExtValue(ext, leafArc) {
|
|
|
1557
1558
|
|
|
1558
1559
|
var IDENTITY_FIELDS = ["san", "issuer", "sourceRepositoryURI"];
|
|
1559
1560
|
var IDENTITY_KEYS = _assign(_create(null), { san: 1, issuer: 1, sourceRepositoryURI: 1 });
|
|
1560
|
-
var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1 });
|
|
1561
|
+
var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1, distrustedKeys: 1 });
|
|
1561
1562
|
|
|
1562
1563
|
/** @internal The bytes a message signature covers, taken once. There is no shape here in which a
|
|
1563
1564
|
* caller hands over a precomputed digest: the arm's own field comment forbids using one as a
|
|
@@ -1702,6 +1703,7 @@ function _statement(payload, payloadType, expectedPredicate) {
|
|
|
1702
1703
|
* predicateType: string, // optional: require this in-toto predicateType (e.g. the SLSA URI); dsse_envelope bundles only
|
|
1703
1704
|
* artifact: BufferSource, // the bytes a message_signature covers; required for that arm, refused for a dsse_envelope
|
|
1704
1705
|
* tsaRoots: Array, // optional timestamp-authority anchors: a DER Buffer or { der } each; required to date a Rekor v2 bundle, which carries no signed entry timestamp
|
|
1706
|
+
* distrustedKeys: Array, // optional SHA-256 digests of SubjectPublicKeyInfo DER (64 hex characters or 32 bytes); a listed key fails the Fulcio chain, or the timestamp authority's chain so its token dates nothing
|
|
1705
1707
|
* time: Date, // optional check-date override (default: the Rekor integratedTime, or an RFC 3161 token's genTime on a v2 entry)
|
|
1706
1708
|
*
|
|
1707
1709
|
* @example
|
|
@@ -1736,6 +1738,9 @@ async function verifyBundle(bundle, opts) {
|
|
|
1736
1738
|
* verification is pending would decide a check the call was made without it. */
|
|
1737
1739
|
var fulcioRoots = _normRoots(o.fulcioRoots || []);
|
|
1738
1740
|
var ctLogs = o.ctLogs === undefined || o.ctLogs === null ? o.ctLogs : _normCtLogs(o.ctLogs);
|
|
1741
|
+
/** @internal Typed here, before the timestamp leg, because that leg passes over a token whose
|
|
1742
|
+
* verification throws, and a malformed list must be reported as the caller's input fault. */
|
|
1743
|
+
var distrusted = guard.distrust.keys(o.distrustedKeys, _err, "sigstore/bad-input", "opts.distrustedKeys");
|
|
1739
1744
|
|
|
1740
1745
|
/** @internal Each option belongs to one arm, and one supplied for the other is refused rather than
|
|
1741
1746
|
* passed over. An ignored option reads as a check that ran: opts.artifact on a DSSE bundle names
|
|
@@ -1796,7 +1801,7 @@ async function verifyBundle(bundle, opts) {
|
|
|
1796
1801
|
* SIGNATURE bytes, which is what dates the ephemeral certificate that made them; a token over the
|
|
1797
1802
|
* payload would date the thing signed rather than the signing. */
|
|
1798
1803
|
var tsaMsList = null;
|
|
1799
|
-
if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots);
|
|
1804
|
+
if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots, distrusted);
|
|
1800
1805
|
var chainDers = _chainDers(vm);
|
|
1801
1806
|
/** @internal A caller-pinned instant is the same for every candidate, so the chain it decides is
|
|
1802
1807
|
* computed on the first candidate and reused rather than rebuilt per entry. Without one the instant
|
|
@@ -1873,7 +1878,7 @@ async function verifyBundle(bundle, opts) {
|
|
|
1873
1878
|
"timestamp authority, or supply opts.time");
|
|
1874
1879
|
}
|
|
1875
1880
|
var candidateChain = pinnedChain !== null ? pinnedChain
|
|
1876
|
-
: await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime);
|
|
1881
|
+
: await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime, distrusted);
|
|
1877
1882
|
if (haveCallerTime) pinnedChain = candidateChain;
|
|
1878
1883
|
var candidateScts = await _verifyEmbeddedScts(leaf, leafDer, candidateChain.issuer, ctLogs, candidateTime, candidateBound);
|
|
1879
1884
|
rekor = candidate;
|
package/lib/smime.js
CHANGED
|
@@ -63,7 +63,7 @@ var SIGN_OPTS = Object.assign(Object.create(null), {
|
|
|
63
63
|
});
|
|
64
64
|
var VERIFY_OPTS = Object.assign(Object.create(null), {
|
|
65
65
|
certs: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, strictMicalg: 1,
|
|
66
|
-
legacyHeaderProtection: 1, expectedSender: 1, allowWeakDigests: 1,
|
|
66
|
+
legacyHeaderProtection: 1, expectedSender: 1, allowWeakDigests: 1, distrustedKeys: 1,
|
|
67
67
|
});
|
|
68
68
|
var ENCRYPT_OPTS = Object.assign(Object.create(null), {
|
|
69
69
|
entity: 1, contentType: 1, protectHeaders: 1, headers: 1, hcp: 1,
|
|
@@ -591,9 +591,9 @@ function _hpSurface(content, outerEnt, expectedMode, authenticated, legacyEnable
|
|
|
591
591
|
|
|
592
592
|
function _cmsSignOpts(opts, detached) {
|
|
593
593
|
var o = { detached: detached };
|
|
594
|
-
if (opts.signingTime
|
|
594
|
+
if (opts.signingTime != null) o.signingTime = opts.signingTime;
|
|
595
595
|
if (opts.sid) o.sid = opts.sid;
|
|
596
|
-
if (opts.signedAttributes
|
|
596
|
+
if (opts.signedAttributes != null) o.signedAttributes = opts.signedAttributes;
|
|
597
597
|
if (opts.additionalSignedAttributes) o.additionalSignedAttributes = opts.additionalSignedAttributes;
|
|
598
598
|
return o;
|
|
599
599
|
}
|
|
@@ -781,6 +781,9 @@ function _capped(msg) {
|
|
|
781
781
|
* @opts time the instant the signer's chain is judged at (default now). Only read with `trustAnchors`.
|
|
782
782
|
* @opts requiredEku key purposes the SIGNER certificate must carry. Defaults to `["emailProtection"]`.
|
|
783
783
|
* @opts checkPurpose the purpose the ANCHOR's own trust metadata must permit. Defaults to `"emailProtection"`.
|
|
784
|
+
* @opts distrustedKeys SHA-256 digests of SubjectPublicKeyInfo DER (64 hexadecimal characters or 32 bytes
|
|
785
|
+
* each, see `pki.trust.spkiDigest`), forwarded to `cms.verify` with `trustAnchors`. A
|
|
786
|
+
* listed key on the signer's chain, the anchor included, reports the signer as not trusted.
|
|
784
787
|
* @opts strictMicalg reject a `multipart/signed` whose `micalg` disagrees with the SignerInfo digest.
|
|
785
788
|
* @opts legacyHeaderProtection opt in to detecting a LEGACY RFC 8551 header-protected message (RFC 9788 sec. 4.10): a Cryptographic Payload that is a bare `message/rfc822` wrap with no `hp=` parameter. When set, a precisely-identified legacy message surfaces the inner message's headers under `headerProtection.legacy = { headers, mode, fromMismatch, confidential }`, where `headers` is an ordered `[{ name, value }]` array (retaining legally-repeated fields such as `Received`) and the mode is inferred from the envelope (`clear` here). They are not placed under `protectedHeaders`, and `present` stays `false`. Consuming `headerProtection.legacy.headers` is an explicit choice: a legacy message is structurally indistinguishable from an ordinary forwarded `message/rfc822`, so this is a heuristic (RFC 9788 sec. 4.10.2: "not based on any strong end-to-end guarantees"); cross-check `legacy.fromMismatch`. Anything not precisely identified (a nested crypto layer, an `hp=` on the inner message, a non-`message/rfc822` payload, a duplicate of a singleton field, or a duplicate Content-Type) reports `legacy: null`. Off by default. The signed-and-encrypted form (RFC 9788 Appendix C.3.17) is a documented gap (`legacy: null` at `decrypt`; surfaces as `clear` only via the caller's re-`verify` step), because the non-recursive layered API exposes no single seam holding both the inner signature verdict and the outer header section.
|
|
786
789
|
* @example
|
|
@@ -795,6 +798,7 @@ function _capped(msg) {
|
|
|
795
798
|
async function verify(message, opts) {
|
|
796
799
|
opts = opts || _create(null);
|
|
797
800
|
opts = _knownOpts(opts, VERIFY_OPTS, "verify");
|
|
801
|
+
var distrustedKeys = guard.distrust.keys(opts.distrustedKeys, _err, "smime/bad-input", "pki.smime.verify opts.distrustedKeys");
|
|
798
802
|
var ent = mime.parse(message, SmimeError, "smime/bad-mime");
|
|
799
803
|
var ct = ent.contentType;
|
|
800
804
|
var vOpts = {};
|
|
@@ -803,8 +807,9 @@ async function verify(message, opts) {
|
|
|
803
807
|
vOpts.trustAnchors = opts.trustAnchors;
|
|
804
808
|
vOpts.requiredEku = opts.requiredEku != null ? opts.requiredEku : ["emailProtection"];
|
|
805
809
|
vOpts.checkPurpose = opts.checkPurpose != null ? opts.checkPurpose : "emailProtection";
|
|
810
|
+
if (distrustedKeys !== null) vOpts.distrustedKeys = distrustedKeys;
|
|
806
811
|
}
|
|
807
|
-
if (opts.time
|
|
812
|
+
if (opts.time != null) vOpts.time = opts.time;
|
|
808
813
|
if (opts.allowWeakDigests === true) vOpts.allowWeakDigests = true;
|
|
809
814
|
if (_isPkcs7(ct.type, "mime")) {
|
|
810
815
|
if (ct.params["smime-type"] && ct.params["smime-type"] !== "signed-data") throw _err("smime/unsupported-type", "unsupported smime-type " + JSON.stringify(ct.params["smime-type"]) + " (only signed-data)");
|
|
@@ -854,10 +859,10 @@ function _envelopedTypeOf(der, badParseCode) {
|
|
|
854
859
|
|
|
855
860
|
function _cmsEncryptOpts(opts) {
|
|
856
861
|
var o = {};
|
|
857
|
-
if (opts.contentEncryptionAlgorithm
|
|
858
|
-
if (opts.oaepHash
|
|
859
|
-
if (opts.keyIdentifier
|
|
860
|
-
if (opts.ukm
|
|
862
|
+
if (opts.contentEncryptionAlgorithm != null) o.contentEncryptionAlgorithm = opts.contentEncryptionAlgorithm;
|
|
863
|
+
if (opts.oaepHash != null) o.oaepHash = opts.oaepHash;
|
|
864
|
+
if (opts.keyIdentifier != null) o.keyIdentifier = opts.keyIdentifier;
|
|
865
|
+
if (opts.ukm != null) o.ukm = opts.ukm;
|
|
861
866
|
return o;
|
|
862
867
|
}
|
|
863
868
|
|
|
@@ -972,12 +977,12 @@ async function decrypt(message, keyMaterial, opts) {
|
|
|
972
977
|
var smimeType = _envelopedTypeOf(der, "smime/unsupported-type");
|
|
973
978
|
if (opts.strictSmimeType && st && stLower !== smimeType.toLowerCase()) throw _err("smime/smime-type-mismatch", "the header smime-type " + JSON.stringify(st) + " disagrees with the CMS body (" + smimeType + ")");
|
|
974
979
|
var cmsOpts = {};
|
|
975
|
-
if (opts.recipientIndex
|
|
976
|
-
if (opts.maxIterations
|
|
980
|
+
if (opts.recipientIndex != null) cmsOpts.recipientIndex = opts.recipientIndex;
|
|
981
|
+
if (opts.maxIterations != null) cmsOpts.maxIterations = opts.maxIterations;
|
|
977
982
|
/** @internal Forwarded rather than re-decided here: an S/MIME body is a CMS structure and the rule about
|
|
978
983
|
* RSAES-PKCS1-v1_5 over an unauthenticated content belongs to the verb that opens it. An
|
|
979
984
|
* `openssl smime -encrypt` or `openssl cms -encrypt` message is exactly that combination by default. */
|
|
980
|
-
if (opts.allowUnauthenticatedRsa15
|
|
985
|
+
if (opts.allowUnauthenticatedRsa15 != null) cmsOpts.allowUnauthenticatedRsa15 = opts.allowUnauthenticatedRsa15;
|
|
981
986
|
var res = await cms.decrypt(der, keyMaterial, cmsOpts);
|
|
982
987
|
return Object.assign({
|
|
983
988
|
content: res.content, smimeType: smimeType, authenticated: res.authenticated,
|
|
@@ -1014,7 +1019,7 @@ async function compress(content, opts) {
|
|
|
1014
1019
|
opts = _knownOpts(opts, COMPRESS_OPTS, "compress");
|
|
1015
1020
|
var entity = _entityBytes(content, opts);
|
|
1016
1021
|
var cOpts = {};
|
|
1017
|
-
if (opts.level
|
|
1022
|
+
if (opts.level != null) cOpts.level = opts.level;
|
|
1018
1023
|
var der = _toBuf(await cms.compress(entity, cOpts));
|
|
1019
1024
|
var head = _pkcs7MimeHead("compressed-data", "smime.p7z");
|
|
1020
1025
|
return _capped(Buffer.concat([head, _base64Body(der), mime.CRLF]));
|
|
@@ -1053,7 +1058,7 @@ async function decompress(message, opts) {
|
|
|
1053
1058
|
if (st && st.toLowerCase() !== "compressed-data") throw _err("smime/unsupported-type", "unsupported smime-type " + JSON.stringify(st) + " (only compressed-data)");
|
|
1054
1059
|
var der = _decodeCms(ent);
|
|
1055
1060
|
var dOpts = {};
|
|
1056
|
-
if (opts.maxOutputBytes
|
|
1061
|
+
if (opts.maxOutputBytes != null) dOpts.maxOutputBytes = opts.maxOutputBytes;
|
|
1057
1062
|
var res = await cms.decompress(der, dOpts);
|
|
1058
1063
|
return { content: res.content, contentType: res.contentType, contentTypeName: res.contentTypeName, compressionAlgorithm: res.compressionAlgorithm };
|
|
1059
1064
|
}
|
package/lib/tls-cert-compress.js
CHANGED
|
@@ -88,7 +88,7 @@ var HEADER_BYTES = 8;
|
|
|
88
88
|
|
|
89
89
|
function _resolveCap(opts) {
|
|
90
90
|
var cap = C.LIMITS.TLS_CERT_MSG_MAX_BYTES;
|
|
91
|
-
if (opts.maxOutputBytes
|
|
91
|
+
if (opts.maxOutputBytes != null) {
|
|
92
92
|
var mo = opts.maxOutputBytes;
|
|
93
93
|
if (typeof mo !== "number" || !isFinite(mo) || mo <= 0 || Math.floor(mo) !== mo) {
|
|
94
94
|
throw _err("tls/bad-input", "opts.maxOutputBytes must be a positive integer");
|
|
@@ -99,6 +99,7 @@ function _resolveCap(opts) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function _resolveAllowed(opts) {
|
|
102
|
+
// allow:caller-option-presence-tested-against-undefined -- absence accepts every codec this build supports, so a null must be refused rather than widen the allowlist
|
|
102
103
|
if (opts.allowedAlgorithms === undefined) return SUPPORTED.slice();
|
|
103
104
|
var list = opts.allowedAlgorithms;
|
|
104
105
|
if (!Array.isArray(list)) throw _err("tls/bad-input", "opts.allowedAlgorithms must be an array of algorithm names or numbers");
|
|
@@ -263,7 +264,7 @@ function decompressCertificate(bytes, opts) {
|
|
|
263
264
|
*/
|
|
264
265
|
function parseCertificateMessage(bytes, opts) {
|
|
265
266
|
opts = opts || intrinsic.create(null);
|
|
266
|
-
var type = opts.certificateType
|
|
267
|
+
var type = opts.certificateType == null ? "X509" : opts.certificateType;
|
|
267
268
|
if (type !== "X509" && type !== "RawPublicKey") {
|
|
268
269
|
throw _err("tls/bad-input", "opts.certificateType must be \"X509\" or \"RawPublicKey\"");
|
|
269
270
|
}
|
|
@@ -330,7 +331,7 @@ function compressCertificate(certificateMessage, opts) {
|
|
|
330
331
|
" bytes, over the " + C.LIMITS.TLS_CERT_MSG_MAX_BYTES + "-byte handshake framing limit");
|
|
331
332
|
}
|
|
332
333
|
var view = guard.bytes.snapshot(viewed, TlsError, "tls/bad-input", "the certificate message");
|
|
333
|
-
var sel = opts.algorithm
|
|
334
|
+
var sel = opts.algorithm == null ? "zlib" : opts.algorithm;
|
|
334
335
|
var name = (typeof sel === "number") ? ALG_BY_NUMBER[sel] : sel;
|
|
335
336
|
if (typeof name !== "string" || SUPPORTED.indexOf(name) === -1) {
|
|
336
337
|
throw _err("tls/unsupported-algorithm", "opts.algorithm names an algorithm this toolkit cannot compress: " + JSON.stringify(sel));
|
|
@@ -338,12 +339,12 @@ function compressCertificate(certificateMessage, opts) {
|
|
|
338
339
|
if (view.length < MIN_CERT_MSG_BYTES) {
|
|
339
340
|
throw _err("tls/bad-input", "a Certificate message is at least " + MIN_CERT_MSG_BYTES + " bytes (RFC 8446 sec. 4.4.2)");
|
|
340
341
|
}
|
|
341
|
-
if (opts.level
|
|
342
|
+
if (opts.level != null && (typeof opts.level !== "number" || !isFinite(opts.level) || Math.floor(opts.level) !== opts.level)) {
|
|
342
343
|
throw _err("tls/bad-input", "opts.level must be an integer");
|
|
343
344
|
}
|
|
344
345
|
var stream;
|
|
345
346
|
try {
|
|
346
|
-
stream = COMPRESS[name](view, opts.level
|
|
347
|
+
stream = COMPRESS[name](view, opts.level != null ? LEVEL_OPT[name](opts.level) : undefined);
|
|
347
348
|
} catch (e) {
|
|
348
349
|
throw _err("tls/bad-input", "the certificate message could not be compressed (check opts.level)", e);
|
|
349
350
|
}
|