@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/acme.js
CHANGED
|
@@ -321,10 +321,41 @@ function _checkType(kind, field, value, obj) {
|
|
|
321
321
|
return null;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
/** @internal The code each resource kind is refused with, written out. A code assembled at
|
|
325
|
+
* runtime from the kind is not a frozen contract: it cannot be read off the source, so the
|
|
326
|
+
* api-snapshot recorded the stem and none of these six, and renaming or losing one would
|
|
327
|
+
* have passed every gate. An unknown kind cannot reach here (the caller's kind is checked
|
|
328
|
+
* first), and falls back to the module's general input code rather than inventing one. */
|
|
329
|
+
var BAD_BY_KIND = intrinsic.assign(intrinsic.create(null), {
|
|
330
|
+
directory: "acme/bad-directory",
|
|
331
|
+
account: "acme/bad-account",
|
|
332
|
+
order: "acme/bad-order",
|
|
333
|
+
authorization: "acme/bad-authorization",
|
|
334
|
+
challenge: "acme/bad-challenge",
|
|
335
|
+
renewalInfo: "acme/bad-renewal-info",
|
|
336
|
+
ordersList: "acme/bad-orders-list",
|
|
337
|
+
});
|
|
338
|
+
/** @internal The table above is written out so the codes can be read off the source, and this
|
|
339
|
+
* holds it to SPECS so it cannot drift from the kinds that use it: a kind added to SPECS
|
|
340
|
+
* without a code here would have been refused with the general input code, losing the one
|
|
341
|
+
* that names the resource. Measured the hard way, by omitting `ordersList`. */
|
|
342
|
+
(function () {
|
|
343
|
+
var kinds = _objectKeys(SPECS);
|
|
344
|
+
for (var i = 0; i < kinds.length; i++) {
|
|
345
|
+
if (BAD_BY_KIND[kinds[i]] === undefined) {
|
|
346
|
+
throw new Error("acme: SPECS kind " + kinds[i] + " has no entry in BAD_BY_KIND");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
var coded = _objectKeys(BAD_BY_KIND);
|
|
350
|
+
for (var j = 0; j < coded.length; j++) {
|
|
351
|
+
if (SPECS[coded[j]] === undefined) {
|
|
352
|
+
throw new Error("acme: BAD_BY_KIND names " + coded[j] + ", which is not a SPECS kind");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
})();
|
|
356
|
+
function _badKindCode(kind) {
|
|
357
|
+
var code = BAD_BY_KIND[guard.text.keyOf(kind)];
|
|
358
|
+
return code === undefined ? "acme/bad-input" : code;
|
|
328
359
|
}
|
|
329
360
|
|
|
330
361
|
/** @internal The NAMED members of a caller's resource object, read ONCE each into a plain record. A
|
|
@@ -363,7 +394,7 @@ var SPEC_NAMES = intrinsic.create(null);
|
|
|
363
394
|
})();
|
|
364
395
|
|
|
365
396
|
function _validate(kind, obj, pre) {
|
|
366
|
-
if (!_isObject(obj)) throw E(
|
|
397
|
+
if (!_isObject(obj)) throw E(_badKindCode(kind), "an ACME " + kind + " must be a JSON object");
|
|
367
398
|
var spec = SPECS[kind];
|
|
368
399
|
/** @internal Every own member is read ONCE, here, before any check runs, and every check below decides
|
|
369
400
|
* from this reading. The walk used to read `obj[field.name]` three times per field, for the closed-enum
|
|
@@ -396,7 +427,7 @@ function _validate(kind, obj, pre) {
|
|
|
396
427
|
throw E("acme/bad-status", "the " + kind + " " + field.name + " " + JSON.stringify(value) + " is not a recognized value");
|
|
397
428
|
}
|
|
398
429
|
var err = _checkType(kind, field, value, read);
|
|
399
|
-
if (err) throw E(
|
|
430
|
+
if (err) throw E(_badKindCode(kind), "the " + kind + " field " + JSON.stringify(field.name) + " " + err);
|
|
400
431
|
}
|
|
401
432
|
return obj;
|
|
402
433
|
}
|
|
@@ -866,16 +897,16 @@ function newAccount(o) {
|
|
|
866
897
|
o = _gateRequestOpts(o, "newAccount");
|
|
867
898
|
if (!_isObject(o.jwk)) throw E("acme/bad-input", "newAccount must embed the account public jwk (RFC 8555 sec. 7.3)");
|
|
868
899
|
var payload = {};
|
|
869
|
-
if (o.contact
|
|
870
|
-
if (o.termsOfServiceAgreed
|
|
900
|
+
if (o.contact != null) { _assertContacts(o.contact); payload.contact = o.contact; }
|
|
901
|
+
if (o.termsOfServiceAgreed != null) {
|
|
871
902
|
if (typeof o.termsOfServiceAgreed !== "boolean") throw E("acme/bad-input", "termsOfServiceAgreed must be a boolean");
|
|
872
903
|
payload.termsOfServiceAgreed = o.termsOfServiceAgreed;
|
|
873
904
|
}
|
|
874
|
-
if (o.onlyReturnExisting
|
|
905
|
+
if (o.onlyReturnExisting != null) {
|
|
875
906
|
if (typeof o.onlyReturnExisting !== "boolean") throw E("acme/bad-input", "onlyReturnExisting must be a boolean");
|
|
876
907
|
payload.onlyReturnExisting = o.onlyReturnExisting;
|
|
877
908
|
}
|
|
878
|
-
if (o.externalAccountBinding
|
|
909
|
+
if (o.externalAccountBinding != null) {
|
|
879
910
|
if (!_isObject(o.externalAccountBinding)) throw E("acme/bad-input", "externalAccountBinding must be an EAB inner JWS object");
|
|
880
911
|
payload.externalAccountBinding = o.externalAccountBinding;
|
|
881
912
|
}
|
|
@@ -969,9 +1000,9 @@ function newOrder(o) {
|
|
|
969
1000
|
o = _gateRequestOpts(o, "newOrder");
|
|
970
1001
|
if (!Array.isArray(o.identifiers) || o.identifiers.length === 0) throw E("acme/bad-order", "newOrder requires a non-empty identifiers array (RFC 8555 sec. 7.4)");
|
|
971
1002
|
var payload = { identifiers: o.identifiers.map(_validateOrderIdentifier) };
|
|
972
|
-
if (o.notBefore
|
|
973
|
-
if (o.notAfter
|
|
974
|
-
if (o.replaces
|
|
1003
|
+
if (o.notBefore != null) { if (!_isRfc3339(o.notBefore)) throw E("acme/bad-order", "notBefore must be an RFC 3339 date-time"); payload.notBefore = o.notBefore; }
|
|
1004
|
+
if (o.notAfter != null) { if (!_isRfc3339(o.notAfter)) throw E("acme/bad-order", "notAfter must be an RFC 3339 date-time"); payload.notAfter = o.notAfter; }
|
|
1005
|
+
if (o.replaces != null) { if (!_isString(o.replaces)) throw E("acme/bad-order", "replaces must be an ARI certID string (RFC 9773 sec. 5)"); payload.replaces = o.replaces; }
|
|
975
1006
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
|
|
976
1007
|
});
|
|
977
1008
|
}
|
|
@@ -1154,7 +1185,7 @@ async function finalize(o) {
|
|
|
1154
1185
|
if (!guard.bytes.isByteSource(o.csr)) throw E("acme/bad-input", "finalize requires a DER CSR BufferSource (opts.csr)");
|
|
1155
1186
|
var csrBuf = guard.bytes.snapshotSource(o.csr, E, "acme/bad-input", "the finalize CSR");
|
|
1156
1187
|
var ids = null;
|
|
1157
|
-
if (identifiers
|
|
1188
|
+
if (identifiers != null) {
|
|
1158
1189
|
if (!Array.isArray(identifiers) || identifiers.length === 0) throw E("acme/bad-input", "finalize identifiers must be the non-empty order identifier array");
|
|
1159
1190
|
ids = [];
|
|
1160
1191
|
for (var ii = 0; ii < identifiers.length; ii++) {
|
|
@@ -1165,7 +1196,7 @@ async function finalize(o) {
|
|
|
1165
1196
|
}
|
|
1166
1197
|
}
|
|
1167
1198
|
var parsed = csr.parse(csrBuf);
|
|
1168
|
-
if (accountJwk
|
|
1199
|
+
if (accountJwk != null) {
|
|
1169
1200
|
var accountSpki = await _jwkToSpki(accountJwk);
|
|
1170
1201
|
if (parsed.subjectPublicKeyInfo && Buffer.isBuffer(parsed.subjectPublicKeyInfo.bytes) &&
|
|
1171
1202
|
parsed.subjectPublicKeyInfo.bytes.equals(accountSpki)) {
|
|
@@ -1202,7 +1233,7 @@ function challengeResponse(o) {
|
|
|
1202
1233
|
return _promised(function () {
|
|
1203
1234
|
if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
|
|
1204
1235
|
o = _gateRequestOpts(o, "challengeResponse");
|
|
1205
|
-
var payload = o.payload
|
|
1236
|
+
var payload = o.payload != null ? o.payload : {};
|
|
1206
1237
|
if (!_isObject(payload)) throw E("acme/bad-input", "a challenge response payload must be a JSON object (RFC 8555 sec. 7.5.1)");
|
|
1207
1238
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
|
|
1208
1239
|
});
|
|
@@ -1301,7 +1332,7 @@ function revokeCert(o) {
|
|
|
1301
1332
|
var hasJwk = _hasOwn(o, "jwk");
|
|
1302
1333
|
if (hasKid === hasJwk) throw E("acme/bad-input", "revokeCert must be signed with EXACTLY ONE of the account kid or the certificate jwk (RFC 8555 sec. 7.6)");
|
|
1303
1334
|
var payload = { certificate: jose.base64url.encode(certBuf) };
|
|
1304
|
-
if (o.reason
|
|
1335
|
+
if (o.reason != null) {
|
|
1305
1336
|
if (typeof o.reason !== "number" || !isFinite(o.reason) || Math.floor(o.reason) !== o.reason || CRL_REASONS.indexOf(o.reason) === -1) {
|
|
1306
1337
|
throw E("acme/bad-revocation-reason", "reason must be an assigned RFC 5280 CRLReason (0-6, 8-10; value 7 is unassigned)");
|
|
1307
1338
|
}
|
|
@@ -1514,10 +1545,17 @@ function _queryRepaired(rawRef, resolvedUrl) {
|
|
|
1514
1545
|
return qi !== -1 && rawRef.slice(qi).split("#")[0] !== _urlSearch(resolvedUrl);
|
|
1515
1546
|
}
|
|
1516
1547
|
var _defaultSleep = require("./sleep").sleep;
|
|
1517
|
-
/** @internal The settings this client forwards to its transport.
|
|
1518
|
-
|
|
1548
|
+
/** @internal The settings this client forwards to its transport. The distrust list is typed here, when
|
|
1549
|
+
* the client is built, and the frozen copy is the one every request carries. A setting not on the list
|
|
1550
|
+
* is refused. */
|
|
1551
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
|
|
1552
|
+
var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
|
|
1519
1553
|
function _clientTls(o) {
|
|
1520
|
-
|
|
1554
|
+
var tls = o.tls;
|
|
1555
|
+
guard.identifier.assertKnownKeys(tls, TLS_KNOWN, E, "acme/bad-input", "unknown opts.tls setting ");
|
|
1556
|
+
var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
|
|
1557
|
+
t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, E, "acme/bad-input", "opts.tls.distrustedKeys");
|
|
1558
|
+
return t;
|
|
1521
1559
|
}
|
|
1522
1560
|
function _splitPemChain(text) {
|
|
1523
1561
|
var ders;
|
|
@@ -1877,7 +1915,7 @@ function _parseLinkNext(headers, base) { var m = _parseLinkRelation(headers, bas
|
|
|
1877
1915
|
* @opts
|
|
1878
1916
|
* - `accountKey` / `accountJwk` / `alg` -- REQUIRED: the account private key, its public JWK, and the JWS alg.
|
|
1879
1917
|
* - `transport` -- injectable `transport(request) -> Promise<{status, headers, body}>`; default pki.transport.https. It must return a promise of the response; anything else is `acme/bad-input`.
|
|
1880
|
-
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity } for the default transport.
|
|
1918
|
+
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys } for the default transport. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `acme/server-key-distrusted`, on every request including a cross-origin one, and a malformed list is `acme/bad-input` when the client is built. A setting not named in that list is refused `acme/bad-input` when the client is built.
|
|
1881
1919
|
* - `timeout` / `maxResponseBytes` / `maxRedirects` -- transport budgets; `maxNonceRetries` -- badNonce retry cap (default 1).
|
|
1882
1920
|
* - `proxy` -- reach the ACME server through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
|
|
1883
1921
|
* - `maxPolls` / `maxTotalWait` / `sleep` -- poll-loop budgets + an injectable sleeper; `clock` -- an injectable receipt clock (default Date.now) for a Retry-After HTTP-date.
|
|
@@ -1976,14 +2014,16 @@ function client(directoryUrl, opts) {
|
|
|
1976
2014
|
var resignKeys = _resignKeysOf(opts.resignKeys);
|
|
1977
2015
|
|
|
1978
2016
|
var transport = checkedTransport;
|
|
2017
|
+
/** @internal One record answers both the trust-anchor check and every request. */
|
|
2018
|
+
var clientTls = _clientTls(opts);
|
|
1979
2019
|
if (!transport) {
|
|
1980
|
-
var t0 =
|
|
2020
|
+
var t0 = clientTls;
|
|
1981
2021
|
var hasAnchors = t0.anchors !== undefined && t0.anchors !== null && !(Array.isArray(t0.anchors) && t0.anchors.length === 0);
|
|
1982
2022
|
if (!hasAnchors && t0.useSystemStore !== true) throw E("acme/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true (RFC 8555 sec. 6.1)");
|
|
1983
2023
|
transport = httpTransport.https({ E: E, errPrefix: "acme" });
|
|
1984
2024
|
}
|
|
1985
2025
|
var budgets = {
|
|
1986
|
-
tls:
|
|
2026
|
+
tls: clientTls,
|
|
1987
2027
|
proxy: httpTransport.snapshotProxy(opts.proxy),
|
|
1988
2028
|
timeout: guard.limits.cap(opts.timeout, "timeout", CLIENT_DEFAULT_TIMEOUT, { E: E, code: "acme/bad-input", min: 1, max: CLIENT_MAX_TIMEOUT }),
|
|
1989
2029
|
maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: E, code: "acme/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES }),
|
|
@@ -2234,17 +2274,17 @@ function client(directoryUrl, opts) {
|
|
|
2234
2274
|
|
|
2235
2275
|
function _bindingSpec(opts) {
|
|
2236
2276
|
var spki = opts.expectedSpki, ids = opts.identifiers, req = opts.requireBinding;
|
|
2237
|
-
if (spki
|
|
2277
|
+
if (spki != null) {
|
|
2238
2278
|
if (!guard.bytes.isByteSource(spki)) throw E("acme/bad-input", "downloadCertificate opts.expectedSpki must be a DER SubjectPublicKeyInfo BufferSource");
|
|
2239
2279
|
spki = guard.bytes.snapshotSource(spki, AcmeError, "acme/bad-input", "downloadCertificate expectedSpki");
|
|
2240
2280
|
}
|
|
2241
|
-
if (ids
|
|
2281
|
+
if (ids != null) {
|
|
2242
2282
|
if (!_isArrayI(ids) || ids.length === 0) throw E("acme/bad-input", "downloadCertificate opts.identifiers must be the non-empty order identifier array");
|
|
2243
2283
|
ids = _mapI(ids, _validateOrderIdentifier);
|
|
2244
2284
|
}
|
|
2245
|
-
if (req
|
|
2246
|
-
var require_ = req
|
|
2247
|
-
if (require_ && spki
|
|
2285
|
+
if (req != null && typeof req !== "boolean") throw E("acme/bad-input", "downloadCertificate opts.requireBinding must be a boolean");
|
|
2286
|
+
var require_ = req == null ? true : req;
|
|
2287
|
+
if (require_ && spki == null && ids == null) {
|
|
2248
2288
|
throw E("acme/binding-required",
|
|
2249
2289
|
"downloadCertificate needs the material that binds the issued certificate to this order -- " +
|
|
2250
2290
|
"opts.expectedSpki (the DER SubjectPublicKeyInfo the CSR asked to have certified) and/or " +
|
|
@@ -2258,7 +2298,7 @@ function client(directoryUrl, opts) {
|
|
|
2258
2298
|
var leaf;
|
|
2259
2299
|
try { leaf = x509.parse(leafDer); }
|
|
2260
2300
|
catch (e) { throw E("acme/bad-certificate-chain", "the downloaded end-entity certificate did not parse (RFC 8555 sec. 7.4.2)", e); }
|
|
2261
|
-
if (spec.spki
|
|
2301
|
+
if (spec.spki != null) {
|
|
2262
2302
|
var got = leaf.subjectPublicKeyInfo && leaf.subjectPublicKeyInfo.bytes;
|
|
2263
2303
|
if (!_isBuffer(got) || !guard.crypto.constantTimeEqual(got, spec.spki)) {
|
|
2264
2304
|
throw E("acme/certificate-key-mismatch",
|
|
@@ -2266,7 +2306,7 @@ function client(directoryUrl, opts) {
|
|
|
2266
2306
|
"to have certified, so it cannot be used with the private key held here (RFC 8555 sec. 7.4)");
|
|
2267
2307
|
}
|
|
2268
2308
|
}
|
|
2269
|
-
if (spec.ids
|
|
2309
|
+
if (spec.ids != null) {
|
|
2270
2310
|
var have = _certIdentifierSet(leaf), want = _orderIdentifierSet(spec.ids);
|
|
2271
2311
|
var haveKeys = _objectKeys(have), wantKeys = _objectKeys(want);
|
|
2272
2312
|
var mismatch = haveKeys.length !== wantKeys.length ||
|
|
@@ -2284,7 +2324,7 @@ function client(directoryUrl, opts) {
|
|
|
2284
2324
|
return _resolve(_Promise, undefined).then(function () {
|
|
2285
2325
|
opts = _gateOpts(opts, _METHOD_OPTS.download, "downloadCertificate");
|
|
2286
2326
|
var select = opts.selectChain;
|
|
2287
|
-
if (select
|
|
2327
|
+
if (select != null && typeof select !== "function") throw E("acme/bad-input", "downloadCertificate opts.selectChain must be a function");
|
|
2288
2328
|
var maxAlt = guard.limits.cap(opts.maxAlternates, "maxAlternates", DEFAULT_MAX_ALTERNATES, { E: E, code: "acme/bad-input", min: 0, max: 64 });
|
|
2289
2329
|
var binding = _bindingSpec(opts);
|
|
2290
2330
|
var dlUrl = _clientUrl(url);
|
|
@@ -2297,7 +2337,10 @@ function client(directoryUrl, opts) {
|
|
|
2297
2337
|
function result(cand) {
|
|
2298
2338
|
_assertCertBinding(cand.certificate, binding);
|
|
2299
2339
|
return { certificate: cand.certificate, chain: cand.chain, certificates: cand.certificates, alternates: alternates,
|
|
2300
|
-
|
|
2340
|
+
/** @internal Read as "not absent", not as "not undefined": with the binding
|
|
2341
|
+
* material left out as `null` the record carries a null, and a test against
|
|
2342
|
+
* `undefined` alone reported the certificate as bound to a key nobody named. */
|
|
2343
|
+
boundToKey: binding.spki != null, boundToIdentifiers: binding.ids != null };
|
|
2301
2344
|
}
|
|
2302
2345
|
if (!select) return result(primary);
|
|
2303
2346
|
return _resolve(_Promise, select(primary)).then(function (primaryMatch) {
|
|
@@ -2327,7 +2370,7 @@ function client(directoryUrl, opts) {
|
|
|
2327
2370
|
var certKeyMode = o.certKey != null || _isObject(o.certJwk) || o.certAlg != null;
|
|
2328
2371
|
if (certKeyMode && !(o.certKey != null && _isObject(o.certJwk) && _isString(o.certAlg))) throw E("acme/bad-input", "revokeCert certificate-key mode requires certKey, certJwk, AND certAlg together, or none (RFC 8555 sec. 7.6)");
|
|
2329
2372
|
var extra = { certificate: o.certificate };
|
|
2330
|
-
if (o.reason
|
|
2373
|
+
if (o.reason != null) extra.reason = o.reason;
|
|
2331
2374
|
return _resource("revokeCert").then(function (url) {
|
|
2332
2375
|
if (certKeyMode) {
|
|
2333
2376
|
return _postJws(url, function (nonce) {
|
|
@@ -2347,10 +2390,10 @@ function client(directoryUrl, opts) {
|
|
|
2347
2390
|
function _updateAccount(o) {
|
|
2348
2391
|
return _promised(function () {
|
|
2349
2392
|
o = guard.identifier.optionsObject(o, E, "acme/bad-input", "pki.acme updateAccount options");
|
|
2350
|
-
if (o.status
|
|
2351
|
-
if (o.termsOfServiceAgreed
|
|
2352
|
-
if (o.orders
|
|
2353
|
-
if (o.externalAccountBinding
|
|
2393
|
+
if (o.status != null) throw E("acme/bad-input", "updateAccount cannot change the account status; use deactivateAccount (RFC 8555 sec. 7.3.6 is the only client-settable status)");
|
|
2394
|
+
if (o.termsOfServiceAgreed != null) throw E("acme/bad-input", "the server MUST ignore a termsOfServiceAgreed update to the account (RFC 8555 sec. 7.3.2); it is set at account creation, and a changed-terms CA answers with a userActionRequired problem (sec. 7.3.3) directing a human to the terms URL");
|
|
2395
|
+
if (o.orders != null) throw E("acme/bad-input", "the account orders URL is server-assigned and cannot be updated (RFC 8555 sec. 7.3.2)");
|
|
2396
|
+
if (o.externalAccountBinding != null) throw E("acme/bad-input", "externalAccountBinding is not updateable by the client (RFC 8555 sec. 7.1.2 / 7.3.4)");
|
|
2354
2397
|
guard.identifier.assertKnownKeys(o, _METHOD_OPTS.updateAccount, E, "acme/bad-input", "unknown updateAccount option ");
|
|
2355
2398
|
var account = _requireKid();
|
|
2356
2399
|
return _post(account, updateAccount, { contact: o.contact }, "kid").then(function (res) { return validate("account", _json(res)); });
|
|
@@ -2435,10 +2478,10 @@ function client(directoryUrl, opts) {
|
|
|
2435
2478
|
return _resolve(_Promise, undefined).then(function () {
|
|
2436
2479
|
o = _gateOpts(o, _METHOD_OPTS.renewalWindow, "renewalWindow");
|
|
2437
2480
|
if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "renewalWindow requires a DER certificate BufferSource");
|
|
2438
|
-
if (o.random
|
|
2439
|
-
if (o.clock
|
|
2440
|
-
if (o.replaced
|
|
2441
|
-
if (o.previous
|
|
2481
|
+
if (o.random != null && typeof o.random !== "function") throw E("acme/bad-input", "renewalWindow opts.random must be a function returning a number in [0, 1]");
|
|
2482
|
+
if (o.clock != null && typeof o.clock !== "function") throw E("acme/bad-input", "renewalWindow opts.clock must be a function returning epoch milliseconds");
|
|
2483
|
+
if (o.replaced != null && typeof o.replaced !== "boolean") throw E("acme/bad-input", "renewalWindow opts.replaced must be a boolean");
|
|
2484
|
+
if (o.previous != null && !_isObject(o.previous)) throw E("acme/bad-input", "renewalWindow opts.previous must be a prior renewalWindow result object");
|
|
2442
2485
|
/** @internal `previous` is READ ONCE, here, before the await below, and the stickiness decision
|
|
2443
2486
|
* reads this reading. It was validated as an object here and then read three more times after the
|
|
2444
2487
|
* renewalInfo fetch: `o.previous.suggestedWindow` twice in one expression, and
|
|
@@ -2446,7 +2489,7 @@ function client(directoryUrl, opts) {
|
|
|
2446
2489
|
* each read separately and the fetch is the window in which to change its answer, so the prior
|
|
2447
2490
|
* window this compares against need not be the one the caller validated, and a non-object arriving
|
|
2448
2491
|
* after the await reached `.start` and raised a bare TypeError. */
|
|
2449
|
-
var prev = o.previous
|
|
2492
|
+
var prev = o.previous == null ? undefined : _readOnce(o.previous, PREVIOUS_WINDOW_NAMES);
|
|
2450
2493
|
var prevWindow = prev && _isObject(prev.suggestedWindow) ? _readOnce(prev.suggestedWindow, WINDOW_BOUND_NAMES) : null;
|
|
2451
2494
|
var _clk = typeof o.clock === "function" ? o.clock : clock;
|
|
2452
2495
|
function clk() { var t = _clk(); if (typeof t !== "number" || !isFinite(t)) throw E("acme/bad-input", "the renewalWindow clock returned a non-finite value"); return t; }
|
|
@@ -2490,11 +2533,13 @@ function client(directoryUrl, opts) {
|
|
|
2490
2533
|
return _resolve(_Promise).then(function () {
|
|
2491
2534
|
o = _gateOpts(o, _METHOD_OPTS.scheduleRenewal, "scheduleRenewal");
|
|
2492
2535
|
if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "scheduleRenewal requires a DER certificate BufferSource");
|
|
2493
|
-
if (o.random
|
|
2494
|
-
if (o.shouldStop
|
|
2495
|
-
if (o.renew
|
|
2536
|
+
if (o.random != null && typeof o.random !== "function") throw E("acme/bad-input", "scheduleRenewal opts.random must be a function returning a number in [0, 1]");
|
|
2537
|
+
if (o.shouldStop != null && typeof o.shouldStop !== "function") throw E("acme/bad-input", "scheduleRenewal opts.shouldStop must be a function returning a boolean");
|
|
2538
|
+
if (o.renew != null && typeof o.renew !== "function") throw E("acme/bad-input", "scheduleRenewal opts.renew must be an async function (decision) -> newCertDer | void");
|
|
2539
|
+
/** @internal The value handed in is the caller's own option, so absence keeps both
|
|
2540
|
+
* of its forms here: a `null` states no count and reads as an omitted one. */
|
|
2496
2541
|
function optCount(v, name) {
|
|
2497
|
-
if (v
|
|
2542
|
+
if (v == null) return null;
|
|
2498
2543
|
if (typeof v !== "number" || !intrinsic.isFinite(v) || v < 1 || intrinsic.floor(v) !== v) throw E("acme/bad-input", "scheduleRenewal opts." + name + " must be a positive integer");
|
|
2499
2544
|
return v;
|
|
2500
2545
|
}
|
package/lib/asn1-der.js
CHANGED
|
@@ -997,12 +997,13 @@ function intToDer(v) {
|
|
|
997
997
|
|
|
998
998
|
function encodeOidContent(dotted) {
|
|
999
999
|
guard.identifier.assertCanonicalOid(dotted, _oidErr, "oid/bad-input", "OID", "oid/bad-arc", "oid/too-many-subidentifiers");
|
|
1000
|
-
var arcs =
|
|
1000
|
+
var arcs = intrinsic.mapList(intrinsic.splitChar(dotted, 46), function (p) { return intrinsic.BigInt(p); });
|
|
1001
1001
|
var a1 = arcs[0], a2 = arcs[1];
|
|
1002
|
-
/** @internal The sub-identifier list
|
|
1003
|
-
* `Array.prototype.concat
|
|
1004
|
-
* the OID a signature or an
|
|
1005
|
-
|
|
1002
|
+
/** @internal The sub-identifier list is assembled by `concatList`, which copies own indexes. A
|
|
1003
|
+
* captured `Array.prototype.concat` still reads `Symbol.isConcatSpreadable` on each argument, so an
|
|
1004
|
+
* inherited hook decided which arcs this encodes, and the result is the OID a signature or an
|
|
1005
|
+
* algorithm is named by. The split and the slice are the captured scans for the same reason. */
|
|
1006
|
+
var subids = intrinsic.concatList([a1 * 40n + a2], intrinsic.copyList(arcs, 2));
|
|
1006
1007
|
var out = [];
|
|
1007
1008
|
for (var s = 0; s < subids.length; s++) {
|
|
1008
1009
|
var v = subids[s];
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -709,7 +709,7 @@ function _checkTargeting(ac, want) {
|
|
|
709
709
|
if (ac.extensions[i].name === "targetInformation") ext = ac.extensions[i];
|
|
710
710
|
}
|
|
711
711
|
if (!ext) return guard.verdict.of({ checked: true, ok: true });
|
|
712
|
-
if (want
|
|
712
|
+
if (want == null) {
|
|
713
713
|
return guard.verdict.of({ checked: false, ok: false,
|
|
714
714
|
reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" });
|
|
715
715
|
}
|
|
@@ -860,11 +860,12 @@ function _fixVerifyOptions(opts) {
|
|
|
860
860
|
guard.identifier.assertKnownKeys(o, KNOWN_VERIFY_OPTS, _err, "attrcert/bad-input",
|
|
861
861
|
"pki.attrcert.verify has an unknown option ");
|
|
862
862
|
var at = null;
|
|
863
|
-
if (o.time
|
|
863
|
+
if (o.time != null) {
|
|
864
864
|
guard.time.assertValid(o.time, _err, "attrcert/bad-input", "pki.attrcert.verify opts.time");
|
|
865
865
|
at = guard.time.instantOf(o.time);
|
|
866
866
|
}
|
|
867
867
|
var revocationStatus = null;
|
|
868
|
+
// allow:caller-option-presence-tested-against-undefined -- absence means no revocation status is applied, so a null must be refused
|
|
868
869
|
if (o.revocationStatus !== undefined) {
|
|
869
870
|
if (typeof o.revocationStatus !== "string" ||
|
|
870
871
|
!_hasOwn(REVOCATION_STATUS, o.revocationStatus)) {
|
|
@@ -874,7 +875,7 @@ function _fixVerifyOptions(opts) {
|
|
|
874
875
|
}
|
|
875
876
|
return {
|
|
876
877
|
at: at, revocationStatus: revocationStatus,
|
|
877
|
-
target: o.target
|
|
878
|
+
target: o.target == null ? undefined : _callerTarget(o.target),
|
|
878
879
|
};
|
|
879
880
|
}
|
|
880
881
|
|
package/lib/cmp-build.js
CHANGED
|
@@ -1009,10 +1009,16 @@ function _cmpMessageDer(input) {
|
|
|
1009
1009
|
return der;
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
|
-
/** @internal The settings this transfer forwards to its transport
|
|
1013
|
-
|
|
1012
|
+
/** @internal The settings this transfer forwards to its transport, with the distrust list typed into a
|
|
1013
|
+
* frozen copy. A setting not on the list is refused. */
|
|
1014
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
|
|
1015
|
+
var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
|
|
1014
1016
|
function _tlsForTransfer(opts) {
|
|
1015
|
-
|
|
1017
|
+
var tls = opts.tls;
|
|
1018
|
+
guard.identifier.assertKnownKeys(tls, TLS_KNOWN, _err, "cmp/bad-input", "unknown opts.tls setting ");
|
|
1019
|
+
var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
|
|
1020
|
+
t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, _err, "cmp/bad-input", "opts.tls.distrustedKeys");
|
|
1021
|
+
return t;
|
|
1016
1022
|
}
|
|
1017
1023
|
|
|
1018
1024
|
/** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
|
|
@@ -1076,8 +1082,10 @@ function _transfer(url, message, opts) {
|
|
|
1076
1082
|
var parsedUrl;
|
|
1077
1083
|
try { parsedUrl = new _URL(_stringOf(url)); }
|
|
1078
1084
|
catch (e) { throw _err("cmp/bad-url", "the CMP URL did not parse: " + guard.text.showValue(url), e); }
|
|
1085
|
+
/** @internal One record answers both the trust-anchor check and the request. */
|
|
1086
|
+
var tls = _tlsForTransfer(opts);
|
|
1079
1087
|
if (!transport) {
|
|
1080
|
-
var t =
|
|
1088
|
+
var t = tls;
|
|
1081
1089
|
var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
|
|
1082
1090
|
if (!hasAnchors && t.useSystemStore !== true) throw _err("cmp/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned server (RFC 9811 sec. 5)");
|
|
1083
1091
|
transport = httpTransport.https({ E: _err, errPrefix: "cmp" });
|
|
@@ -1093,7 +1101,6 @@ function _transfer(url, message, opts) {
|
|
|
1093
1101
|
if (lk !== "content-length" && lk !== "transfer-encoding" && lk !== "content-type") headers[k] = opts.headers[k];
|
|
1094
1102
|
});
|
|
1095
1103
|
headers["content-type"] = PKIXCMP;
|
|
1096
|
-
var tls = _tlsForTransfer(opts);
|
|
1097
1104
|
return guard.async.invoked(transport, [{ method: "POST", url: parsedUrl.href, headers: headers, body: der, tls: tls, proxy: proxy, timeout: timeout, maxResponseBytes: maxResponseBytes }], _err, "cmp/bad-input", "opts.transport").then(function (res) {
|
|
1098
1105
|
res = res || intrinsic.create(null);
|
|
1099
1106
|
var h = guard.header.lowerCased(res.headers);
|
|
@@ -1185,8 +1192,11 @@ function wellKnownUrl(base, opts) {
|
|
|
1185
1192
|
* @opts
|
|
1186
1193
|
* - `transport` -- an injectable `transport(request) -> Promise<{status,headers,body}>` (default
|
|
1187
1194
|
* `pki.transport.https`, which fail-closes on a non-https URL and an unpinned server).
|
|
1188
|
-
* - `tls` -- `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }` for the
|
|
1195
|
+
* - `tls` -- `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }` for the
|
|
1189
1196
|
* default transport (mutual-TLS `cert`/`key` is common for CMP in addition to message protection).
|
|
1197
|
+
* `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain
|
|
1198
|
+
* carries a listed key is refused with `cmp/server-key-distrusted`, and a malformed list is `cmp/bad-input`. A
|
|
1199
|
+
* setting not named in that list is refused `cmp/bad-input`.
|
|
1190
1200
|
* - `headers` -- extra request headers. The `content-type: application/pkixcmp` is always set and not
|
|
1191
1201
|
* overridable (any casing), and the request-framing headers (`content-length`, `transfer-encoding`) are
|
|
1192
1202
|
* stripped -- the transport computes Content-Length from the exact body, so a caller cannot desync framing.
|
package/lib/cmp-session.js
CHANGED
|
@@ -70,7 +70,7 @@ var KNOWN_SESSION_OPTS = Object.assign(Object.create(null), {
|
|
|
70
70
|
extraCerts: 1, implicitConfirm: 1, maxPolls: 1, maxTotalWait: 1, sleep: 1, time: 1,
|
|
71
71
|
transport: 1, tls: 1, headers: 1, timeout: 1, maxResponseBytes: 1, proxy: 1, pss: 1, digestAlgorithm: 1,
|
|
72
72
|
acceptCert: 1, senderKID: 1, recipKID: 1, expectedSender: 1, acceptCentralKeyGeneration: 1,
|
|
73
|
-
authorizedBySharedSecret: 1,
|
|
73
|
+
authorizedBySharedSecret: 1, distrustedKeys: 1,
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
var TRANSCRIPT_RETAIN_RESPONSES = 2;
|
|
@@ -407,6 +407,7 @@ function _certConfHash(certDer) {
|
|
|
407
407
|
* - `url` -- REQUIRED: the CMP endpoint URL.
|
|
408
408
|
* - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }` for PBMAC1 protection.
|
|
409
409
|
* - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the issued certificate's own signature + chain before confirmation (not the response protection). `intermediates` supplies an extra chain pool.
|
|
410
|
+
* - `distrustedKeys`: SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or 32 bytes each), read once at construction. A listed key on the response signer's chain is `cmp/untrusted-signer`, on the issued certificate's chain `cmp/bad-cert-response`, and on the key generation authority's chain `cmp/unauthorized-kga`, which also refuses a listed authority key under `authorizedBySharedSecret`. A `rootCaKeyUpdate` answer whose new root key is listed is `cmp/bad-info-value`. A malformed list, or a list naming a key on a session without `trustAnchors`, is `cmp/bad-input`; an empty list refuses nothing and is accepted.
|
|
410
411
|
* - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an empty subject (identified only by its subjectAltName) REQUIRES an explicit `sender`, because the empty subject cannot name the requester for a peer that binds the sender to the SAN.
|
|
411
412
|
* - `senderKID` / `recipKID` -- optional key identifiers emitted on every request header, so a CA selecting among several shared secrets (senderKID) or recipient keys resolves the right credential.
|
|
412
413
|
* - `expectedSender` -- optional CA signer certificate (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it also resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it, rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
|
|
@@ -454,6 +455,10 @@ function session(opts) {
|
|
|
454
455
|
if (!hasAnchors) throw _err("cmp/bad-input", "signature protection requires opts.trustAnchors to authenticate the CA's response signer (RFC 9483 sec. 3.2)");
|
|
455
456
|
}
|
|
456
457
|
if (isMac && Array.isArray(opts.trustAnchors) && opts.trustAnchors.length === 0) opts.trustAnchors = null;
|
|
458
|
+
var _distrusted = guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys");
|
|
459
|
+
if (_distrusted !== null && _distrusted.length > 0 && opts.trustAnchors == null) {
|
|
460
|
+
throw _err("cmp/bad-input", "opts.distrustedKeys applies to the certificate paths this session validates against opts.trustAnchors, and this shared-secret session supplies none");
|
|
461
|
+
}
|
|
457
462
|
if (opts.acceptCentralKeyGeneration != null && typeof opts.acceptCentralKeyGeneration !== "boolean") {
|
|
458
463
|
throw _err("cmp/bad-input", "opts.acceptCentralKeyGeneration is a boolean stating that this caller accepts a key pair the authority generated (RFC 9483 sec. 4.1.6)");
|
|
459
464
|
}
|
|
@@ -596,7 +601,10 @@ function session(opts) {
|
|
|
596
601
|
function _verifyOpts(fresh, signerCertOverride, extraIntermediates, responseExtra) {
|
|
597
602
|
var o = Object.assign(Object.create(null), { transactionID: transactionID, expectRecipNonce: fresh });
|
|
598
603
|
if (isMac) o.sharedSecret = opts.mac.secret;
|
|
599
|
-
else if (opts.trustAnchors != null)
|
|
604
|
+
else if (opts.trustAnchors != null) {
|
|
605
|
+
o.trustAnchors = opts.trustAnchors;
|
|
606
|
+
if (_distrusted !== null) o.distrustedKeys = _distrusted;
|
|
607
|
+
}
|
|
600
608
|
var issuers = Array.isArray(responseExtra) ? responseExtra : [];
|
|
601
609
|
var extra = [];
|
|
602
610
|
if (Array.isArray(extraIntermediates)) {
|
|
@@ -974,6 +982,10 @@ function session(opts) {
|
|
|
974
982
|
}
|
|
975
983
|
|
|
976
984
|
async function _validateLeaf(leaf, caPubs) {
|
|
985
|
+
/** @internal Checked before the anchors are, since a session with none validates no chain and
|
|
986
|
+
* this is then the only check the issued key meets. */
|
|
987
|
+
var leafRefusal = guard.distrust.refusal(_distrusted, x509.parse(leaf).subjectPublicKeyInfo.bytes, "opts.distrustedKeys");
|
|
988
|
+
if (leafRefusal !== null) throw _err("cmp/bad-cert-response", "the issued certificate's key " + leafRefusal);
|
|
977
989
|
if (_engine == null || opts.trustAnchors == null) return;
|
|
978
990
|
var caPubsList = [];
|
|
979
991
|
if (Array.isArray(caPubs)) caPubs.forEach(function (c) { if (Buffer.isBuffer(c)) _push(caPubsList,c); });
|
|
@@ -982,7 +994,9 @@ function session(opts) {
|
|
|
982
994
|
cachedChain.forEach(function (c) { if (Buffer.isBuffer(c)) _push(added,c); });
|
|
983
995
|
var pool = _boundedPool(caPubsList, added);
|
|
984
996
|
var res;
|
|
985
|
-
|
|
997
|
+
var leafOpts = { trustAnchors: _asCertList(opts.trustAnchors), intermediates: pool, validate: true, time: opts.time != null ? opts.time : new Date() };
|
|
998
|
+
if (_distrusted !== null) leafOpts.distrustedKeys = _distrusted;
|
|
999
|
+
try { res = await _engine.build(leaf, leafOpts); }
|
|
986
1000
|
catch (e) {
|
|
987
1001
|
if (e.code === "path/bad-input") throw _err("cmp/bad-input", "invalid trust / validation options for issued-certificate validation: " + guard.text.describeThrown(e), e);
|
|
988
1002
|
throw _err("cmp/bad-cert-response", "the issued certificate could not be validated to a supplied trust anchor: " + guard.text.describeThrown(e), e);
|
|
@@ -1041,6 +1055,9 @@ function session(opts) {
|
|
|
1041
1055
|
open.trustAnchors = _asOpenableCerts(opts.trustAnchors, "opts.trustAnchors", true);
|
|
1042
1056
|
if (opts.intermediates != null) open.intermediates = _asOpenableCerts(opts.intermediates, "opts.intermediates", false);
|
|
1043
1057
|
}
|
|
1058
|
+
/** @internal The list goes with either rule: on the chain the verb validates, or, under the
|
|
1059
|
+
* exemption, on the authority's own key. */
|
|
1060
|
+
if (_distrusted !== null) open.distrustedKeys = _distrusted;
|
|
1044
1061
|
/** @internal The instant the session validates every other certificate at governs the authority's
|
|
1045
1062
|
* chain too, so a historical enrollment does not judge one certificate at a stated time and
|
|
1046
1063
|
* another at the current one. */
|
|
@@ -1689,6 +1706,12 @@ function session(opts) {
|
|
|
1689
1706
|
var newWithOld = _asCertificate(value.newWithOld, "the rootCaKeyUpdate newWithOld");
|
|
1690
1707
|
var oldWithNew = value.oldWithNew == null ? null : _asCertificate(value.oldWithNew, "the rootCaKeyUpdate oldWithNew");
|
|
1691
1708
|
var pNewNew = x509.parse(newWithNew), pNewOld = x509.parse(newWithOld), pOldRoot = x509.parse(oldRootDer);
|
|
1709
|
+
var newRootRefusal = guard.distrust.refusal(_distrusted, pNewNew.subjectPublicKeyInfo.bytes, "opts.distrustedKeys");
|
|
1710
|
+
if (newRootRefusal !== null) throw _err("cmp/bad-info-value", "the rootCaKeyUpdate new root key " + newRootRefusal);
|
|
1711
|
+
/** @internal The old root's signature on newWithOld is what authorizes the new key, and the holder
|
|
1712
|
+
* of a compromised or distrusted old key can make that signature for any key. */
|
|
1713
|
+
var oldRootRefusal = guard.distrust.refusal(_distrusted, pOldRoot.subjectPublicKeyInfo.bytes, "opts.distrustedKeys");
|
|
1714
|
+
if (oldRootRefusal !== null) throw _err("cmp/bad-info-value", "the rootCaKeyUpdate old root key " + oldRootRefusal);
|
|
1692
1715
|
_assertCaCapable(pNewNew, "rootCaKeyUpdate newWithNew");
|
|
1693
1716
|
_assertCaCapable(pNewOld, "rootCaKeyUpdate newWithOld");
|
|
1694
1717
|
if (!cmpBuild.samePublicKey(pNewOld.subjectPublicKeyInfo.bytes, pNewNew.subjectPublicKeyInfo.bytes)) {
|
package/lib/cmp-verify.js
CHANGED
|
@@ -57,7 +57,7 @@ var _certExtDecoders = pkix.certExtensionDecoders(NS).byOid;
|
|
|
57
57
|
var KNOWN_VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
58
58
|
sharedSecret: 1, signerCert: 1, trustAnchors: 1, intermediates: 1, time: 1,
|
|
59
59
|
transactionID: 1, expectRecipNonce: 1, revocationChecker: 1, maxIterations: 1, kem: 1,
|
|
60
|
-
messageTimeTolerance: 1,
|
|
60
|
+
messageTimeTolerance: 1, distrustedKeys: 1,
|
|
61
61
|
});
|
|
62
62
|
var KNOWN_KEM_VERIFY_KEYS = intrinsic.assign(intrinsic.create(null),
|
|
63
63
|
{ sharedSecret: 1, transactionID: 1 });
|
|
@@ -651,6 +651,7 @@ async function _chainSigner(signer, m, opts, extra) {
|
|
|
651
651
|
}
|
|
652
652
|
var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: time };
|
|
653
653
|
if (opts.revocationChecker != null) buildOpts.revocationChecker = opts.revocationChecker;
|
|
654
|
+
if (opts.distrustedKeys !== null) buildOpts.distrustedKeys = opts.distrustedKeys;
|
|
654
655
|
try {
|
|
655
656
|
var res = await _engine.build(signer.der, buildOpts);
|
|
656
657
|
if (!res || res.valid !== true) return guard.verdict.of({ trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" });
|
|
@@ -826,10 +827,16 @@ function _fixVerifyOptions(opts, made) {
|
|
|
826
827
|
maxIterations: opts.maxIterations,
|
|
827
828
|
kem: _fixKem(opts.kem, made),
|
|
828
829
|
messageTimeTolerance: _fixTolerance(opts.messageTimeTolerance),
|
|
830
|
+
distrustedKeys: guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys"),
|
|
829
831
|
};
|
|
830
832
|
|
|
831
833
|
_assertEchoBytes(f.transactionID, "transactionID");
|
|
832
834
|
_assertEchoBytes(f.expectRecipNonce, "expectRecipNonce");
|
|
835
|
+
/** @internal Only a list that names a key is refused here: an empty one refuses nothing, so it
|
|
836
|
+
* asks for no check that the missing path would leave undone. */
|
|
837
|
+
if (f.distrustedKeys !== null && f.distrustedKeys.length > 0 && f.trustAnchors == null) {
|
|
838
|
+
throw _err("cmp/bad-input", "opts.distrustedKeys applies to the signer certificate's path, which is validated only against opts.trustAnchors; MAC and KEM protection validate no path");
|
|
839
|
+
}
|
|
833
840
|
return f;
|
|
834
841
|
}
|
|
835
842
|
|
|
@@ -874,7 +881,7 @@ async function _verify(message, opts) {
|
|
|
874
881
|
|
|
875
882
|
var KNOWN_OPEN_KEY_PACKAGE_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
876
883
|
key: 1, cert: 1, password: 1, trustAnchors: 1, intermediates: 1, time: 1,
|
|
877
|
-
authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1,
|
|
884
|
+
authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1, distrustedKeys: 1,
|
|
878
885
|
});
|
|
879
886
|
var OID_A_KEY_PACKAGE = oid.byName("aKeyPackage");
|
|
880
887
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
@@ -950,6 +957,7 @@ function _fixOpenKeyPackageOptions(opts, made) {
|
|
|
950
957
|
time: _fixTime(opts.time),
|
|
951
958
|
exempt: opts.authorizedBySharedSecret === true,
|
|
952
959
|
allowRsa15: opts.allowUnauthenticatedRsa15 === true,
|
|
960
|
+
distrustedKeys: guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys"),
|
|
953
961
|
};
|
|
954
962
|
if (opts.authorizedBySharedSecret != null && typeof opts.authorizedBySharedSecret !== "boolean") {
|
|
955
963
|
throw _err("cmp/bad-input", "opts.authorizedBySharedSecret is a boolean stating that this entity protected its request with the shared secret");
|
|
@@ -1020,6 +1028,10 @@ async function _openKeyPackage(container, opts) {
|
|
|
1020
1028
|
var signerCert = verdict.signers[0] && verdict.signers[0].cert;
|
|
1021
1029
|
if (!signerCert) throw _keyPackageErr("the SignedData names no signer certificate to authorize as the key generation authority");
|
|
1022
1030
|
_authorizeKga(verdict, signerCert, opts);
|
|
1031
|
+
/** @internal The shared-secret exemption replaces the authority's chain check, which is where the
|
|
1032
|
+
* list is otherwise applied, so the authority's own key is checked here. */
|
|
1033
|
+
var kgaRefusal = opts.exempt ? guard.distrust.refusal(opts.distrustedKeys, x509.parse(signerCert).subjectPublicKeyInfo.bytes, "opts.distrustedKeys") : null;
|
|
1034
|
+
if (kgaRefusal !== null) throw _err("cmp/unauthorized-kga", "the key generation authority's key " + kgaRefusal);
|
|
1023
1035
|
if (verdict.eContent == null) throw _keyPackageErr("the SignedData carries no encapsulated content, so no key package was delivered");
|
|
1024
1036
|
return guard.verdict.of({ keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true });
|
|
1025
1037
|
}
|
|
@@ -1047,6 +1059,7 @@ function _signedDataVerifyOpts(opts) {
|
|
|
1047
1059
|
var v = { trustAnchors: opts.trustAnchors, requiredEku: [OID_CM_KGA] };
|
|
1048
1060
|
if (opts.intermediates != null) v.certs = opts.intermediates;
|
|
1049
1061
|
if (opts.time != null) v.time = opts.time;
|
|
1062
|
+
if (opts.distrustedKeys !== null) v.distrustedKeys = opts.distrustedKeys;
|
|
1050
1063
|
return v;
|
|
1051
1064
|
}
|
|
1052
1065
|
|
|
@@ -1152,6 +1165,10 @@ function _coerceKeyPackageBytes(container) {
|
|
|
1152
1165
|
* authority. REQUIRED unless `authorizedBySharedSecret` is set.
|
|
1153
1166
|
* - `intermediates` (Buffer|Buffer[]|PEM) -- extra untrusted pool certificates for path building.
|
|
1154
1167
|
* - `time` (Date) -- the validity instant for path validation. Defaults to the current time.
|
|
1168
|
+
* - `distrustedKeys` (Array): SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters
|
|
1169
|
+
* or 32 bytes each). A listed key on the authority, an intermediate or the anchor is
|
|
1170
|
+
* `cmp/unauthorized-kga`. Under `authorizedBySharedSecret`, which validates no chain, the list
|
|
1171
|
+
* is applied to the authority's own key.
|
|
1155
1172
|
* - `authorizedBySharedSecret` (boolean) -- authorize the authority by the secret that protected
|
|
1156
1173
|
* the request rather than by a chain, which sec. 4.1.6 permits a MAC-protected exchange. Requires
|
|
1157
1174
|
* `opts.password` and refuses `opts.trustAnchors`.
|
|
@@ -1264,6 +1281,11 @@ function openKeyPackage(container, opts) {
|
|
|
1264
1281
|
* - `transactionID` (Buffer) -- opt-in: require `header.transactionID` to equal it (response-echo defense).
|
|
1265
1282
|
* - `expectRecipNonce` (Buffer) -- opt-in: require `header.recipNonce` to echo the sent sender nonce.
|
|
1266
1283
|
* - `revocationChecker` -- forwarded to `pki.path.validate` when chaining the signer certificate.
|
|
1284
|
+
* - `distrustedKeys` (Array): SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or
|
|
1285
|
+
* 32 bytes each). A listed key on the signer, an intermediate or the anchor makes the verdict
|
|
1286
|
+
* `trusted: false` with `cmp/untrusted-signer`. A list that names a key requires `trustAnchors`, so
|
|
1287
|
+
* a MAC- or KEM-protected message refuses one as `cmp/bad-input`, as it does a malformed list; an
|
|
1288
|
+
* empty list refuses nothing and is accepted.
|
|
1267
1289
|
* - `maxIterations` (number) -- downward-only override of the PBKDF2 iteration cap.
|
|
1268
1290
|
*
|
|
1269
1291
|
* @example
|
package/lib/cms-compress.js
CHANGED
|
@@ -27,12 +27,12 @@ async function compress(content, opts) {
|
|
|
27
27
|
opts = opts || _create(null);
|
|
28
28
|
var raw = guard.bytes.source(content, CmsError, "cms/bad-input", "content");
|
|
29
29
|
var level = opts.level;
|
|
30
|
-
if (level
|
|
30
|
+
if (level != null && (typeof level !== "number" || !isFinite(level) || Math.floor(level) !== level)) throw _err("cms/bad-input", "opts.level must be an integer");
|
|
31
31
|
var ctName = opts.contentType || "data";
|
|
32
32
|
var ctOid = O(ctName);
|
|
33
33
|
if (!ctOid) throw _err("cms/bad-input", "opts.contentType is not a known OID name: " + ctName);
|
|
34
34
|
var stream;
|
|
35
|
-
try { stream = zlib.deflateSync(raw, level
|
|
35
|
+
try { stream = zlib.deflateSync(raw, level != null ? { level: level } : undefined); }
|
|
36
36
|
catch (e) { throw _err("cms/bad-input", "the content could not be compressed (check opts.level)", e); }
|
|
37
37
|
var cd = b.sequence([
|
|
38
38
|
b.integer(0),
|
|
@@ -46,7 +46,7 @@ async function compress(content, opts) {
|
|
|
46
46
|
async function decompress(input, opts) {
|
|
47
47
|
opts = opts || _create(null);
|
|
48
48
|
var cap = C.LIMITS.COMPRESS_MAX_BYTES;
|
|
49
|
-
if (opts.maxOutputBytes
|
|
49
|
+
if (opts.maxOutputBytes != null) {
|
|
50
50
|
var mo = opts.maxOutputBytes;
|
|
51
51
|
if (typeof mo !== "number" || !isFinite(mo) || mo <= 0 || Math.floor(mo) !== mo) throw _err("cms/bad-input", "opts.maxOutputBytes must be a positive integer");
|
|
52
52
|
if (mo < cap) cap = mo;
|