@blamejs/pki 0.8.49 → 0.8.51
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 +27 -0
- package/README.md +3 -3
- package/lib/acme.js +53 -27
- package/lib/cmp-build.js +19 -8
- package/lib/constants.js +3 -2
- package/lib/ct.js +190 -43
- package/lib/est.js +42 -19
- package/lib/guard-bytes.js +17 -2
- package/lib/guard-identifier.js +22 -0
- package/lib/guard-intrinsic.js +55 -0
- package/lib/http-digest.js +8 -1
- package/lib/http-transport.js +48 -20
- package/lib/inspect.js +3 -2
- package/lib/lint.js +105 -2
- package/lib/ocsp.js +3 -1
- package/lib/path-validate.js +93 -9
- package/lib/pki-build.js +40 -0
- package/lib/revocation-fetch.js +14 -5
- package/lib/scep.js +36 -15
- package/lib/x509-sign.js +22 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/revocation-fetch.js
CHANGED
|
@@ -31,6 +31,14 @@ var OID_AD_OCSP = oid.byName("ocsp");
|
|
|
31
31
|
var OID_OCSP_NONCE = oid.byName("ocspNonce");
|
|
32
32
|
|
|
33
33
|
var _append = guard.list.append;
|
|
34
|
+
/** @internal The URL parser, taken at load: the host it reports is the distribution point a fetch is
|
|
35
|
+
* directed to. Its accessors are taken with it, since the constructor's capture does not cover the
|
|
36
|
+
* prototype the value reads its parts off and the host read here is the one the filter applies to. */
|
|
37
|
+
var _URL = intrinsic.URL;
|
|
38
|
+
var _urlHref = intrinsic.urlHref;
|
|
39
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
40
|
+
var _urlHostname = intrinsic.urlHostname;
|
|
41
|
+
var _urlSetHash = intrinsic.urlSetHash;
|
|
34
42
|
var LIMITS = constants.LIMITS;
|
|
35
43
|
|
|
36
44
|
var CHECKER_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
@@ -162,18 +170,19 @@ function _ocspCandidates(descriptions, net_, skips) {
|
|
|
162
170
|
function _acceptableUrl(value, net_) {
|
|
163
171
|
if (typeof value !== "string" || value === "") return _no("is not one this client fetches");
|
|
164
172
|
var u;
|
|
165
|
-
try { u = new
|
|
173
|
+
try { u = new _URL(value); }
|
|
166
174
|
catch (_e) { return _no("is not one this client fetches"); } // allow:swallow-unverified a value that is not a URL names no location
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
var scheme = _urlProtocol(u);
|
|
176
|
+
if (scheme !== "https:" && !(net_.allowHttp && scheme === "http:")) return _no("is not one this client fetches");
|
|
177
|
+
var host = _urlHostname(u);
|
|
169
178
|
if (host.charAt(0) === "[" && host.charAt(host.length - 1) === "]") host = host.slice(1, -1);
|
|
170
179
|
if (net.isIP(host) !== 0) {
|
|
171
180
|
if (httpTransport.isBlockedIp(host)) return _no("is not one this client fetches");
|
|
172
181
|
} else if (!net_.guardsAddresses) {
|
|
173
182
|
return _no("names a host whose resolved address opts.transport does not declare it filters");
|
|
174
183
|
}
|
|
175
|
-
u
|
|
176
|
-
return { url: u
|
|
184
|
+
_urlSetHash(u, "");
|
|
185
|
+
return { url: _urlHref(u), why: null };
|
|
177
186
|
}
|
|
178
187
|
|
|
179
188
|
function _no(why) { return { url: null, why: why }; }
|
package/lib/scep.js
CHANGED
|
@@ -37,6 +37,21 @@
|
|
|
37
37
|
|
|
38
38
|
var frameworkError = require("./framework-error");
|
|
39
39
|
var ScepError = frameworkError.ScepError;
|
|
40
|
+
var intrinsic = require("./guard-intrinsic");
|
|
41
|
+
/** @internal The URL parser, taken at load: what it returns names the host an operation is sent to,
|
|
42
|
+
* and a redirect is resolved against the current operation URL with it. The conversion the parser is
|
|
43
|
+
* handed is taken with it, since a replacement answers with a host of its own. */
|
|
44
|
+
var _URL = intrinsic.URL;
|
|
45
|
+
var _stringOf = intrinsic.String;
|
|
46
|
+
/** @internal And the accessors its result is read through: capturing the constructor does not capture
|
|
47
|
+
* the prototype the value reads its parts off, and the scheme here decides whether a redirect may
|
|
48
|
+
* be followed while the origin decides whether it is a cross-origin one. */
|
|
49
|
+
var _urlHref = intrinsic.urlHref;
|
|
50
|
+
var _urlOrigin = intrinsic.urlOrigin;
|
|
51
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
52
|
+
var _urlSearchParams = intrinsic.urlSearchParams;
|
|
53
|
+
var _urlHash = intrinsic.urlHash;
|
|
54
|
+
var _searchSet = intrinsic.uncurry(URLSearchParams.prototype.set);
|
|
40
55
|
var oid = require("./oid");
|
|
41
56
|
var asn1 = require("./asn1-der");
|
|
42
57
|
var b = asn1.build;
|
|
@@ -661,28 +676,34 @@ function _responseLen(body) {
|
|
|
661
676
|
if (Buffer.isBuffer(body)) return guard.bytes.lengthOf(body);
|
|
662
677
|
return guard.bytes.lengthOf(guard.bytes.source(body, ScepError, "scep/bad-response", "the response body"));
|
|
663
678
|
}
|
|
679
|
+
/** @internal The settings this client forwards to its transport. */
|
|
680
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
|
|
664
681
|
function _tlsForRequest(opts) {
|
|
665
|
-
|
|
666
|
-
return { anchors: t.anchors, useSystemStore: t.useSystemStore, cert: t.cert, key: t.key, minVersion: t.minVersion, servername: t.servername, checkServerIdentity: t.checkServerIdentity };
|
|
682
|
+
return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
|
|
667
683
|
}
|
|
668
684
|
function _redirectTarget(current, location, method, allowCrossOrigin) {
|
|
669
685
|
if (location == null || _trim(String(location)) === "") throw _err("scep/bad-response", "a redirect response carried no Location header");
|
|
670
686
|
var resolved;
|
|
671
|
-
try { resolved = new
|
|
687
|
+
try { resolved = new _URL(_stringOf(location), _urlHref(current)); }
|
|
672
688
|
catch (e) { throw _err("scep/bad-url", "a redirect Location did not parse: " + location, e); }
|
|
673
|
-
|
|
674
|
-
|
|
689
|
+
/** @internal Each scheme read ONCE: the first test decides whether the target is HTTP at all and
|
|
690
|
+
* the second whether it is a downgrade, and those two have to be answered about one value. */
|
|
691
|
+
var toScheme = _urlProtocol(resolved);
|
|
692
|
+
var fromScheme = _urlProtocol(current);
|
|
693
|
+
if (toScheme !== "http:" && toScheme !== "https:") throw _err("scep/bad-url", "a redirect to a non-HTTP scheme is refused: " + toScheme);
|
|
694
|
+
if (fromScheme === "https:" && toScheme !== "https:") throw _err("scep/insecure-redirect", "a redirect from https to " + toScheme + " is refused");
|
|
675
695
|
var safe = method === "GET" || method === "HEAD";
|
|
676
|
-
if (resolved
|
|
696
|
+
if (_urlOrigin(resolved) !== _urlOrigin(current) && !safe && !allowCrossOrigin) throw _err("scep/cross-origin-redirect", "a cross-origin redirect on a " + method + " needs opts.allowCrossOriginRedirect");
|
|
677
697
|
return resolved;
|
|
678
698
|
}
|
|
679
699
|
function _scepUrl(baseUrl, operation) {
|
|
680
700
|
var url;
|
|
681
|
-
try { url = new
|
|
701
|
+
try { url = new _URL(_stringOf(baseUrl)); }
|
|
682
702
|
catch (e) { throw _err("scep/bad-url", "the SCEP base URL did not parse: " + _showValue(baseUrl), e); }
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
url
|
|
703
|
+
var baseScheme = _urlProtocol(url);
|
|
704
|
+
if (baseScheme !== "http:" && baseScheme !== "https:") throw _err("scep/bad-url", "the SCEP base URL must be http or https, got " + baseScheme);
|
|
705
|
+
if (_urlHash(url)) throw _err("scep/bad-url", "the SCEP base URL must not carry a fragment");
|
|
706
|
+
_searchSet(_urlSearchParams(url), "operation", operation);
|
|
686
707
|
return url;
|
|
687
708
|
}
|
|
688
709
|
function _pkiMethod(opts) {
|
|
@@ -693,27 +714,27 @@ function _pkiMethod(opts) {
|
|
|
693
714
|
}
|
|
694
715
|
function _pkiRequest(method, opUrl, reqMsg, clientOpts) {
|
|
695
716
|
if (method === "GET") {
|
|
696
|
-
var getUrl = new
|
|
697
|
-
getUrl
|
|
717
|
+
var getUrl = new _URL(_urlHref(opUrl));
|
|
718
|
+
_searchSet(_urlSearchParams(getUrl), "message", Buffer.from(reqMsg).toString("base64"));
|
|
698
719
|
return _client("GET", getUrl, null, {}, clientOpts);
|
|
699
720
|
}
|
|
700
721
|
return _client("POST", opUrl, reqMsg, { "content-type": CT_PKI_MESSAGE }, clientOpts);
|
|
701
722
|
}
|
|
702
723
|
function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
703
|
-
var redirects = 0, curMethod = method, curBody = body, initialOrigin = url
|
|
724
|
+
var redirects = 0, curMethod = method, curBody = body, initialOrigin = _urlOrigin(url);
|
|
704
725
|
var proxySnap = _snapshotProxy(opts.proxy);
|
|
705
726
|
var tlsBase = _tlsForRequest(opts);
|
|
706
727
|
var allowCrossOrigin = !!opts.allowCrossOriginRedirect;
|
|
707
728
|
function _hopTls(current) {
|
|
708
729
|
var t = tlsBase;
|
|
709
|
-
if (current
|
|
730
|
+
if (_urlOrigin(current) !== initialOrigin) {
|
|
710
731
|
t = Object.assign({}, t);
|
|
711
732
|
t.cert = null; t.key = null; t.servername = null;
|
|
712
733
|
}
|
|
713
734
|
return t;
|
|
714
735
|
}
|
|
715
736
|
function step(current) {
|
|
716
|
-
var req = { method: curMethod, url: current
|
|
737
|
+
var req = { method: curMethod, url: _urlHref(current), headers: headers, tls: _hopTls(current), timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes };
|
|
717
738
|
if (proxySnap != null) req.proxy = proxySnap;
|
|
718
739
|
if (curBody != null) req.body = curBody;
|
|
719
740
|
return guard.async.invoked(transport, [req], _err, "scep/bad-input", "opts.transport").then(function (res) {
|
package/lib/x509-sign.js
CHANGED
|
@@ -71,6 +71,7 @@ var KNOWN_EXT_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
|
71
71
|
authorityInfoAccess: 1, cRLDistributionPoints: 1, freshestCRL: 1,
|
|
72
72
|
policyConstraints: 1, inhibitAnyPolicy: 1, policyMappings: 1, policyMappingsCritical: 1,
|
|
73
73
|
issuerAltName: 1, qcStatements: 1, qcStatementsCritical: 1,
|
|
74
|
+
tlsFeature: 1, tlsFeatureCritical: 1,
|
|
74
75
|
precertificatePoison: 1, signedCertificateTimestampList: 1,
|
|
75
76
|
msCertificateTemplate: 1, msEnrollCertType: 1, msCaVersion: 1, msPreviousCertHash: 1,
|
|
76
77
|
msApplicationPolicies: 1,
|
|
@@ -279,6 +280,9 @@ function _buildExtensions(extSpec, ctx) {
|
|
|
279
280
|
* unrecognized critical extension; the default is the form that validates and the knob reaches
|
|
280
281
|
* the other, as policyMappings and certificatePolicies do. */
|
|
281
282
|
if (extSpec.qcStatements != null) guard.list.append(out, _ext(O("qcStatements"), !!extSpec.qcStatementsCritical, _extQcStatements(extSpec.qcStatements)));
|
|
283
|
+
/** @internal RFC 7633 sec. 4: SHOULD NOT be critical, and the clause names the case where critical
|
|
284
|
+
* is the desired behavior, so the knob exists and the default is the compatible form. */
|
|
285
|
+
if (extSpec.tlsFeature != null) guard.list.append(out, _ext(O("tlsFeature"), !!extSpec.tlsFeatureCritical, _b.extTlsFeature(extSpec.tlsFeature)));
|
|
282
286
|
if (extSpec.nameConstraints != null) {
|
|
283
287
|
if (!caTrue) throw _err("x509/bad-input", "nameConstraints appears only in a CA certificate, so it requires basicConstraints cA=TRUE (RFC 5280 sec. 4.2.1.10)");
|
|
284
288
|
guard.list.append(out, _ext(O("nameConstraints"), true, _extNameConstraints(extSpec.nameConstraints)));
|
|
@@ -628,6 +632,18 @@ function _hasCriticalSan(extSpec) {
|
|
|
628
632
|
* know an unknown syntax. `pki.path.validate` does not process this extension, so it is emitted
|
|
629
633
|
* non-critical unless `qcStatementsCritical` is set.
|
|
630
634
|
*
|
|
635
|
+
* `extensions.tlsFeature` builds the RFC 7633 TLS Feature extension, the "must-staple" declaration,
|
|
636
|
+
* from a list of TLS ExtensionType values. Each is a number, a bigint, or one of the registry names
|
|
637
|
+
* this build knows, `status_request` and `status_request_v2`. The values come from the IANA TLS
|
|
638
|
+
* Extensions registry that RFC 7633 sec. 4.1 points at, and the type states no range, so a value
|
|
639
|
+
* outside that registry's width encodes and `pki.lint.certificate(der, { profile: "rfc7633" })`
|
|
640
|
+
* reports it rather than the encoder refusing it; the same holds for a repeated value, which the
|
|
641
|
+
* syntax permits. An EMPTY list is refused, because the extension it would emit advertises nothing.
|
|
642
|
+
* Emitted non-critical unless `tlsFeatureCritical` is set: sec. 4 says it SHOULD NOT be critical and
|
|
643
|
+
* names the case where critical is the caller's intent. `pki.path.validate` enforces sec. 4.2.2 over
|
|
644
|
+
* a built path, so a certificate issued under a CA that advertises features must offer the same set
|
|
645
|
+
* or a superset.
|
|
646
|
+
*
|
|
631
647
|
* `extensions.subjectInfoAccess` takes the same `{ accessMethod, accessLocation }` list
|
|
632
648
|
* `authorityInfoAccess` takes, since RFC 5280 sec. 4.2.2.2 gives it sec. 4.2.2.1's syntax; the two
|
|
633
649
|
* access methods that section defines are the registered names `id-ad-caRepository` and
|
|
@@ -910,6 +926,10 @@ var EXTENSION_ROWS = [
|
|
|
910
926
|
["cRLDistributionPoints", "cRLDistributionPoints", { fixed: false, cite: "RFC 5280 sec. 4.2.1.13" }, function (v) { return _extCrlDp(v, "cRLDistributionPoints"); }],
|
|
911
927
|
["freshestCRL", "freshestCRL", { fixed: false, cite: "RFC 5280 sec. 4.2.1.15" }, function (v) { return _extCrlDp(v, "freshestCRL"); }],
|
|
912
928
|
["qcStatements", "qcStatements", { knob: true, dflt: false }, function (v) { return _extQcStatements(v); }],
|
|
929
|
+
/** @internal RFC 7633 sec. 4 reads "SHOULD NOT be marked critical" and then names the case where
|
|
930
|
+
* the critical form is what the caller wants, so this takes a knob defaulting to the compatible
|
|
931
|
+
* form rather than refusing one: `fixed: false` would promote that SHOULD NOT into a refusal. */
|
|
932
|
+
["tlsFeature", "tlsFeature", { knob: true, dflt: false }, function (v) { return _b.extTlsFeature(v); }],
|
|
913
933
|
["subjectDirectoryAttributes", "subjectDirectoryAttributes", { fixed: false, cite: "RFC 5280 sec. 4.2.1.8" }, function (v) { return _b.extSubjectDirectoryAttributes(v); }],
|
|
914
934
|
["msCertificateTemplate", "msCertificateTemplate", { fixed: false, cite: "the toolkit issues the form its readers accept" }, function (v) { return _b.extMsCertificateTemplate(v); }],
|
|
915
935
|
["msEnrollCertType", "msEnrollCertType", { fixed: false, cite: "the toolkit issues the form its readers accept" }, function (v) { return _b.extMsEnrollCertType(v); }],
|
|
@@ -971,8 +991,8 @@ function _extensionCritical(row, opts) {
|
|
|
971
991
|
* (the key identifiers, the constraint and policy-constraint extensions, the access and
|
|
972
992
|
* distribution extensions), or the toolkit issues one form only, `opts.critical` may only restate
|
|
973
993
|
* that value. `keyUsage` (default critical), `extendedKeyUsage`, `certificatePolicies`,
|
|
974
|
-
* `policyMappings`, `qcStatements` (default non-critical), and `subjectAltName`
|
|
975
|
-
* when the subject is empty, which the caller knows) take `opts.critical`; `basicConstraints`
|
|
994
|
+
* `policyMappings`, `qcStatements` and `tlsFeature` (default non-critical), and `subjectAltName`
|
|
995
|
+
* (critical exactly when the subject is empty, which the caller knows) take `opts.critical`; `basicConstraints`
|
|
976
996
|
* carries `critical` inside its value. An unregistered OID takes `opts.critical` as stated.
|
|
977
997
|
*
|
|
978
998
|
* @opts
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:3a1ddfe7-c71b-4cb4-8bd0-256c3b6075ba",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-05T13:17:14.754Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.51",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.51",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.51",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.51",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|