@blamejs/pki 0.9.5 → 0.9.6
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 +37 -0
- package/README.md +7 -6
- package/bin/pki.js +110 -34
- package/lib/acme.js +91 -46
- package/lib/attrcert-sign.js +4 -3
- package/lib/cmp-build.js +16 -6
- package/lib/cmp-session.js +19 -3
- package/lib/cmp-verify.js +25 -2
- package/lib/cms-compress.js +3 -3
- package/lib/cms-digest.js +3 -3
- package/lib/cms-verify.js +18 -8
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +3 -3
- package/lib/csr-sign.js +1 -1
- package/lib/ct.js +16 -3
- package/lib/est.js +44 -29
- package/lib/guard-all.js +2 -0
- package/lib/guard-distrust.js +224 -0
- package/lib/guard-json.js +1 -1
- package/lib/http-transport.js +299 -36
- package/lib/jose.js +1 -1
- package/lib/lint.js +67 -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 +112 -16
- package/lib/possession.js +6 -2
- package/lib/related-cert.js +2 -2
- package/lib/revocation-fetch.js +38 -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 +121 -20
- package/lib/trustanchor-build.js +16 -1
- package/lib/tsp-sign.js +7 -1
- package/lib/webauthn-mds.js +29 -8
- package/lib/webauthn.js +46 -14
- package/lib/x509-sign.js +8 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -761,6 +761,7 @@ async function _verifyOneTimestamp(attr, targetSig, claimedSigningTime, opts) {
|
|
|
761
761
|
trustAnchors: opts.timestampTrustAnchors,
|
|
762
762
|
revocationChecker: opts.timestampRevocationChecker,
|
|
763
763
|
allowWeakDigests: opts.allowWeakDigests === true,
|
|
764
|
+
distrustedKeys: opts.distrustedKeys,
|
|
764
765
|
});
|
|
765
766
|
} catch (e) {
|
|
766
767
|
return guard.verdict.of({ valid: false, trusted: false, genTime: null,
|
|
@@ -975,6 +976,10 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
975
976
|
* trust decision from the signer: an operator may anchor one and not the other.
|
|
976
977
|
* @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
|
|
977
978
|
* takes one. Each row reports what it established in `revocationChecked`.
|
|
979
|
+
* @opts distrustedKeys SHA-256 digests of SubjectPublicKeyInfo DER (64 hexadecimal characters or
|
|
980
|
+
* 32 bytes each, see `pki.trust.spkiDigest`), applied to the signer chain and to the
|
|
981
|
+
* TSA chain of every timestamp row. A listed key on a chain, the anchor included,
|
|
982
|
+
* reports that signer or that row as not trusted.
|
|
978
983
|
* @opts requireAlgorithmProtection Fail a signer that carries no RFC 6211
|
|
979
984
|
* `CMSAlgorithmProtection` attribute, with `cms/missing-algorithm-protection`. Off
|
|
980
985
|
* by default: RFC 6211 says nothing about absence and RFC 8933 sec. 4.1 makes
|
|
@@ -1026,7 +1031,7 @@ var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content:
|
|
|
1026
1031
|
* leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
|
|
1027
1032
|
* two answers one. */
|
|
1028
1033
|
timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1,
|
|
1029
|
-
requireAlgorithmProtection: 1 });
|
|
1034
|
+
requireAlgorithmProtection: 1, distrustedKeys: 1 });
|
|
1030
1035
|
|
|
1031
1036
|
function verify(input, opts) {
|
|
1032
1037
|
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
@@ -1043,12 +1048,15 @@ async function _verify(input, opts) {
|
|
|
1043
1048
|
var allowWeakDigests = opts.allowWeakDigests === true;
|
|
1044
1049
|
var requireProtection = guard.identifier.booleanOption(opts.requireAlgorithmProtection, _err,
|
|
1045
1050
|
"cms/bad-input", "pki.cms.verify opts.requireAlgorithmProtection");
|
|
1051
|
+
/** @internal Typed here, before any signer is judged: the signer chain is built only for a signer
|
|
1052
|
+
* that verified, and every throw from a timestamp verify becomes a row. One list serves both. */
|
|
1053
|
+
var distrustedKeys = guard.distrust.keys(opts.distrustedKeys, _err, "cms/bad-input", "pki.cms.verify opts.distrustedKeys");
|
|
1046
1054
|
var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
|
|
1047
1055
|
return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
|
|
1048
1056
|
}, _err, "cms/bad-input", "the SignedData");
|
|
1049
1057
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
1050
|
-
var trustCfg = _snapshotTrust(opts);
|
|
1051
|
-
var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
|
|
1058
|
+
var trustCfg = _snapshotTrust(opts, distrustedKeys);
|
|
1059
|
+
var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests, distrustedKeys);
|
|
1052
1060
|
var parsedCerts = [];
|
|
1053
1061
|
(parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
|
|
1054
1062
|
(opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
|
|
@@ -1130,7 +1138,7 @@ function _cloneAnchorValue(v, depth) {
|
|
|
1130
1138
|
}
|
|
1131
1139
|
return out;
|
|
1132
1140
|
}
|
|
1133
|
-
function _snapshotTrust(opts) {
|
|
1141
|
+
function _snapshotTrust(opts, distrustedKeys) {
|
|
1134
1142
|
var raw = opts.trustAnchors;
|
|
1135
1143
|
var anchors = null;
|
|
1136
1144
|
if (raw != null) {
|
|
@@ -1140,9 +1148,10 @@ function _snapshotTrust(opts) {
|
|
|
1140
1148
|
}
|
|
1141
1149
|
return {
|
|
1142
1150
|
trustAnchors: anchors,
|
|
1143
|
-
time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time
|
|
1151
|
+
time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time == null ? new intrinsic.Date() : opts.time),
|
|
1144
1152
|
requiredEku: Array.isArray(opts.requiredEku) ? guard.list.snapshot(opts.requiredEku) : opts.requiredEku,
|
|
1145
1153
|
checkPurpose: opts.checkPurpose,
|
|
1154
|
+
distrustedKeys: distrustedKeys,
|
|
1146
1155
|
};
|
|
1147
1156
|
}
|
|
1148
1157
|
|
|
@@ -1153,7 +1162,7 @@ function _snapshotTrust(opts) {
|
|
|
1153
1162
|
* authorities. The certificates are copied by value, since a caller holding the array could swap an
|
|
1154
1163
|
* entry after the row that read it. The checker is a function and is captured as the one reference
|
|
1155
1164
|
* every row calls. */
|
|
1156
|
-
function _snapshotTimestampTrust(opts, allowWeakDigests) {
|
|
1165
|
+
function _snapshotTimestampTrust(opts, allowWeakDigests, distrustedKeys) {
|
|
1157
1166
|
var certs = opts.timestampCerts;
|
|
1158
1167
|
var anchors = opts.timestampTrustAnchors;
|
|
1159
1168
|
return {
|
|
@@ -1167,6 +1176,7 @@ function _snapshotTimestampTrust(opts, allowWeakDigests) {
|
|
|
1167
1176
|
}),
|
|
1168
1177
|
timestampRevocationChecker: opts.timestampRevocationChecker,
|
|
1169
1178
|
allowWeakDigests: allowWeakDigests,
|
|
1179
|
+
distrustedKeys: distrustedKeys,
|
|
1170
1180
|
};
|
|
1171
1181
|
}
|
|
1172
1182
|
|
|
@@ -1200,7 +1210,7 @@ async function _applyTrust(res, parsedCerts, cfg) {
|
|
|
1200
1210
|
if (!anchors.length) {
|
|
1201
1211
|
throw _err("cms/bad-input", "opts.trustAnchors is empty -- name at least one root, or omit it to state that the signer is not being anchored");
|
|
1202
1212
|
}
|
|
1203
|
-
if (cfg.time
|
|
1213
|
+
if (cfg.time != null) guard.time.assertValid(cfg.time, _err, "cms/bad-input", "opts.time");
|
|
1204
1214
|
var purposes = _engine.resolvePurposeOpts({ requiredEku: cfg.requiredEku, checkPurpose: cfg.checkPurpose });
|
|
1205
1215
|
intrinsic.forEach(anchors, function (a) {
|
|
1206
1216
|
_engine.toAnchor(a);
|
|
@@ -1210,7 +1220,7 @@ async function _applyTrust(res, parsedCerts, cfg) {
|
|
|
1210
1220
|
var s = res.signers[_si];
|
|
1211
1221
|
{
|
|
1212
1222
|
if (s.ok !== true || !s.cert) { s.trusted = false; continue; }
|
|
1213
|
-
var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at };
|
|
1223
|
+
var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at, distrustedKeys: cfg.distrustedKeys };
|
|
1214
1224
|
if (cfg.requiredEku != null) buildOpts.requiredEku = cfg.requiredEku;
|
|
1215
1225
|
if (cfg.checkPurpose != null) buildOpts.checkPurpose = cfg.checkPurpose;
|
|
1216
1226
|
if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; continue; }
|
package/lib/constants.js
CHANGED
|
@@ -165,6 +165,7 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
165
165
|
MERKLE_MAX_PROOF_NODES: 65,
|
|
166
166
|
MERKLE_MAX_LEAVES: 1048576,
|
|
167
167
|
PROXY_MAX_TLS_ANCHORS: 256,
|
|
168
|
+
DISTRUSTED_KEYS_MAX: 4096,
|
|
168
169
|
PATH_MAX_CERTS: 100,
|
|
169
170
|
PATH_MAX_POLICY_NODES: 4096,
|
|
170
171
|
PATH_BUILD_MAX_DEPTH: 20,
|
package/lib/crl-sign.js
CHANGED
|
@@ -770,16 +770,16 @@ function isRevoked(crl, serialNumber, opts) {
|
|
|
770
770
|
return "pki.crl.isRevoked has an unknown option " + JSON.stringify(k) +
|
|
771
771
|
"; it takes `time` and `historicalMode`";
|
|
772
772
|
});
|
|
773
|
-
if (o.historicalMode
|
|
773
|
+
if (o.historicalMode != null && o.time == null) {
|
|
774
774
|
throw _err("crl/bad-input", "pki.crl.isRevoked opts.historicalMode says how to read an entry " +
|
|
775
775
|
"against the instant asked about, so it needs opts.time as well");
|
|
776
776
|
}
|
|
777
|
-
if (o.historicalMode
|
|
777
|
+
if (o.historicalMode != null && typeof o.historicalMode !== "boolean") {
|
|
778
778
|
throw _err("crl/bad-input", "pki.crl.isRevoked opts.historicalMode must be a boolean");
|
|
779
779
|
}
|
|
780
780
|
var parsed = _coerceCrl(crl);
|
|
781
781
|
var hex = _serialHexOf(serialNumber);
|
|
782
|
-
if (o.time
|
|
782
|
+
if (o.time != null) {
|
|
783
783
|
guard.time.assertValid(o.time, _err, "crl/bad-input", "pki.crl.isRevoked opts.time");
|
|
784
784
|
var at = guard.time.instantOf(o.time);
|
|
785
785
|
if (at < guard.time.instantOf(parsed.thisUpdate)) {
|
package/lib/csr-sign.js
CHANGED
|
@@ -114,7 +114,7 @@ function _possessionStatement(spec) {
|
|
|
114
114
|
"privateKeyPossessionStatement needs the certificate: this verb resolves the signature algorithm from its key and checks the signature against it. Pass includeCertificate: false to leave it off the wire, which RFC 9883 sec. 3 allows");
|
|
115
115
|
}
|
|
116
116
|
var certDer = _b.reqDer(spec.certificate, "privateKeyPossessionStatement certificate");
|
|
117
|
-
var include = spec.includeCertificate
|
|
117
|
+
var include = spec.includeCertificate == null ? true : spec.includeCertificate;
|
|
118
118
|
if (include !== true && include !== false) {
|
|
119
119
|
throw _err("csr/bad-input", "privateKeyPossessionStatement includeCertificate is a flag, so it is true, false or omitted");
|
|
120
120
|
}
|
package/lib/ct.js
CHANGED
|
@@ -1216,6 +1216,9 @@ function _trustValue(v, field) {
|
|
|
1216
1216
|
return v;
|
|
1217
1217
|
}
|
|
1218
1218
|
|
|
1219
|
+
/** @internal The settings `_tlsForFetch` reads. A setting not on the list is refused at the door. */
|
|
1220
|
+
var _TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, ["anchors", "useSystemStore", "cert", "key",
|
|
1221
|
+
"minVersion", "servername", "checkServerIdentity", "distrustedKeys"]));
|
|
1219
1222
|
function _tlsForFetch(opts) {
|
|
1220
1223
|
/** @internal A record with NO prototype where the caller supplied no TLS at all, because the empty
|
|
1221
1224
|
* object a literal gives inherits from `Object.prototype` and every field below is then answerable
|
|
@@ -1232,9 +1235,12 @@ function _tlsForFetch(opts) {
|
|
|
1232
1235
|
anchors = intrinsic.isArray(anchors)
|
|
1233
1236
|
? guard.list.copyMap(anchors, function (a, i) { return _trustValue(a, "opts.tls.anchors[" + i + "]"); })
|
|
1234
1237
|
: _trustValue(anchors, "opts.tls.anchors");
|
|
1238
|
+
/** @internal The distrust list is typed into a frozen copy. `guard.distrust.keys` reads each entry
|
|
1239
|
+
* through its descriptor and refuses an accessor, a hole and a Proxy, so no caller code runs. */
|
|
1240
|
+
var distrustedKeys = guard.distrust.keys(t.distrustedKeys, _ctErr, "ct/bad-input", "opts.tls.distrustedKeys");
|
|
1235
1241
|
return { anchors: anchors, useSystemStore: t.useSystemStore, cert: _trustValue(t.cert, "opts.tls.cert"),
|
|
1236
1242
|
key: _trustValue(t.key, "opts.tls.key"), minVersion: t.minVersion, servername: t.servername,
|
|
1237
|
-
checkServerIdentity: t.checkServerIdentity };
|
|
1243
|
+
checkServerIdentity: t.checkServerIdentity, distrustedKeys: distrustedKeys };
|
|
1238
1244
|
}
|
|
1239
1245
|
|
|
1240
1246
|
function _sameOrigin(a, b) { return _urlOrigin(new _URL(a)) === _urlOrigin(new _URL(b)); }
|
|
@@ -1418,6 +1424,7 @@ function _settleDecisive(o, keyName) {
|
|
|
1418
1424
|
guard.identifier.refuseAccessorFields(o.tls,
|
|
1419
1425
|
guard.identifier.readableNames(o.tls, _ctErr, "ct/bad-input", "opts.tls"),
|
|
1420
1426
|
_ctErr, "ct/bad-input", "opts.tls");
|
|
1427
|
+
guard.identifier.assertKnownKeys(o.tls, _TLS_KNOWN, _ctErr, "ct/bad-input", "unknown opts.tls setting ");
|
|
1421
1428
|
/** @internal And the ELEMENTS of the anchor list, which refusing accessors on `tls` itself does not
|
|
1422
1429
|
* reach. An accessor on `anchors[0]` runs when the list is copied, and one that overwrote
|
|
1423
1430
|
* `sth.rootHash` with the leaf being proven had an empty audit path verify. The refusal has to go
|
|
@@ -1652,11 +1659,17 @@ async function verifySth(sth, logKey) {
|
|
|
1652
1659
|
*
|
|
1653
1660
|
* `treeSize` and `timestamp` come back as `BigInt`, since both are uint64 on the wire.
|
|
1654
1661
|
*
|
|
1662
|
+
* `opts.tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest). A
|
|
1663
|
+
* log server whose chain carries a listed key is refused with `ct/server-key-distrusted`, and a
|
|
1664
|
+
* malformed list is `ct/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`,
|
|
1665
|
+
* `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused
|
|
1666
|
+
* `ct/bad-input`. The other sec. 4 messages read it the same way.
|
|
1667
|
+
*
|
|
1655
1668
|
* @opts
|
|
1656
1669
|
* url: string, // the log's base URL, with or without the ct/v1/ prefix
|
|
1657
1670
|
* logKey: BufferSource, // the log's SubjectPublicKeyInfo, pinned by the caller
|
|
1658
1671
|
* transport: function, // optional (request) => Promise<{ status, headers, body }>
|
|
1659
|
-
* tls: object, // optional { anchors, useSystemStore, ... } when no transport is given
|
|
1672
|
+
* tls: object, // optional { anchors, useSystemStore, distrustedKeys, ... } when no transport is given
|
|
1660
1673
|
* headers: object, // optional extra request headers
|
|
1661
1674
|
* timeout: number, // optional per-request timeout in milliseconds
|
|
1662
1675
|
* maxResponseBytes: number, // optional response cap
|
|
@@ -2194,7 +2207,7 @@ function _b64Array(obj, name, code, label, exactLen, maxCount) {
|
|
|
2194
2207
|
* @opts signerKey REQUIRED and caller-pinned -- the distributor SubjectPublicKeyInfo as a DER Buffer; no baked-in key.
|
|
2195
2208
|
* @opts sigUrl OPTIONAL -- the detached `log_list.sig` URL (https, must share the log-list URL's origin); default `url` with `.json` -> `.sig` (a non-.json url requires an explicit sigUrl).
|
|
2196
2209
|
* @opts transport OPTIONAL injectable `transport(request) -> Promise<{status,headers,body}>` (default `pki.transport.https`); the test seam.
|
|
2197
|
-
* @opts tls OPTIONAL `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }` threaded to the default transport (ignored when a transport is injected); `rejectUnauthorized` is always on.
|
|
2210
|
+
* @opts tls OPTIONAL `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }` threaded to the default transport (ignored when a transport is injected); `rejectUnauthorized` is always on. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest): a distributor whose chain carries a listed key is refused with `ct/server-key-distrusted`, and a malformed list is `ct/bad-input`. A setting not named in that list is refused `ct/bad-input`.
|
|
2198
2211
|
* @opts headers OPTIONAL extra request headers (the request-framing headers are stripped; the verb owns the GET method).
|
|
2199
2212
|
* @opts timeout OPTIONAL ms budget, default 30s (cap-validated).
|
|
2200
2213
|
* @opts maxResponseBytes OPTIONAL per-GET size cap, default 4 MiB, tightenable DOWNWARD only.
|
package/lib/est.js
CHANGED
|
@@ -918,10 +918,23 @@ function _parseUrl(urlStr) {
|
|
|
918
918
|
return url;
|
|
919
919
|
}
|
|
920
920
|
|
|
921
|
-
/** @internal The settings this client forwards to its transport.
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
921
|
+
/** @internal The settings this client forwards to its transport. The distrust list is typed here, once
|
|
922
|
+
* per verb, and the frozen copy is the one every hop carries. A setting not on the list is refused. */
|
|
923
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
|
|
924
|
+
var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
|
|
925
|
+
function _tlsForRequest(tls) {
|
|
926
|
+
guard.identifier.assertKnownKeys(tls, TLS_KNOWN, E, "est/bad-input", "unknown opts.tls setting ");
|
|
927
|
+
var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
|
|
928
|
+
t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, E, "est/bad-input", "opts.tls.distrustedKeys");
|
|
929
|
+
return t;
|
|
930
|
+
}
|
|
931
|
+
/** @internal The proxy and the tls settings, taken at the synchronous verb door. Every request and
|
|
932
|
+
* redirect hop of the verb uses this record. */
|
|
933
|
+
function _doorSnapshot(opts) {
|
|
934
|
+
var proxy, tls;
|
|
935
|
+
try { proxy = _snapProxy(opts); tls = opts.tls; }
|
|
936
|
+
catch (_pe) { throw E("est/bad-input", "the options object could not be read", _pe); }
|
|
937
|
+
return { proxy: proxy, tls: _tlsForRequest(tls) };
|
|
925
938
|
}
|
|
926
939
|
|
|
927
940
|
function _redirectTarget(current, location, method, opts) {
|
|
@@ -980,7 +993,7 @@ function _authScheme(opts) {
|
|
|
980
993
|
if (sc !== "basic" && sc !== "digest") throw E("est/bad-input", "opts.auth.scheme must be \"basic\" or \"digest\"");
|
|
981
994
|
return sc;
|
|
982
995
|
}
|
|
983
|
-
if (opts.username
|
|
996
|
+
if (opts.username != null || opts.password != null) return "basic";
|
|
984
997
|
return null;
|
|
985
998
|
}
|
|
986
999
|
function _authUser(opts) { return opts.auth && opts.auth.username !== undefined ? opts.auth.username : opts.username; }
|
|
@@ -1095,7 +1108,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
1095
1108
|
/** @internal `checked` is the transport the verb already took at its door. Taking it again here
|
|
1096
1109
|
* would ask the caller's options a second time, and anything read in between could change the
|
|
1097
1110
|
* answer, so a verb that holds one passes it and this reads the option only when none was held. */
|
|
1098
|
-
function _client(op, method, baseUrl, body, headers, opts,
|
|
1111
|
+
function _client(op, method, baseUrl, body, headers, opts, snap, onAttempt, checked) {
|
|
1099
1112
|
var base;
|
|
1100
1113
|
try { base = new _URL(_stringOf(baseUrl)); }
|
|
1101
1114
|
catch (e) { throw E("est/bad-url", "the EST base URL did not parse: " + guard.text.showValue(baseUrl), e); }
|
|
@@ -1103,14 +1116,14 @@ function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt,
|
|
|
1103
1116
|
var url = _parseUrl(paths(baseUrl, { label: opts.label })[op]);
|
|
1104
1117
|
var transport = checked !== undefined ? checked : _assertTransport(opts);
|
|
1105
1118
|
if (!transport) {
|
|
1106
|
-
var t =
|
|
1119
|
+
var t = snap.tls;
|
|
1107
1120
|
var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
|
|
1108
1121
|
if (!hasAnchors && t.useSystemStore !== true) throw E("est/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
1109
1122
|
transport = httpTransport.https({ E: E, errPrefix: "est" });
|
|
1110
1123
|
}
|
|
1111
1124
|
var budgets = {
|
|
1112
|
-
tls:
|
|
1113
|
-
proxy:
|
|
1125
|
+
tls: snap.tls,
|
|
1126
|
+
proxy: snap.proxy,
|
|
1114
1127
|
timeout: guard.limits.cap(opts.timeout, "timeout", DEFAULT_TIMEOUT, { E: E, code: "est/bad-input", min: 1, max: MAX_TIMEOUT }),
|
|
1115
1128
|
maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: E, code: "est/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES }),
|
|
1116
1129
|
maxRedirects: guard.limits.cap(opts.maxRedirects, "maxRedirects", 5, { E: E, code: "est/bad-input", min: 0, max: 32 }),
|
|
@@ -1241,18 +1254,19 @@ function fullcmc(baseUrl, request, opts) {
|
|
|
1241
1254
|
* caller's nested objects, and one of those reads must not be able to change which transport
|
|
1242
1255
|
* the request goes out on, so the value that was checked is the value carried below. */
|
|
1243
1256
|
var carriedTransport = opts.transport;
|
|
1244
|
-
|
|
1245
|
-
|
|
1257
|
+
/** @internal Taken before the own-property copy below, so a tls a defaults bag carries on its
|
|
1258
|
+
* prototype is the one the request uses. */
|
|
1259
|
+
var snap = _doorSnapshot(opts);
|
|
1246
1260
|
der = _cmcRequestDer(request);
|
|
1247
1261
|
wanted = _requestedPublicKeys(der);
|
|
1248
1262
|
sent = _cmcSent(opts, der);
|
|
1249
1263
|
/** @internal The copy below keeps own properties only, and a defaults bag holds its options on
|
|
1250
1264
|
* a prototype, so the transport is put back from the value held at the door. */
|
|
1251
1265
|
opts = _shallowCopy(opts);
|
|
1252
|
-
if (carriedTransport
|
|
1266
|
+
if (carriedTransport != null) opts.transport = carriedTransport;
|
|
1253
1267
|
var body = transferEncode(der);
|
|
1254
1268
|
return _client("fullcmc", "POST", baseUrl, body,
|
|
1255
|
-
{ accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts,
|
|
1269
|
+
{ accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts, snap, undefined, carriedTransport)
|
|
1256
1270
|
.then(function (res) { return _fullcmcResult(res, opts, wanted, sent); });
|
|
1257
1271
|
} catch (e) {
|
|
1258
1272
|
return _rej(e);
|
|
@@ -1571,7 +1585,7 @@ function _causedBy(e, target) {
|
|
|
1571
1585
|
|
|
1572
1586
|
var ENROLL_HEADERS = intrinsic.assign(intrinsic.create(null), { accept: "application/pkcs7-mime", "content-type": "application/pkcs10" });
|
|
1573
1587
|
|
|
1574
|
-
function _enrollBound(op, baseUrl, builder, opts,
|
|
1588
|
+
function _enrollBound(op, baseUrl, builder, opts, snap) {
|
|
1575
1589
|
/** @internal Held before anything else the caller supplied is read, and carried to the client:
|
|
1576
1590
|
* the builder runs during the handshake, so a later read could answer with a different value. */
|
|
1577
1591
|
var checkedTransport = opts.transport;
|
|
@@ -1601,7 +1615,7 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
|
|
|
1601
1615
|
.then(null, function (e) { attempt.error = e; attempt.done = true; throw e; });
|
|
1602
1616
|
};
|
|
1603
1617
|
}
|
|
1604
|
-
return _client(op, "POST", baseUrl, hopBody, ENROLL_HEADERS, opts,
|
|
1618
|
+
return _client(op, "POST", baseUrl, hopBody, ENROLL_HEADERS, opts, snap, hopBody, checkedTransport)
|
|
1605
1619
|
.then(function (res) {
|
|
1606
1620
|
if (current && current.error) throw current.error;
|
|
1607
1621
|
if (!current || !current.done) {
|
|
@@ -1617,13 +1631,13 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
|
|
|
1617
1631
|
});
|
|
1618
1632
|
}
|
|
1619
1633
|
|
|
1620
|
-
function _enroll(op, baseUrl, csrInput, opts,
|
|
1621
|
-
if (typeof csrInput === "function") return _enrollBound(op, baseUrl, csrInput, opts,
|
|
1634
|
+
function _enroll(op, baseUrl, csrInput, opts, snap) {
|
|
1635
|
+
if (typeof csrInput === "function") return _enrollBound(op, baseUrl, csrInput, opts, snap);
|
|
1622
1636
|
var checkedTransport = opts.transport;
|
|
1623
1637
|
var csrDer = _csrDer(csrInput);
|
|
1624
1638
|
var spki = csr.parse(csrDer).subjectPublicKeyInfo;
|
|
1625
1639
|
var body = transferEncode(csrDer);
|
|
1626
|
-
return _client(op, "POST", baseUrl, body, ENROLL_HEADERS, opts,
|
|
1640
|
+
return _client(op, "POST", baseUrl, body, ENROLL_HEADERS, opts, snap, undefined, checkedTransport)
|
|
1627
1641
|
.then(function (res) { return _certsResult(op, res, opts, spki); });
|
|
1628
1642
|
}
|
|
1629
1643
|
|
|
@@ -1646,7 +1660,7 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
|
|
|
1646
1660
|
*
|
|
1647
1661
|
* @opts
|
|
1648
1662
|
* - `transport`: an injected `transport(request) -> Promise<{status, headers, body, tls}>`; default pki.transport.https. It must return a promise of the response; anything else is `est/bad-input`.
|
|
1649
|
-
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }.
|
|
1663
|
+
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `est/server-key-distrusted`, on every hop including a cross-origin redirect, and a malformed list is `est/bad-input`. A setting not named in that list is refused `est/bad-input`. The verb reads `tls` when it is called, so a change the caller makes to it afterward does not reach the request.
|
|
1650
1664
|
* - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
|
|
1651
1665
|
* - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
|
|
1652
1666
|
* - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
|
|
@@ -1664,10 +1678,10 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
|
|
|
1664
1678
|
*/
|
|
1665
1679
|
function cacerts(baseUrl, opts) {
|
|
1666
1680
|
opts = opts || intrinsic.create(null);
|
|
1667
|
-
var
|
|
1681
|
+
var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
|
|
1668
1682
|
return _promiseResolve(_Promise, undefined).then(function () {
|
|
1669
1683
|
opts = _knownOpts(opts, CACERTS_OPTS, "cacerts");
|
|
1670
|
-
return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts,
|
|
1684
|
+
return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts, snap, undefined, opts.transport);
|
|
1671
1685
|
}).then(function (res) { return _certsResult("cacerts", res, opts, null); });
|
|
1672
1686
|
}
|
|
1673
1687
|
|
|
@@ -1732,10 +1746,10 @@ function cacerts(baseUrl, opts) {
|
|
|
1732
1746
|
*/
|
|
1733
1747
|
function simpleenroll(baseUrl, csrInput, opts) {
|
|
1734
1748
|
opts = opts || intrinsic.create(null);
|
|
1735
|
-
var
|
|
1749
|
+
var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
|
|
1736
1750
|
return _promiseResolve(_Promise, undefined).then(function () {
|
|
1737
1751
|
opts = _knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
|
|
1738
|
-
return _enroll("simpleenroll", baseUrl, csrInput, opts,
|
|
1752
|
+
return _enroll("simpleenroll", baseUrl, csrInput, opts, snap);
|
|
1739
1753
|
});
|
|
1740
1754
|
}
|
|
1741
1755
|
|
|
@@ -1772,12 +1786,12 @@ function simpleenroll(baseUrl, csrInput, opts) {
|
|
|
1772
1786
|
*/
|
|
1773
1787
|
function simplereenroll(baseUrl, csrInput, opts) {
|
|
1774
1788
|
opts = opts || intrinsic.create(null);
|
|
1775
|
-
var
|
|
1789
|
+
var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
|
|
1776
1790
|
return _promiseResolve(_Promise, undefined).then(function () {
|
|
1777
1791
|
opts = _knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
|
|
1778
1792
|
if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
|
|
1779
1793
|
if (typeof csrInput !== "function") reenrollGuard(opts.oldCert, _csrDer(csrInput));
|
|
1780
|
-
return _enroll("simplereenroll", baseUrl, csrInput, opts,
|
|
1794
|
+
return _enroll("simplereenroll", baseUrl, csrInput, opts, snap);
|
|
1781
1795
|
});
|
|
1782
1796
|
}
|
|
1783
1797
|
|
|
@@ -1944,7 +1958,7 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
1944
1958
|
*/
|
|
1945
1959
|
function serverkeygen(baseUrl, csrInput, opts) {
|
|
1946
1960
|
opts = opts || intrinsic.create(null);
|
|
1947
|
-
var
|
|
1961
|
+
var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
|
|
1948
1962
|
return _promiseResolve(_Promise, undefined).then(function () {
|
|
1949
1963
|
opts = _knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
|
|
1950
1964
|
var checkedTransport = opts.transport;
|
|
@@ -1955,6 +1969,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
|
|
|
1955
1969
|
* coerced to `false` and contradicted a CSR the caller never disagreed with. The value is a
|
|
1956
1970
|
* boolean by the time it reaches here, so it is compared rather than coerced. */
|
|
1957
1971
|
if (opts.requestedEncryption != null && opts.requestedEncryption !== derived.requestedEncryption) throw E("est/bad-input", "opts.requestedEncryption (" + opts.requestedEncryption + ") contradicts the CSR's advertised key-encryption attribute (" + derived.requestedEncryption + ") (RFC 7030 sec. 4.4.1)");
|
|
1972
|
+
// allow:caller-option-presence-tested-against-undefined -- absence means the recipient is not checked against the CSR, so a null must be refused
|
|
1958
1973
|
if (opts.expectedRecipientKeyId !== undefined) {
|
|
1959
1974
|
if (!Buffer.isBuffer(opts.expectedRecipientKeyId)) throw E("est/bad-input", "opts.expectedRecipientKeyId must be a Buffer");
|
|
1960
1975
|
if (derived.expectedRecipientKeyId && !opts.expectedRecipientKeyId.equals(derived.expectedRecipientKeyId)) throw E("est/bad-input", "opts.expectedRecipientKeyId contradicts the key identifier the CSR advertised (RFC 7030 sec. 4.4.1)");
|
|
@@ -1968,7 +1983,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
|
|
|
1968
1983
|
if ((opts.expectedRecipientKeyId !== undefined || opts.expectedRecipientIssuerSerial != null) && !derived.requestedEncryption) {
|
|
1969
1984
|
throw E("est/bad-input", "a recipient expectation (expectedRecipientKeyId / expectedRecipientIssuerSerial) implies an encrypted key, but the CSR advertised no DecryptKeyIdentifier / AsymmetricDecryptKeyIdentifier attribute (RFC 7030 sec. 4.4.1)");
|
|
1970
1985
|
}
|
|
1971
|
-
return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts,
|
|
1986
|
+
return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts, snap, undefined, checkedTransport)
|
|
1972
1987
|
.then(function (res) { return _serverkeygenResult(res, opts, derived); });
|
|
1973
1988
|
});
|
|
1974
1989
|
}
|
|
@@ -2010,10 +2025,10 @@ function _csrattrsResult(res, opts) {
|
|
|
2010
2025
|
*/
|
|
2011
2026
|
function csrattrs(baseUrl, opts) {
|
|
2012
2027
|
opts = opts || intrinsic.create(null);
|
|
2013
|
-
var
|
|
2028
|
+
var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
|
|
2014
2029
|
return _promiseResolve(_Promise, undefined).then(function () {
|
|
2015
2030
|
opts = _knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
|
|
2016
|
-
return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts,
|
|
2031
|
+
return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts, snap, undefined, opts.transport);
|
|
2017
2032
|
}).then(function (res) { return _csrattrsResult(res, opts); });
|
|
2018
2033
|
}
|
|
2019
2034
|
|
package/lib/guard-all.js
CHANGED
|
@@ -22,6 +22,7 @@ var secret = require("./guard-secret");
|
|
|
22
22
|
var parsed = require("./guard-parsed");
|
|
23
23
|
var verdict = require("./guard-verdict");
|
|
24
24
|
var async_ = require("./guard-async");
|
|
25
|
+
var distrust = require("./guard-distrust");
|
|
25
26
|
|
|
26
27
|
module.exports = _freeze({
|
|
27
28
|
bytes: bytes,
|
|
@@ -42,4 +43,5 @@ module.exports = _freeze({
|
|
|
42
43
|
parsed: parsed,
|
|
43
44
|
verdict: verdict,
|
|
44
45
|
async: async_,
|
|
46
|
+
distrust: distrust,
|
|
45
47
|
});
|