@blamejs/pki 0.8.25 → 0.8.27
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 +54 -0
- package/README.md +2 -2
- package/lib/attrcert-sign.js +26 -5
- package/lib/cmp-verify.js +16 -2
- package/lib/cms-decrypt.js +8 -2
- package/lib/cms-sign.js +41 -4
- package/lib/composite-sig.js +5 -0
- package/lib/constants.js +1 -0
- package/lib/guard-name.js +147 -7
- package/lib/http-retry-after.js +28 -7
- package/lib/inspect.js +8 -7
- package/lib/lint.js +1526 -35
- package/lib/oid.js +10 -0
- package/lib/path-validate.js +8 -1
- package/lib/rfc3339.js +12 -5
- package/lib/schema-attrcert.js +39 -9
- package/lib/schema-cms.js +23 -0
- package/lib/schema-tsp.js +82 -0
- package/package.json +2 -2
- package/sbom.cdx.json +6 -6
package/lib/guard-name.js
CHANGED
|
@@ -290,8 +290,7 @@ function escapeDnValue(v) {
|
|
|
290
290
|
return out;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
// @enforced-by guard
|
|
294
|
-
// @guard-shape lastIndexOf\("@"\)
|
|
293
|
+
// @enforced-by behavioral -- a shape naming the separator cannot fire, since the walk strips string literals before applying one, and the split has no other form that separates it from the authority scans in this tree; the vectors are the guard, where a local part differing in case alone is a different mailbox and a host part that does is the same one
|
|
295
294
|
function emailEqual(a, b) {
|
|
296
295
|
if (typeof a !== "string" || typeof b !== "string") return "not-comparable";
|
|
297
296
|
var ai = _lastIndexOf(a, "@"), bi = _lastIndexOf(b, "@");
|
|
@@ -447,7 +446,9 @@ var _SCHEME_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV
|
|
|
447
446
|
* that needs a host decides what to do with that rather than being handed a guess. `host` is null
|
|
448
447
|
* when the authority carries none, and `bracketed` says the host arrived inside `[]`, which is the
|
|
449
448
|
* only place an IPv6 literal may appear. Percent-escapes are left as written, so a caller comparing
|
|
450
|
-
* the host decides whether to accept one.
|
|
449
|
+
* the host decides whether to accept one. `authStart` and `authEnd` bound the authority as written,
|
|
450
|
+
* userinfo and port included, and are equal on a URI that carries none, so a caller reading the rest
|
|
451
|
+
* of the URI reads it from the same walk rather than scanning again. */
|
|
451
452
|
// @enforced-by behavioral -- a URI walk has no rename-proof shape that separates it from any other character scan; the vectors reading a scheme, refusing two userinfo separators and stripping a port are the guard
|
|
452
453
|
function uriParts(uri) {
|
|
453
454
|
if (typeof uri !== "string") return null;
|
|
@@ -460,7 +461,8 @@ function uriParts(uri) {
|
|
|
460
461
|
if (_charCodeAt(uri, p) !== 0x3a) return null;
|
|
461
462
|
var scheme = _strSlice(uri, 0, p);
|
|
462
463
|
if (_charCodeAt(uri, p + 1) !== 0x2f || _charCodeAt(uri, p + 2) !== 0x2f) {
|
|
463
|
-
return { scheme: scheme, hasAuthority: false, host: null, bracketed: false
|
|
464
|
+
return { scheme: scheme, hasAuthority: false, host: null, bracketed: false,
|
|
465
|
+
authStart: p + 1, authEnd: p + 1 };
|
|
464
466
|
}
|
|
465
467
|
p += 3;
|
|
466
468
|
var authStart = p;
|
|
@@ -474,12 +476,17 @@ function uriParts(uri) {
|
|
|
474
476
|
if (firstAt !== _lastIndexOf(authority, "@")) return null;
|
|
475
477
|
if (firstAt !== -1) authority = _strSlice(authority, firstAt + 1);
|
|
476
478
|
var host = _strSlice(authority, 0, _portStripIndex(authority));
|
|
477
|
-
if (host === "")
|
|
479
|
+
if (host === "") {
|
|
480
|
+
return { scheme: scheme, hasAuthority: true, host: null, bracketed: false,
|
|
481
|
+
authStart: authStart, authEnd: p };
|
|
482
|
+
}
|
|
478
483
|
var hn = host.length;
|
|
479
484
|
if (hn >= 2 && _charCodeAt(host, 0) === 0x5b && _charCodeAt(host, hn - 1) === 0x5d) {
|
|
480
|
-
return { scheme: scheme, hasAuthority: true, host: _strSlice(host, 1, hn - 1), bracketed: true
|
|
485
|
+
return { scheme: scheme, hasAuthority: true, host: _strSlice(host, 1, hn - 1), bracketed: true,
|
|
486
|
+
authStart: authStart, authEnd: p };
|
|
481
487
|
}
|
|
482
|
-
return { scheme: scheme, hasAuthority: true, host: host, bracketed: false
|
|
488
|
+
return { scheme: scheme, hasAuthority: true, host: host, bracketed: false,
|
|
489
|
+
authStart: authStart, authEnd: p };
|
|
483
490
|
}
|
|
484
491
|
|
|
485
492
|
/** @internal Where a `:port` suffix starts, or the length when there is none. RFC 3986 sec. 3.2.3
|
|
@@ -497,6 +504,138 @@ function _portStripIndex(s) {
|
|
|
497
504
|
return (i > 0 && _charCodeAt(s, i - 1) === 0x3a) ? i - 1 : s.length;
|
|
498
505
|
}
|
|
499
506
|
|
|
507
|
+
var _UNRESERVED_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
|
|
508
|
+
var _HEXUP = "0123456789ABCDEF";
|
|
509
|
+
|
|
510
|
+
/** @internal Percent-encoding normalization (RFC 3986 sec. 6.2.2.2, which RFC 3987 sec. 5.3.2.3
|
|
511
|
+
* carries): a triplet encoding an unreserved character is decoded to that character, and the hex
|
|
512
|
+
* digits of every triplet that remains are written in uppercase. A `%` that does not introduce two
|
|
513
|
+
* hex digits is left as written, since a value this cannot read is one the comparison must not
|
|
514
|
+
* rewrite. */
|
|
515
|
+
function _pctNormalize(s) {
|
|
516
|
+
var out = "", i = 0, n = s.length;
|
|
517
|
+
while (i < n) {
|
|
518
|
+
var c = _charCodeAt(s, i);
|
|
519
|
+
if (c !== 0x25 || i + 2 >= n) { out += _charAt(s, i); i += 1; continue; }
|
|
520
|
+
var hi = _hexNibble(_charCodeAt(s, i + 1)), lo = _hexNibble(_charCodeAt(s, i + 2));
|
|
521
|
+
if (hi < 0 || lo < 0) { out += _charAt(s, i); i += 1; continue; }
|
|
522
|
+
var v = hi * 16 + lo;
|
|
523
|
+
out += _UNRESERVED_TABLE[v] ? _fromCharCode(v)
|
|
524
|
+
: "%" + _charAt(_HEXUP, hi) + _charAt(_HEXUP, lo);
|
|
525
|
+
i += 3;
|
|
526
|
+
}
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** @internal Path segment normalization (RFC 3986 sec. 5.2.4's remove_dot_segments, which RFC 3987
|
|
531
|
+
* sec. 5.3.2.4 carries). The document writes the algorithm as repeated removal from the front of the
|
|
532
|
+
* input and appending to an output buffer; both are done here with an index and a list of the segments
|
|
533
|
+
* kept, because rewriting the remaining input on each step costs a copy of it per segment, and a path
|
|
534
|
+
* of many `a/../` segments then costs time in the square of its length. A certificate can carry a URI
|
|
535
|
+
* of any length the encoding allows, and this runs on one while an operator waits, so the walk reads
|
|
536
|
+
* each character once.
|
|
537
|
+
* A segment is read with the `/` that introduces it, so the kept list holds whole `/segment` units and
|
|
538
|
+
* removing "the last segment" is removing the last of them. A `.` or `..` that opens a relative path
|
|
539
|
+
* takes its following `/` with it, which is what sec. 5.2.4 step 2A does. */
|
|
540
|
+
function _removeDotSegments(path) {
|
|
541
|
+
var kept = [], i = 0, n = path.length;
|
|
542
|
+
while (i < n) {
|
|
543
|
+
var lead = _charCodeAt(path, i) === 0x2f;
|
|
544
|
+
if (lead) i += 1;
|
|
545
|
+
var segStart = i;
|
|
546
|
+
while (i < n && _charCodeAt(path, i) !== 0x2f) i += 1;
|
|
547
|
+
var seg = _strSlice(path, segStart, i);
|
|
548
|
+
if (seg === "." || seg === "..") {
|
|
549
|
+
if (!lead) {
|
|
550
|
+
if (i < n) i += 1;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (seg === ".." && kept.length) kept.length -= 1;
|
|
554
|
+
/** @internal The last segment of the input being `.` or `..` leaves the path ending in a
|
|
555
|
+
* separator, which steps 2B and 2C write as replacing it with `/`. */
|
|
556
|
+
if (i >= n) _append(kept, "/");
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
_append(kept, lead ? "/" + seg : seg);
|
|
560
|
+
}
|
|
561
|
+
return intrinsic.join(kept, "");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** @internal The port each scheme RFC 5280 sec. 7.4 names leaves out when it is the default one
|
|
565
|
+
* (RFC 3986 sec. 6.2.3). The clause requires scheme-based normalization for exactly these four and
|
|
566
|
+
* says to omit step 5 for any other scheme, so a scheme absent from this table keeps its port. */
|
|
567
|
+
var _SCHEME_DEFAULT_PORT = intrinsic.assign(_create(null),
|
|
568
|
+
{ http: "80", https: "443", ftp: "21", ldap: "389" });
|
|
569
|
+
|
|
570
|
+
/** @internal A digit string with its leading zeros removed, and "0" for one that is all zeros, so a
|
|
571
|
+
* port written with them is read as the number RFC 3986 sec. 3.2.3 says it is. */
|
|
572
|
+
function _dropLeadingZeros(digits) {
|
|
573
|
+
var i = 0;
|
|
574
|
+
while (i < digits.length - 1 && _charCodeAt(digits, i) === 0x30) i += 1;
|
|
575
|
+
return _strSlice(digits, i);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** @internal A URI prepared for comparison as RFC 5280 sec. 7.4 requires, or null for a value that
|
|
579
|
+
* is not a URI. The clause's step 1 converts an internationalized name to ASCII Compatible Encoding;
|
|
580
|
+
* a `uniformResourceIdentifier` is an IA5String, so a value read out of a certificate is already
|
|
581
|
+
* ASCII and that step has nothing to do, and a caller handing this a non-ASCII string gets it back
|
|
582
|
+
* unconverted rather than guessed at. Step 2 lowercases the scheme and the host, step 3 normalizes
|
|
583
|
+
* percent-encoding, step 4 normalizes path segments, and step 5 drops a default port and writes an
|
|
584
|
+
* empty path as `/` for the four schemes the clause names. Everything else keeps its case, which is
|
|
585
|
+
* what makes the exact match the clause then calls for answer correctly. The steps run in the order
|
|
586
|
+
* the clause numbers them, so a host is lowercased before its percent-encoding is read and a triplet
|
|
587
|
+
* standing for an unreserved character contributes that character's own case. */
|
|
588
|
+
function _uriPrepare(uri) {
|
|
589
|
+
var p = uriParts(uri);
|
|
590
|
+
if (p === null) return null;
|
|
591
|
+
var scheme = lowerAscii(p.scheme);
|
|
592
|
+
var rest = _pctNormalize(_strSlice(uri, p.authEnd));
|
|
593
|
+
var authority = "";
|
|
594
|
+
if (p.hasAuthority) {
|
|
595
|
+
var auth = _strSlice(uri, p.authStart, p.authEnd);
|
|
596
|
+
var at = _lastIndexOf(auth, "@");
|
|
597
|
+
var userinfo = at === -1 ? "" : _pctNormalize(_strSlice(auth, 0, at + 1));
|
|
598
|
+
var hostPort = _strSlice(auth, at + 1);
|
|
599
|
+
var cut = _portStripIndex(hostPort);
|
|
600
|
+
var host = _pctNormalize(lowerAscii(_strSlice(hostPort, 0, cut)));
|
|
601
|
+
var port = _strSlice(hostPort, cut);
|
|
602
|
+
/** @internal RFC 3986 sec. 6.2.3, which step 5 runs for the four schemes the clause names: an
|
|
603
|
+
* explicit `:port` whose port is empty, or is that scheme's default, is the same URI as one
|
|
604
|
+
* written without it. sec. 3.2.3 writes `port = *DIGIT`, so the port is a number and `:080` on
|
|
605
|
+
* http is the default one; the digits are read as a number rather than compared as text, which is
|
|
606
|
+
* what the clause's own purpose, reducing false negatives, asks for. A scheme the clause does not
|
|
607
|
+
* name keeps the port as written, since step 5 is omitted for it, and no step removes a trailing
|
|
608
|
+
* dot from a host, so one is kept too. */
|
|
609
|
+
if (_hasOwn(_SCHEME_DEFAULT_PORT, scheme) &&
|
|
610
|
+
(port === ":" || _dropLeadingZeros(_strSlice(port, 1)) === _SCHEME_DEFAULT_PORT[scheme])) {
|
|
611
|
+
port = "";
|
|
612
|
+
}
|
|
613
|
+
authority = "//" + userinfo + host + port;
|
|
614
|
+
}
|
|
615
|
+
var pathEnd = rest.length;
|
|
616
|
+
for (var i = 0; i < rest.length; i++) {
|
|
617
|
+
var c = _charCodeAt(rest, i);
|
|
618
|
+
if (c === 0x3f || c === 0x23) { pathEnd = i; break; }
|
|
619
|
+
}
|
|
620
|
+
var path = _removeDotSegments(_strSlice(rest, 0, pathEnd));
|
|
621
|
+
if (path === "" && p.hasAuthority && _hasOwn(_SCHEME_DEFAULT_PORT, scheme)) path = "/";
|
|
622
|
+
return scheme + ":" + authority + path + _strSlice(rest, pathEnd);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** @internal Whether two URIs name the same resource, under the preparation RFC 5280 sec. 7.4
|
|
626
|
+
* requires before the "case-sensitive exact match" it then calls for. Folding case over the whole
|
|
627
|
+
* value instead reads two URIs differing only in a path or a userinfo as one name, and comparing the
|
|
628
|
+
* values as written reads two differing only in the case of a scheme or a host as two. Returns
|
|
629
|
+
* "not-comparable" for a value that is not a URI, which leaves the caller to say that rather than
|
|
630
|
+
* report a verdict this did not reach. */
|
|
631
|
+
// @enforced-by behavioral -- the preparation is five character scans with no shape that separates it from any other; the vectors driving each step, and the pair that differs only in a path, are the guard
|
|
632
|
+
function uriEqual(a, b) {
|
|
633
|
+
if (typeof a !== "string" || typeof b !== "string") return "not-comparable";
|
|
634
|
+
var pa = _uriPrepare(a), pb = _uriPrepare(b);
|
|
635
|
+
if (pa === null || pb === null) return "not-comparable";
|
|
636
|
+
return pa === pb ? "match" : "no-match";
|
|
637
|
+
}
|
|
638
|
+
|
|
500
639
|
var _ATEXT_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~");
|
|
501
640
|
|
|
502
641
|
/** @internal A quoted local part (RFC 5321 sec. 4.1.2 Quoted-string): printable ASCII between
|
|
@@ -773,6 +912,7 @@ module.exports = intrinsic.freeze({
|
|
|
773
912
|
lowerAscii: lowerAscii, uriSchemeIs: uriSchemeIs,
|
|
774
913
|
stripTrailingDot: stripTrailingDot, isFqdnHost: isFqdnHost, splitMailbox: splitMailbox,
|
|
775
914
|
isHostName: isHostName, hostLabels: hostLabels, isXnLabel: isXnLabel, uriParts: uriParts,
|
|
915
|
+
uriEqual: uriEqual,
|
|
776
916
|
constraintBaseRefusal: constraintBaseRefusal,
|
|
777
917
|
constraintBaseUnreadable: constraintBaseUnreadable,
|
|
778
918
|
});
|
package/lib/http-retry-after.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// @internal
|
|
5
5
|
|
|
6
6
|
var constants = require("./constants");
|
|
7
|
+
var guard = require("./guard-all");
|
|
7
8
|
|
|
8
9
|
var MAX_RETRY_AFTER_SECONDS = constants.TIME.days(365) / constants.TIME.seconds(1);
|
|
9
10
|
|
|
@@ -28,12 +29,27 @@ function _isAllDigits(s) {
|
|
|
28
29
|
return true;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
/** @internal RFC 7231 sec. 7.1.1.1: a recipient "MUST interpret a timestamp that appears to be more
|
|
33
|
+
* than 50 years in the future as representing the most recent year in the past that had the same last
|
|
34
|
+
* two digits". The reading therefore depends on when the value is received, and a caller that does not
|
|
35
|
+
* say when that was is receiving it now. A fixed cutoff instead is this rule evaluated once, for the
|
|
36
|
+
* year the cutoff was chosen in, and it misreads a widening band of two-digit years for every year
|
|
37
|
+
* that passes after it.
|
|
38
|
+
* THE CLAUSE COMPARES TIMESTAMPS, so this compares timestamps, which is why it runs once the month, day
|
|
39
|
+
* and time are read. A comparison of years alone reads a timestamp in the fiftieth year ahead as inside
|
|
40
|
+
* the window whatever its month: received on 28 September 2026, `31-Dec-76` is fifty years and three
|
|
41
|
+
* months ahead, which is more than fifty, and names 1976. */
|
|
42
|
+
function _rfc850Year(yy, mon, day, hh, mi, ss, refMs) {
|
|
43
|
+
var ref = (typeof refMs === "number" && isFinite(refMs)) ? refMs : Date.now();
|
|
44
|
+
var refDate = new Date(ref);
|
|
45
|
+
var refInstant = guard.time.instantOf(refDate);
|
|
46
|
+
if (isNaN(refInstant)) return yy + (yy < 70 ? 2000 : 1900);
|
|
47
|
+
var full = Math.floor(refDate.getUTCFullYear() / 100) * 100 + yy;
|
|
48
|
+
var horizon = new Date(refInstant);
|
|
49
|
+
horizon.setUTCFullYear(horizon.getUTCFullYear() + 50);
|
|
50
|
+
var horizonInstant = guard.time.instantOf(horizon);
|
|
51
|
+
if (isNaN(horizonInstant)) return full;
|
|
52
|
+
return Date.UTC(full, mon, day, hh, mi, ss) > horizonInstant ? full - 100 : full;
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
function _scanImf(s) {
|
|
@@ -52,7 +68,12 @@ function _scanRfc850(s, refMs) {
|
|
|
52
68
|
var mo = _monthAt(s, p); if (!mo || s.charAt(p + 3) !== "-") return null; p += 4;
|
|
53
69
|
var yy = _digits2(s, p); if (yy < 0 || s.charAt(p + 2) !== " ") return null; p += 3;
|
|
54
70
|
var t = _timeAt(s, p); if (!t || s.slice(t.next, t.next + 4) !== " GMT" || t.next + 4 !== s.length) return null;
|
|
55
|
-
|
|
71
|
+
/** @internal The century is resolved once the whole timestamp is read, since the clause compares the
|
|
72
|
+
* timestamp rather than its year. A month the calendar does not hold is refused by the round-trip
|
|
73
|
+
* check in `httpDateMs`, which reads the resolved year this returns. */
|
|
74
|
+
var mon = MONTHS.indexOf(mo);
|
|
75
|
+
var year = _rfc850Year(yy, mon, dd, t.hh, t.mi, t.ss, refMs);
|
|
76
|
+
return { day: dd, monName: mo, year: year, hh: t.hh, mi: t.mi, ss: t.ss };
|
|
56
77
|
}
|
|
57
78
|
function _scanAsctime(s) {
|
|
58
79
|
var p = _matchName(s, 0, DOW_SHORT);
|
package/lib/inspect.js
CHANGED
|
@@ -164,16 +164,17 @@ var KU_LABEL = NAMES.KEY_USAGE;
|
|
|
164
164
|
var EKU_LABEL = NAMES.EXT_KEY_USAGE;
|
|
165
165
|
var GN_KIND = NAMES.GENERAL_NAME;
|
|
166
166
|
|
|
167
|
+
/** @internal A 4- or 16-octet buffer is ONE address, rendered by the shared home so that one address
|
|
168
|
+
* reads the same wherever a report carries it: the RFC 3779 blocks reach that home through
|
|
169
|
+
* `_ipAddressText`, and a GeneralName reaching it through a second renderer would print the same
|
|
170
|
+
* octets two ways in one report. An 8- or 32-octet buffer is a name constraint's address AND mask,
|
|
171
|
+
* which is not an address, so each half is rendered and the two are joined. The home is not widened
|
|
172
|
+
* to take those widths, because a mask reaching it as an address would render as one everywhere. */
|
|
167
173
|
function _ipString(buf) {
|
|
168
174
|
if (!Buffer.isBuffer(buf)) return "";
|
|
169
|
-
if (buf.length === 4
|
|
175
|
+
if (buf.length === 4 || buf.length === 16) return ipUtils.textFromOctets(buf) || _hexColon(buf, {});
|
|
170
176
|
if (buf.length === 8) return _ipString(buf.subarray(0, 4)) + "/" + _ipString(buf.subarray(4));
|
|
171
|
-
if (buf.length ===
|
|
172
|
-
var groups = [];
|
|
173
|
-
for (var i = 0; i < 16; i += 2) groups.push((((buf[i] << 8) | buf[i + 1]) >>> 0).toString(16).toUpperCase());
|
|
174
|
-
var s = groups.join(":");
|
|
175
|
-
return buf.length === 32 ? s + "/" + _ipString(buf.subarray(16)) : s;
|
|
176
|
-
}
|
|
177
|
+
if (buf.length === 32) return _ipString(buf.subarray(0, 16)) + "/" + _ipString(buf.subarray(16));
|
|
177
178
|
return _hexColon(buf, {});
|
|
178
179
|
}
|
|
179
180
|
|