@blamejs/pki 0.8.17 → 0.8.19
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 +51 -0
- package/README.md +1 -1
- package/lib/guard-time.js +50 -0
- package/lib/lint.js +1307 -35
- package/lib/oid.js +29 -1
- package/lib/pki-build.js +76 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/oid.js
CHANGED
|
@@ -118,11 +118,39 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
118
118
|
|
|
119
119
|
pkixOn: { base: [1, 3, 6, 1, 5, 5, 7, 8], of: { hardwareModuleName: 4, dnsSRV: 7, smtpUtf8Mailbox: 9, macAddress: 12 } },
|
|
120
120
|
|
|
121
|
+
/** @internal The S/MIME BR reserved policy identifiers of sec. 7.1.6.1 are a 4x3 matrix of
|
|
122
|
+
* certificate type by generation, and a subscriber certificate asserts exactly one. Reading it
|
|
123
|
+
* is how a linter learns which generation's table to hold the certificate to, so all twelve are
|
|
124
|
+
* named rather than only the current ones. */
|
|
121
125
|
cabfPolicy: { base: [2, 23, 140, 1], of: {
|
|
122
|
-
"ev-guidelines": 1, "domain-validated": [2, 1], "organization-validated": [2, 2], "individual-validated": [2, 3]
|
|
126
|
+
"ev-guidelines": 1, "domain-validated": [2, 1], "organization-validated": [2, 2], "individual-validated": [2, 3],
|
|
127
|
+
"smime-mailbox-legacy": [5, 1, 1], "smime-mailbox-multipurpose": [5, 1, 2], "smime-mailbox-strict": [5, 1, 3],
|
|
128
|
+
"smime-organization-legacy": [5, 2, 1], "smime-organization-multipurpose": [5, 2, 2], "smime-organization-strict": [5, 2, 3],
|
|
129
|
+
"smime-sponsor-legacy": [5, 3, 1], "smime-sponsor-multipurpose": [5, 3, 2], "smime-sponsor-strict": [5, 3, 3],
|
|
130
|
+
"smime-individual-legacy": [5, 4, 1], "smime-individual-multipurpose": [5, 4, 2], "smime-individual-strict": [5, 4, 3],
|
|
131
|
+
"code-signing-ev": 3, "code-signing": [4, 1], "code-signing-timestamping": [4, 2] } },
|
|
123
132
|
|
|
124
133
|
idCp: { base: [1, 3, 6, 1, 5, 5, 7, 14], of: { "id-cp-ipAddr-asNumber": 2, "id-cp-ipAddr-asNumber-v2": 3 } },
|
|
125
134
|
|
|
135
|
+
/** @internal The Legal Entity Identifier extensions of S/MIME BR sec. 7.1.2.3 (l), which the
|
|
136
|
+
* certificate type decides the admissibility of. */
|
|
137
|
+
lei: { base: [1, 3, 6, 1, 4, 1, 52266], of: { "lei-identifier": 1, "lei-role": 2 } },
|
|
138
|
+
|
|
139
|
+
/** @internal The Adobe extensions of S/MIME BR sec. 7.1.2.3 (m), prohibited under the Strict
|
|
140
|
+
* generation. */
|
|
141
|
+
adobe: { base: [1, 2, 840, 113583, 1, 1, 9], of: { "adobe-timestamp": 1, "adobe-archive-rev-info": 2 } },
|
|
142
|
+
|
|
143
|
+
/** @internal The extended key purposes Code Signing BR sec. 7.1.2.3 (f) names as MAY beside
|
|
144
|
+
* id-kp-codeSigning, so a row reporting any other purpose can exclude them by name. Document
|
|
145
|
+
* Signing is registered twice: Microsoft assigns it 1.3.6.1.4.1.311.10.3.12, the sibling of the
|
|
146
|
+
* Lifetime Signing arc printed two lines above it in that clause, and the clause itself prints
|
|
147
|
+
* 1.3.6.1.4.1.311.3.10.3.12. Both resolve, so a certificate carrying either is read as the
|
|
148
|
+
* purpose the clause permits. */
|
|
149
|
+
msEku: { base: [1, 3, 6, 1, 4, 1, 311], of: {
|
|
150
|
+
"ms-lifetime-signing": [10, 3, 13],
|
|
151
|
+
"ms-document-signing": [10, 3, 12],
|
|
152
|
+
"ms-document-signing-as-printed": [3, 10, 3, 12] } },
|
|
153
|
+
|
|
126
154
|
gsmaRspRole: { base: [2, 23, 146, 1, 2, 1], of: {
|
|
127
155
|
"id-rspRole-ci": 0, "id-rspRole-euicc-v2": 1, "id-rspRole-euicc": [0, 0, 0, 0, 0],
|
|
128
156
|
"id-rspRole-eum-v2": 2, "id-rspRole-eum": [0, 0, 0],
|
package/lib/pki-build.js
CHANGED
|
@@ -42,6 +42,9 @@ function _isSubDelim(c) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function _isUserinfoByte(c) { return _isUnreserved(c) || _isSubDelim(c) || c === 58; }
|
|
45
|
+
/** @internal The bytes RFC 3986 sec. 3.2.2 admits in a reg-name, beside a percent-encoding. A DNS
|
|
46
|
+
* label admits fewer, which is why a reader does not hold a host it was given to that rule. */
|
|
47
|
+
function _isRegNameByte(c) { return _isUnreserved(c) || _isSubDelim(c); }
|
|
45
48
|
function _isUriByte(c) {
|
|
46
49
|
return _isUnreserved(c) || _isSubDelim(c) || c === 58 || c === 47 || c === 63 || c === 35 || c === 64;
|
|
47
50
|
}
|
|
@@ -99,7 +102,61 @@ function _validUriIp6Host(s, from, to) {
|
|
|
99
102
|
return packed !== null && intrinsic.sizeOf(packed) === 16;
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
/** @internal A port as RFC 3986 sec. 3.2.3 writes it: ":" *DIGIT, or nothing. An empty port after
|
|
106
|
+
* the colon is what the grammar allows, so it is not a reason to refuse. */
|
|
107
|
+
function _validUriPort(s, at, to) {
|
|
108
|
+
if (at === to) return true;
|
|
109
|
+
if (_charCodeAt(s, at) !== 58) return false;
|
|
110
|
+
for (var i = at + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/** @internal An IPvFuture literal, RFC 3986 sec. 3.2.2: "v" 1*HEXDIG "." 1*( unreserved /
|
|
114
|
+
* sub-delims / ":" ). */
|
|
115
|
+
function _validIpvFuture(s, from, to) {
|
|
116
|
+
if (to - from < 4) return false;
|
|
117
|
+
var c = _charCodeAt(s, from);
|
|
118
|
+
if (c !== 118 && c !== 86) return false;
|
|
119
|
+
var i = from + 1, hex = 0;
|
|
120
|
+
while (i < to && _isHexCode(_charCodeAt(s, i))) { i += 1; hex += 1; }
|
|
121
|
+
if (hex === 0 || i >= to || _charCodeAt(s, i) !== 46) return false;
|
|
122
|
+
i += 1;
|
|
123
|
+
if (i >= to) return false;
|
|
124
|
+
for (; i < to; i += 1) {
|
|
125
|
+
c = _charCodeAt(s, i);
|
|
126
|
+
if (!_isUnreserved(c) && !_isSubDelim(c) && c !== 58) return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
/** @internal The authority of a URL a reader was GIVEN, as RFC 3986 sec. 3.2 writes it:
|
|
131
|
+
* [ userinfo "@" ] host [ ":" port ], where host is a non-empty reg-name, an IPv6 literal in
|
|
132
|
+
* brackets, or an IPvFuture literal. RFC 7230 sec. 2.7.1 forbids an empty host for http, so the
|
|
133
|
+
* host is required here. The structure is checked; the reg-name's own bytes are not held to DNS
|
|
134
|
+
* label rules, which is what a URL this toolkit EMITS must satisfy and is not what RFC 3986
|
|
135
|
+
* conformance means. Applying the emit rule to a value someone else wrote rejects legal URLs, and
|
|
136
|
+
* dropping the structure with it accepts "http://:80". */
|
|
137
|
+
function _validReadAuthority(s, from, to) {
|
|
138
|
+
var i, at = -1;
|
|
139
|
+
for (i = from; i < to; i += 1) { if (_charCodeAt(s, i) === 64) at = i; }
|
|
140
|
+
if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false;
|
|
141
|
+
var hostStart = at === -1 ? from : at + 1;
|
|
142
|
+
if (hostStart >= to) return false;
|
|
143
|
+
if (_charCodeAt(s, hostStart) === 91) {
|
|
144
|
+
var close = -1;
|
|
145
|
+
for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } }
|
|
146
|
+
if (close === -1) return false;
|
|
147
|
+
if (!_validUriIp6Host(s, hostStart + 1, close) && !_validIpvFuture(s, hostStart + 1, close)) return false;
|
|
148
|
+
return _validUriPort(s, close + 1, to);
|
|
149
|
+
}
|
|
150
|
+
var colon = -1;
|
|
151
|
+
for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } }
|
|
152
|
+
var hostEnd = colon === -1 ? to : colon;
|
|
153
|
+
if (hostEnd === hostStart) return false;
|
|
154
|
+
if (!_componentBytesOk(s, hostStart, hostEnd, _isRegNameByte)) return false;
|
|
155
|
+
return colon === -1 ? true : _validUriPort(s, colon, to);
|
|
156
|
+
}
|
|
157
|
+
|
|
102
158
|
function _validUriAuthority(s, from, to, shape) {
|
|
159
|
+
if (shape.anyAuthority) return _validReadAuthority(s, from, to);
|
|
103
160
|
var i, c, at = -1;
|
|
104
161
|
for (i = from; i < to; i += 1) {
|
|
105
162
|
c = _charCodeAt(s, i);
|
|
@@ -136,6 +193,13 @@ var _URI_SHAPE = intrinsic.assign(intrinsic.create(null), { singleLabel: false,
|
|
|
136
193
|
/** @internal An HTTP URL (RFC 2616 sec. 3.2.2: "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]):
|
|
137
194
|
* no userinfo, a host of one label or many, a query only after an absolute path, no fragment. */
|
|
138
195
|
var _HTTP_URL_SHAPE = intrinsic.assign(intrinsic.create(null), { singleLabel: true, userinfo: false, emptyAuthority: false, emptyHost: false, queryNeedsPath: true, fragment: false });
|
|
196
|
+
/** @internal An http or https URL as RFC 7230 sec. 2.7.1 and 2.7.2 write it: "http:" "//"
|
|
197
|
+
* authority path-abempty [ "?" query ], where path-abempty MAY be empty, so a query sits directly
|
|
198
|
+
* after the authority, and where a fragment is allowed on the URI reference (RFC 3986 sec. 3.5).
|
|
199
|
+
* This is the shape a reader holds a URL it was GIVEN to. The shape above is what the builders
|
|
200
|
+
* hold the URLs they EMIT to, which is deliberately narrower, and applying it to a value someone
|
|
201
|
+
* else wrote reports a conforming URL. */
|
|
202
|
+
var _HTTP_URL_READ_SHAPE = intrinsic.assign(intrinsic.create(null), { singleLabel: true, userinfo: true, emptyAuthority: false, emptyHost: false, queryNeedsPath: false, fragment: true, anyAuthority: true });
|
|
139
203
|
/** @internal An LDAP URL (RFC 4516 sec. 2: scheme "://" [host [":" port]] ["/" dn ...]): no
|
|
140
204
|
* userinfo, a host of any label count that may be left out, with or without a port, and no
|
|
141
205
|
* fragment. The dn, attributes, scope and filter after the "/" are not parsed. */
|
|
@@ -143,6 +207,17 @@ var _LDAP_URL_SHAPE = intrinsic.assign(intrinsic.create(null), { singleLabel: tr
|
|
|
143
207
|
|
|
144
208
|
function _looksLikeUri(s) { return _uriShape(s, _URI_SHAPE); }
|
|
145
209
|
function isHttpUrl(s) { return typeof s === "string" && guard.name.uriSchemeIs(s, "http") && _uriShape(s, _HTTP_URL_SHAPE); }
|
|
210
|
+
/** @internal The scheme of a well-formed http or https URL as a READER judges one, or null where
|
|
211
|
+
* the string is not one of either. Carrying the scheme token is not the same as being a URI of
|
|
212
|
+
* that scheme: RFC 7230 sec. 2.7.1 writes http-URI as "http:" "//" authority path-abempty and
|
|
213
|
+
* forbids an empty host, so "http:example.com/a" and "http://:80" are neither. A caller that
|
|
214
|
+
* admits one scheme and not the other compares the answer rather than testing a prefix. */
|
|
215
|
+
function httpUrlScheme(s) {
|
|
216
|
+
if (typeof s !== "string" || !_uriShape(s, _HTTP_URL_READ_SHAPE)) return null;
|
|
217
|
+
if (guard.name.uriSchemeIs(s, "http")) return "http";
|
|
218
|
+
if (guard.name.uriSchemeIs(s, "https")) return "https";
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
146
221
|
function isLdapUrl(s) { return typeof s === "string" && guard.name.uriSchemeIs(s, "ldap") && _uriShape(s, _LDAP_URL_SHAPE); }
|
|
147
222
|
|
|
148
223
|
function _uriShape(s, shape) {
|
|
@@ -1378,4 +1453,4 @@ function samePublicKey(spkiA, spkiB) {
|
|
|
1378
1453
|
return _keyEquals(ka, kb) === true;
|
|
1379
1454
|
}
|
|
1380
1455
|
|
|
1381
|
-
module.exports = intrinsic.freeze({ makeBuilder: makeBuilder, reqDenseArray: reqDenseArrayImpl, KU_BIT: KU_BIT, tbsNameField: tbsNameField, tbsSerialNumber: tbsSerialNumber, randomSerial: randomSerial, skiValueOf: skiValueOf, isHttpUrl: isHttpUrl, isLdapUrl: isLdapUrl });
|
|
1456
|
+
module.exports = intrinsic.freeze({ makeBuilder: makeBuilder, reqDenseArray: reqDenseArrayImpl, KU_BIT: KU_BIT, tbsNameField: tbsNameField, tbsSerialNumber: tbsSerialNumber, randomSerial: randomSerial, skiValueOf: skiValueOf, isHttpUrl: isHttpUrl, httpUrlScheme: httpUrlScheme, isLdapUrl: isLdapUrl });
|
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:226c63e5-3f86-43a6-a5bd-ad58867e4706",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-24T10:24:04.955Z",
|
|
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.19",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.19",
|
|
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.19",
|
|
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.19",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|