@blamejs/pki 0.8.57 → 0.8.59
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 +35 -0
- package/lib/acme.js +38 -23
- package/lib/asn1-der.js +16 -5
- package/lib/cmc-build.js +4 -1
- package/lib/cmp-build.js +10 -1
- package/lib/cms-decrypt.js +6 -2
- package/lib/cms-verify.js +3 -3
- package/lib/crl-sign.js +3 -3
- package/lib/ct.js +10 -1
- package/lib/est.js +43 -23
- package/lib/guard-identifier.js +1 -1
- package/lib/guard-limits.js +13 -6
- package/lib/http-transport.js +256 -107
- package/lib/key.js +11 -7
- package/lib/path-validate.js +5 -5
- package/lib/pki-build.js +4 -4
- package/lib/sign-scheme.js +4 -4
- package/lib/webcrypto.js +5 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.8.59 — 2026-10-07
|
|
8
|
+
|
|
9
|
+
Render an object identifier, match a header name and read a media type through operations a replacement cannot answer.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The check that holds these modules to captured operations now counts a method reached through an index, not only one reached through a call. It is the same receiver one bracket over, and the earlier form could not see it: two live copies in the RFC 5280 policy-mapping walk, where the copy becomes a node's expected policy set, were found by other means.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- An object identifier is rendered to its dotted form through operations bound when the module loaded, and its arcs are collected by defining each index rather than by appending to the array. Three different replacements each decided that string, which is the value `pki.oid` resolves, which selects a signature algorithm and which names an extension: a replaced integer conversion made the content octets of `1.2.3` render as `1.3.4`, and an accessor inherited at the first index of every array took each arc as it was collected and made the same octets render as `0.1.3`. Appending stores at an index, and a store consults the prototype chain for a setter, so a list this build assembles is now built by defining its indexes.
|
|
18
|
+
- A media type is read on the delimiter character and folded through a captured conversion, in `pki.acme`, `pki.cmp` and `pki.ct`. The token decides which parser a response body is handed to, and it was produced by splitting, trimming and folding through live operations. Splitting is now a character scan, which this toolkit requires in its libraries because a separator's own split hook is part of that operation.
|
|
19
|
+
- `pki.est` reads a content-type parameter through captured operations, and collects the segments it searches by defining each index. That is where the RFC 7030 multipart boundary comes from, so the value decides where a multi-part response is divided: measured, a header declaring `boundary=wire` yielded `forged`, both through a replaced append and through an accessor inherited at an array index. The name comparison that selects a parameter, the slicing that extracts it and the quote stripping around it were each live, as were the name and value of every header on a part, which is where a part's own content type comes from: measured, a part declaring `application/pkcs8` was read as `application/pkcs7-mime` carrying `smime-type=certs-only`. Its two case-insensitive header lookups are folded through the capture on both sides of the comparison.
|
|
20
|
+
- `pki.acme` parses a Link header through captured operations: the header is divided into links, each link into parameters, and the target cut out of its angle brackets, all through operations bound when the module loaded. The relation decides whether a header names a resource the client will follow and the target is the URL it would fetch, and both were produced by methods read off the live prototype: measured, a header stating one alternate chain resolved to a different URL.
|
|
21
|
+
- `pki.cmc.build` selects which request arm a message carries through a selection that reads each candidate once and builds its own result, rather than through a method that is both read off the prototype and specified to construct its result through a replaceable protocol.
|
|
22
|
+
- The algorithm, extension and criticality tables that `pki.path.validate`, `pki.cms`, `pki.crl`, `pki.key`, `pki.webcrypto`, `pki.x509` and the signature-scheme registry build when they load are built through captured walks, so a replacement installed before a module loads cannot decide which rows exist.
|
|
23
|
+
|
|
24
|
+
## v0.8.58 — 2026-10-07
|
|
25
|
+
|
|
26
|
+
Connect under the trust anchors, the address and the identity check the request named, not the ones a second read answers with.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `pki.transport.https` applies the private-address refusal the request asked for. `blockPrivateAddresses` was read once to test for presence and again for the value, so a value that answered `true` and then `false` left the refusal unarmed: measured, a request to `127.0.0.1` completed with the block in force. The same split applied to `tls.servername`, which is the name the server's certificate is verified against, to `tls.checkServerIdentity`, where the caller's hook was found to be a function and then not installed, to `tls` and `tls.anchors` themselves, to the transport's own `defaults.tls`, to `proxy`, and to `timeout` and `maxResponseBytes`. Each is now read once.
|
|
31
|
+
- `pki.transport.https` connects under the trust anchors the request named. The list is assembled by appending to an empty array and the appending operation was read off the live prototype, so one answering with an empty array left the connection with no trust list of its own and the platform's certificate store decided instead: a caller that passed `tls.anchors` to pin one issuer was pinned to none of them. Measured against a loopback endpoint whose certificate only the named anchor trusts, the request then failed on the platform store's verdict rather than succeeding on the caller's. The proxy CONNECT path assembled its own trust list the same way.
|
|
32
|
+
- The address a private-address check cleared is the address the connection is made to. A resolver that returns a list had its own entries forwarded to the socket layer, so an entry whose address answered a public value to the check and a loopback one afterwards reached loopback with `blockPrivateAddresses` set. Each address is now read once, that read is what is classified, and the snapshot is what is handed on.
|
|
33
|
+
- A sparse proxy trust-anchor list is refused rather than read. The eligibility check walks the array's own property descriptors, which cannot see a gap, so a list whose gap was answered from the array prototype was validated as holding one anchor and converted as holding another: reading an array by index is specified to consult the prototype at a hole. A list with a gap is now refused as `transport/bad-proxy`.
|
|
34
|
+
- The platform trust store is read through the provider bound when the module loads, so a replacement installed afterwards cannot answer with its own certificate list for a `useSystemStore` connection.
|
|
35
|
+
- Every connection is opened through the function bound when the module loaded, not a property read off `node:https`, `node:http` or `node:tls` at the moment of the call: the request itself, the proxy CONNECT, the TLS connection made over a tunnel, and the one `pki.transport.peerChain` opens. A replacement installed afterwards sent a request prepared for one destination to another and handed the caller that peer's answer.
|
|
36
|
+
- The socket itself is opened by this module rather than by the agent node builds, on the request and on the proxy CONNECT alike. `agent: false` does not mean no agent, which is what it had been relied on for: node builds one from the default agent's constructor, and that agent's own connection builder opened the socket, so replacing it returned a connection to another destination and the request went out over it. Measured, an `https:` request with `blockPrivateAddresses` set and certificate verification on reached a local plaintext listener that presented no certificate, and its body reached the caller. On the CONNECT path the same replacement could answer with a plain socket in place of the proxy's TLS connection, which is the request that carries `Proxy-Authorization`. The request options node computed for the hop are forwarded unchanged, so every TLS setting still applies, and there is still no connection pooling.
|
|
37
|
+
- A per-request `tls.checkServerIdentity` keeps the transport's default from being read at all. The default was read whether or not the request overrode it, so a default supplied as an accessor ran even for a request that replaced it, and one that throws refused the request before it connected.
|
|
38
|
+
- A response is as long as the bytes that arrived, and holds those bytes. It is allocated at the counted length and written through operations bound at load, because three earlier spellings were each steerable: a copy read off the prototype, a slice whose result is built through the species constructor, and a conversion that reads the received view's own `valueOf` and `length`. The last of those decided the length as well as the content: measured, an eight-byte response came back as 64 bytes with a `maxResponseBytes` of 8, so the size cap was reported as enforced on a body that exceeded it.
|
|
39
|
+
- Every remaining operation `pki.transport.https` reaches a socket through is bound when the module loads: the PEM armor and IPv6 bracket scanners, the peer-chain walk, the anchor conversion, the TLS error classifier that decides whether a failure is reported as a trust failure or a transport one, the header names a proxy credential is stripped from, and the CONNECT tunnel's own options. The trust lists are assembled by copying and appending index by index, because appending with the built-in consults a spreading hook each argument can inherit, which replaced a validated anchor with another one.
|
|
40
|
+
- `guard.limits.cap` compares a value against the bound it validated. `min` and `max` were each read more than once, with the integer check reading one value and the comparison another, so a bound answering a small integer to the check and a large one to the comparison configured a cap that admitted a value above its own limit. Each bound is now read once. No shipped verb could reach this, because every caller in the toolkit passes a fixed bound; the guard is what has to hold for the next one.
|
|
41
|
+
|
|
7
42
|
## v0.8.57 — 2026-10-07
|
|
8
43
|
|
|
9
44
|
Read the OCSP response a CMS message carries in its crls field, instead of accepting it unread.
|
package/lib/acme.js
CHANGED
|
@@ -88,6 +88,7 @@ var _sortI = intrinsic.sort;
|
|
|
88
88
|
var _objectKeys = intrinsic.keys;
|
|
89
89
|
var _lower = intrinsic.toLowerCase;
|
|
90
90
|
var _StringI = intrinsic.String;
|
|
91
|
+
var _acmeTrim = intrinsic.uncurry(String.prototype.trim);
|
|
91
92
|
var _stringify = intrinsic.stringify;
|
|
92
93
|
var _push = intrinsic.push;
|
|
93
94
|
var _pop = intrinsic.pop;
|
|
@@ -98,6 +99,7 @@ var _split = intrinsic.uncurry(String.prototype.split);
|
|
|
98
99
|
var _parseIntI = intrinsic.parseInt;
|
|
99
100
|
var _numToString = intrinsic.numberToString;
|
|
100
101
|
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
102
|
+
var _charAtA = intrinsic.uncurry(String.prototype.charAt);
|
|
101
103
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
102
104
|
var _charAt = intrinsic.uncurry(String.prototype.charAt);
|
|
103
105
|
var pkgVersion = require("../package.json").version;
|
|
@@ -1707,61 +1709,71 @@ function _hasCtlOctet(s) {
|
|
|
1707
1709
|
}
|
|
1708
1710
|
|
|
1709
1711
|
function _splitLinkValues(s) {
|
|
1712
|
+
/** @internal The header is cut through the captured operations, the same as the parameters below:
|
|
1713
|
+
* each piece becomes a link whose relation decides whether this client follows it. */
|
|
1710
1714
|
var out = [], start = 0, inAngle = false, inQuote = false;
|
|
1711
1715
|
for (var i = 0; i < s.length; i++) {
|
|
1712
|
-
var ch = s
|
|
1716
|
+
var ch = _charAtA(s, i);
|
|
1713
1717
|
if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
|
|
1714
1718
|
if (ch === "\"") { inQuote = true; continue; }
|
|
1715
1719
|
if (ch === "<") inAngle = true;
|
|
1716
1720
|
else if (ch === ">") inAngle = false;
|
|
1717
|
-
else if (ch === "," && !inAngle) {
|
|
1721
|
+
else if (ch === "," && !inAngle) { intrinsic.append(out, _strSlice(s, start, i)); start = i + 1; }
|
|
1718
1722
|
}
|
|
1719
1723
|
if (inQuote || inAngle) throw E("acme/bad-link", "the Link header has an unterminated quote or angle bracket (RFC 8288)");
|
|
1720
|
-
|
|
1724
|
+
intrinsic.append(out, _strSlice(s, start));
|
|
1721
1725
|
return out;
|
|
1722
1726
|
}
|
|
1723
1727
|
function _splitLinkParams(rest) {
|
|
1724
1728
|
var parts = [], start = 0, inQuote = false;
|
|
1725
1729
|
for (var i = 0; i < rest.length; i++) {
|
|
1726
|
-
var ch = rest
|
|
1730
|
+
var ch = _charAtA(rest, i);
|
|
1727
1731
|
if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
|
|
1728
1732
|
if (ch === "\"") inQuote = true;
|
|
1729
|
-
else if (ch === ";") {
|
|
1733
|
+
else if (ch === ";") { intrinsic.append(parts, _strSlice(rest, start, i)); start = i + 1; }
|
|
1730
1734
|
}
|
|
1731
|
-
|
|
1735
|
+
intrinsic.append(parts, _strSlice(rest, start));
|
|
1732
1736
|
var out = [];
|
|
1733
1737
|
for (var j = 0; j < parts.length; j++) {
|
|
1734
1738
|
var p = _trimOWS(parts[j]);
|
|
1735
1739
|
if (p === "") { if (j === 0) continue; throw E("acme/bad-link", "an empty Link parameter (a ';' with no parameter) is malformed (RFC 8288): " + JSON.stringify(rest)); }
|
|
1736
|
-
|
|
1737
|
-
|
|
1740
|
+
/** @internal The parameter name and value are cut and folded through the captured operations.
|
|
1741
|
+
* A Link parameter is what names the relation this client follows, and read off the prototype a
|
|
1742
|
+
* replacement chose it: measured, a header stating `rel=next` parsed as `alternate`. */
|
|
1743
|
+
var eq = intrinsic.stringIndexOf(p, "=");
|
|
1744
|
+
var name = _lower(eq === -1 ? p : _trimOWS(_strSlice(p, 0, eq)));
|
|
1738
1745
|
if (!pkix.allCharsIn(name, _LINK_TOKEN_TABLE)) throw E("acme/bad-link", "a Link parameter name must be a token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
|
|
1739
|
-
if (eq === -1) {
|
|
1740
|
-
var v = _trimOWS(p
|
|
1741
|
-
if (v.length >= 2 && v
|
|
1742
|
-
var inner = v
|
|
1746
|
+
if (eq === -1) { intrinsic.append(out, { name: name, value: "", hasValue: false }); continue; }
|
|
1747
|
+
var v = _trimOWS(_strSlice(p, eq + 1));
|
|
1748
|
+
if (v.length >= 2 && _charAtA(v, 0) === "\"" && _charAtA(v, v.length - 1) === "\"") {
|
|
1749
|
+
var inner = _strSlice(v, 1, -1);
|
|
1743
1750
|
if (!_isValidQuotedInterior(inner)) throw E("acme/bad-link", "a quoted Link parameter value is malformed (an unescaped quote or dangling backslash, RFC 7230): " + JSON.stringify(p));
|
|
1744
1751
|
v = _unescapeQuotedPair(inner);
|
|
1745
1752
|
} else if (!pkix.allCharsIn(v, _LINK_TOKEN_TABLE)) {
|
|
1746
1753
|
throw E("acme/bad-link", "an unquoted Link parameter value must be a non-empty token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
|
|
1747
1754
|
}
|
|
1748
|
-
|
|
1755
|
+
intrinsic.append(out, { name: name, value: v, hasValue: true });
|
|
1749
1756
|
}
|
|
1750
1757
|
return out;
|
|
1751
1758
|
}
|
|
1752
1759
|
function _relHas(rel, relName) {
|
|
1753
|
-
|
|
1754
|
-
|
|
1760
|
+
/** @internal The relation names are scanned on the delimiter CHARACTER, which hard rule 11 requires
|
|
1761
|
+
* in lib/, and each token is folded through the captured conversion: the match decides whether a
|
|
1762
|
+
* Link header names the resource this client then follows. */
|
|
1763
|
+
var toks = intrinsic.splitChar(_StringI(rel), 32);
|
|
1764
|
+
for (var i = 0; i < toks.length; i++) { if (_lower(toks[i]) === relName) return true; }
|
|
1755
1765
|
return false;
|
|
1756
1766
|
}
|
|
1757
1767
|
function _parseLinkValue(raw) {
|
|
1758
1768
|
var s = _trimOWS(raw);
|
|
1759
1769
|
if (s === "") return null;
|
|
1760
|
-
var gt =
|
|
1761
|
-
if (s
|
|
1770
|
+
var gt = intrinsic.stringIndexOf(s, ">");
|
|
1771
|
+
if (_charAtA(s, 0) !== "<" || gt === -1) throw E("acme/bad-link", "a Link value must be <URI-Reference> with parameters (RFC 8288): " + JSON.stringify(raw));
|
|
1762
1772
|
var rest = _stripLeadingOWS(s, gt + 1);
|
|
1763
|
-
if (rest !== "" && rest
|
|
1764
|
-
|
|
1773
|
+
if (rest !== "" && _charAtA(rest, 0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
|
|
1774
|
+
/** @internal The target this client may follow is cut through the captured operation: read off the
|
|
1775
|
+
* prototype, a replacement decided the URL a Link header resolves to. */
|
|
1776
|
+
var uri = _strSlice(s, 1, gt);
|
|
1765
1777
|
if (_linkUriInvalid(uri)) throw E("acme/bad-link", "a Link URI-Reference contains a character, percent-escape, or encoded dot-segment not permitted by RFC 3986: " + JSON.stringify(raw));
|
|
1766
1778
|
var params = _splitLinkParams(rest), rel = "", relSeen = false, anchor = null, anchorSeen = false;
|
|
1767
1779
|
for (var i = 0; i < params.length; i++) {
|
|
@@ -1780,14 +1792,16 @@ function _parseLinkRelation(headers, base, relName, opts) {
|
|
|
1780
1792
|
var raw = null;
|
|
1781
1793
|
var hNames = guard.identifier.optionNames(headers);
|
|
1782
1794
|
for (var hi = 0; hi < hNames.length; hi++) {
|
|
1783
|
-
if (hNames[hi]
|
|
1795
|
+
if (_lower(hNames[hi]) === "link") { raw = headers[hNames[hi]]; break; }
|
|
1784
1796
|
}
|
|
1785
1797
|
if (raw == null) return [];
|
|
1786
1798
|
var baseUrl = new _URL(base), baseOrigin = _urlOrigin(baseUrl), baseHref = _urlHref(baseUrl);
|
|
1787
1799
|
var baseKey = _dedupKey(baseHref);
|
|
1788
|
-
var fields =
|
|
1800
|
+
var fields = intrinsic.isArray(raw) ? raw : [raw], out = [], seen = intrinsic.create(null), distinctCount = 0, totalBytes = 0;
|
|
1789
1801
|
for (var fi = 0; fi < fields.length; fi++) {
|
|
1790
|
-
|
|
1802
|
+
/** @internal The header field is converted through the capture: read through the live global, a
|
|
1803
|
+
* replacement supplied the whole Link header and with it the target this client resolves. */
|
|
1804
|
+
var field = _StringI(fields[fi]);
|
|
1791
1805
|
totalBytes += field.length + 1;
|
|
1792
1806
|
if (totalBytes > LINK_HEADER_MAX_BYTES) throw E("acme/bad-link", "the Link header(s) exceed the " + LINK_HEADER_MAX_BYTES + "-byte aggregate cap (RFC 8288, CWE-770)");
|
|
1793
1807
|
if (_hasCtlOctet(field)) throw E("acme/bad-link", "a Link header must not contain control octets (RFC 9110 field-value)");
|
|
@@ -2202,7 +2216,8 @@ function client(directoryUrl, opts) {
|
|
|
2202
2216
|
function _pollAuthorization(url, budget) { return _poll("authorization", url, budget); }
|
|
2203
2217
|
|
|
2204
2218
|
function _readCertChain(res) {
|
|
2205
|
-
var
|
|
2219
|
+
var ctParts = intrinsic.splitChar(_StringI(res.headers["content-type"] || ""), 59);
|
|
2220
|
+
var ctToken = _lower(_acmeTrim(ctParts.length ? ctParts[0] : ""));
|
|
2206
2221
|
if (ctToken !== "application/pem-certificate-chain") throw E("acme/bad-certificate-chain", "the certificate download returned an unexpected media type " + JSON.stringify(ctToken) + " (expected application/pem-certificate-chain, RFC 8555 sec. 7.4.2)");
|
|
2207
2222
|
var chain = _splitPemChain(_bodyText(res));
|
|
2208
2223
|
return { certificate: chain[0], chain: chain.slice(1), certificates: chain };
|
package/lib/asn1-der.js
CHANGED
|
@@ -566,8 +566,16 @@ function decodeOidContent(buf) {
|
|
|
566
566
|
"OBJECT IDENTIFIER carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
|
|
567
567
|
}
|
|
568
568
|
var value = 0n;
|
|
569
|
-
|
|
570
|
-
|
|
569
|
+
/** @internal The arc is accumulated through the captured conversion. Read off the global, a
|
|
570
|
+
* replacement decides the sub-identifier, and therefore the dotted string every algorithm and
|
|
571
|
+
* extension decision in this toolkit is made on: measured, the same two bytes rendered as
|
|
572
|
+
* `1.2.3` and as `1.2.4`. */
|
|
573
|
+
for (var k = arcStart; k <= i; k++) value = value * 128n + intrinsic.BigInt(buf[k] & 0x7f);
|
|
574
|
+
/** @internal `append`, not a captured `push`: appending with `push` STORES at the index, and a
|
|
575
|
+
* store walks the prototype chain for a setter, so an accessor installed at
|
|
576
|
+
* `Array.prototype[0]` took the arc and answered with another. `append` defines the index on
|
|
577
|
+
* the array itself. */
|
|
578
|
+
intrinsic.append(arcs, value);
|
|
571
579
|
arcStart = i + 1;
|
|
572
580
|
}
|
|
573
581
|
}
|
|
@@ -577,9 +585,12 @@ function decodeOidContent(buf) {
|
|
|
577
585
|
if (first < 40n) { a1 = 0n; a2 = first; }
|
|
578
586
|
else if (first < 80n) { a1 = 1n; a2 = first - 40n; }
|
|
579
587
|
else { a1 = 2n; a2 = first - 80n; }
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
588
|
+
/** @internal Each arc is rendered through the captured conversion. Read off the prototype, the
|
|
589
|
+
* value that becomes the dotted identifier is whatever a replacement returns, and the identifier
|
|
590
|
+
* is what every algorithm and extension decision in this toolkit is made on. */
|
|
591
|
+
var out = [intrinsic.bigIntToString(a1, 10), intrinsic.bigIntToString(a2, 10)];
|
|
592
|
+
for (var j = 1; j < arcs.length; j++) intrinsic.append(out, intrinsic.bigIntToString(arcs[j], 10));
|
|
593
|
+
return intrinsic.join(out, ".");
|
|
583
594
|
}
|
|
584
595
|
|
|
585
596
|
function _decodeText(buf, encoding) {
|
package/lib/cmc-build.js
CHANGED
|
@@ -173,7 +173,10 @@ function fixedRequestId(req, index) {
|
|
|
173
173
|
return "unknown field " + JSON.stringify(k) + " on request " + index +
|
|
174
174
|
" -- a request names one of tcr / crm / orm, and optionally bodyPartID";
|
|
175
175
|
});
|
|
176
|
-
|
|
176
|
+
/** @internal `selectList`, not the live `filter`: the selection decides which request arm this
|
|
177
|
+
* message carries, and `Array.prototype.filter` is both read off the prototype and specified to
|
|
178
|
+
* build its result through the species constructor. */
|
|
179
|
+
var arms = intrinsic.selectList(["tcr", "crm", "orm"], function (k) { return req[k] != null; });
|
|
177
180
|
if (arms.length !== 1) {
|
|
178
181
|
throw E("cmc/bad-input",
|
|
179
182
|
"each request names exactly one of tcr / crm / orm, got " + (arms.length ? arms.join(" + ") : "none") +
|
package/lib/cmp-build.js
CHANGED
|
@@ -29,6 +29,7 @@ var cmp = require("./schema-cmp");
|
|
|
29
29
|
var crmf = require("./crmf-sign");
|
|
30
30
|
var schemaCrmf = require("./schema-crmf");
|
|
31
31
|
var intrinsic = require("./guard-intrinsic");
|
|
32
|
+
var _strTrim = intrinsic.uncurry(String.prototype.trim);
|
|
32
33
|
/** @internal The promise a signature self-check is awaited through, built from the constructor captured
|
|
33
34
|
* at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
|
|
34
35
|
* the construction to whatever that binding holds, and one settling without awaiting its argument runs
|
|
@@ -1012,7 +1013,15 @@ function _tlsForTransfer(opts) {
|
|
|
1012
1013
|
return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
|
|
1013
1014
|
}
|
|
1014
1015
|
|
|
1015
|
-
|
|
1016
|
+
/** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
|
|
1017
|
+
* rather than through `split`, which hard rule 11 bans in lib/ because the separator's own
|
|
1018
|
+
* `Symbol.split` is part of that operation. The token decides which parser a response body is
|
|
1019
|
+
* handed to. */
|
|
1020
|
+
function _ctypeToken(headers) {
|
|
1021
|
+
var raw = intrinsic.String((headers || {})["content-type"] || "");
|
|
1022
|
+
var parts = intrinsic.splitChar(raw, 59);
|
|
1023
|
+
return intrinsic.toLowerCase(_strTrim(parts.length ? parts[0] : ""));
|
|
1024
|
+
}
|
|
1016
1025
|
function _isPkixcmp(headers) { var t = _ctypeToken(headers); return t === PKIXCMP || t === PKIXCMP_POLL; }
|
|
1017
1026
|
|
|
1018
1027
|
/** @internal The sentinel is added at the one exit rather than at each `return`, so a branch added
|
package/lib/cms-decrypt.js
CHANGED
|
@@ -631,9 +631,13 @@ var HASH_W3C = intrinsic.create(null); HASH_W3C[O("sha1")] = "SHA-1"; HASH_W3C[O
|
|
|
631
631
|
function _hashW3c(o) { if (!HASH_W3C[o]) throw _err("cms/unsupported-algorithm", "unsupported OAEP hash " + o); return HASH_W3C[o]; }
|
|
632
632
|
|
|
633
633
|
var X963_HASH = intrinsic.create(null);
|
|
634
|
-
[["dhSinglePass-stdDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-stdDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-stdDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-stdDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-stdDH-sha512kdf-scheme", "SHA-512"],
|
|
634
|
+
var _X963_ROWS = [["dhSinglePass-stdDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-stdDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-stdDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-stdDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-stdDH-sha512kdf-scheme", "SHA-512"],
|
|
635
635
|
["dhSinglePass-cofactorDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-cofactorDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-cofactorDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-cofactorDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-cofactorDH-sha512kdf-scheme", "SHA-512"]
|
|
636
|
-
]
|
|
636
|
+
];
|
|
637
|
+
/** @internal The registry is built through the captured walk. The method is read off the live
|
|
638
|
+
* prototype at the moment it runs even where the receiver is a literal, and these rows decide which
|
|
639
|
+
* hash a key-agreement recipient's KDF uses. */
|
|
640
|
+
intrinsic.forEach(_X963_ROWS, function (r) { X963_HASH[O(r[0])] = r[1]; });
|
|
637
641
|
function _x963Hash(scheme) { if (!X963_HASH[scheme]) throw _err("cms/unsupported-algorithm", "unsupported kari key-agreement scheme " + scheme); return X963_HASH[scheme]; }
|
|
638
642
|
function _kariWrap(kea) {
|
|
639
643
|
var params = asn1.decode(kea.parameters);
|
package/lib/cms-verify.js
CHANGED
|
@@ -126,9 +126,9 @@ var SIG_SCHEME = intrinsic.assign(intrinsic.create(null), {
|
|
|
126
126
|
ecdsaWithSHA384: { kind: "ec", hash: "SHA-384", params: "absent" },
|
|
127
127
|
ecdsaWithSHA512: { kind: "ec", hash: "SHA-512", params: "absent" },
|
|
128
128
|
});
|
|
129
|
-
["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
130
|
-
|
|
131
|
-
]
|
|
129
|
+
intrinsic.forEach(["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
130
|
+
"shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
|
|
131
|
+
], function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + intrinsic.toUpperCase(s), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
|
|
132
132
|
function _isDerNull(p) { return guard.der.isNull(p); }
|
|
133
133
|
function _algParamsOk(shape, p) { return shape === "null" ? _isDerNull(p) : (p === null || p === undefined); }
|
|
134
134
|
var EC_CURVE = intrinsic.create(null);
|
package/lib/crl-sign.js
CHANGED
|
@@ -108,8 +108,8 @@ var KNOWN_REVOKED_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
var REQUIRED_CRITICALITY = intrinsic.create(null);
|
|
111
|
-
[["cRLNumber", false], ["authorityKeyIdentifier", false], ["freshestCRL", false], ["authorityInfoAccess", false],
|
|
112
|
-
["issuingDistributionPoint", true], ["deltaCRLIndicator", true]]
|
|
111
|
+
intrinsic.forEach([["cRLNumber", false], ["authorityKeyIdentifier", false], ["freshestCRL", false], ["authorityInfoAccess", false],
|
|
112
|
+
["issuingDistributionPoint", true], ["deltaCRLIndicator", true]], function (r) { REQUIRED_CRITICALITY[O(r[0])] = r[1]; });
|
|
113
113
|
|
|
114
114
|
var REQUIRED_ENTRY_CRITICALITY = intrinsic.create(null);
|
|
115
115
|
(function () {
|
|
@@ -199,7 +199,7 @@ var _PRE_ENCODED_IDP_SCHEMA = pkix.issuingDistributionPoint("crl/bad-idp");
|
|
|
199
199
|
function _validatePreEncodedIdp(inner) {
|
|
200
200
|
if (!inner.children || !inner.children.length) throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint MUST NOT be empty (RFC 5280 sec. 5.2.5)");
|
|
201
201
|
var f = schema.walk(_PRE_ENCODED_IDP_SCHEMA, inner, NS).fields;
|
|
202
|
-
["onlyContainsUserCerts", "onlyContainsCACerts", "onlyContainsAttributeCerts"]
|
|
202
|
+
intrinsic.forEach(["onlyContainsUserCerts", "onlyContainsCACerts", "onlyContainsAttributeCerts"], function (name) {
|
|
203
203
|
if (f[name].present && f[name].value !== true) throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint " + name + " encodes its DEFAULT FALSE, which DER omits (X.690 sec. 11.5)");
|
|
204
204
|
});
|
|
205
205
|
if (f.indirectCRL.present) throw _err("crl/bad-idp", _INDIRECT_IDP_DEFERRED);
|
package/lib/ct.js
CHANGED
|
@@ -63,6 +63,7 @@ var _bufToString = intrinsic.bufToString;
|
|
|
63
63
|
var _bufferCompare = intrinsic.compare;
|
|
64
64
|
var _subarray = intrinsic.subarray;
|
|
65
65
|
var _toLowerCase = intrinsic.toLowerCase;
|
|
66
|
+
var _ctTrim = intrinsic.uncurry(String.prototype.trim);
|
|
66
67
|
var _bufCopy = intrinsic.uncurry(Buffer.prototype.copy);
|
|
67
68
|
var _writeU64BE = intrinsic.uncurry(Buffer.prototype.writeBigUInt64BE);
|
|
68
69
|
var _searchSet = intrinsic.uncurry(URLSearchParams.prototype.set);
|
|
@@ -1230,7 +1231,15 @@ function _assertTransport(opts) {
|
|
|
1230
1231
|
"(request) => Promise<{ status, headers, body }>");
|
|
1231
1232
|
}
|
|
1232
1233
|
|
|
1233
|
-
|
|
1234
|
+
/** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
|
|
1235
|
+
* rather than through `split`, which hard rule 11 bans in lib/ because the separator's own
|
|
1236
|
+
* `Symbol.split` is part of that operation. The token decides which parser a log's response body is
|
|
1237
|
+
* handed to. */
|
|
1238
|
+
function _fetchCtypeToken(headers) {
|
|
1239
|
+
var raw = intrinsic.String((headers || {})["content-type"] || "");
|
|
1240
|
+
var parts = intrinsic.splitChar(raw, 59);
|
|
1241
|
+
return _toLowerCase(_ctTrim(parts.length ? parts[0] : ""));
|
|
1242
|
+
}
|
|
1234
1243
|
|
|
1235
1244
|
function _parseHttpsUrl(u, label) {
|
|
1236
1245
|
var parsed;
|
package/lib/est.js
CHANGED
|
@@ -76,6 +76,9 @@ var intrinsic = require("./guard-intrinsic");
|
|
|
76
76
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
77
77
|
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
78
78
|
var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
|
|
79
|
+
var _estTrim = intrinsic.uncurry(String.prototype.trim);
|
|
80
|
+
var _estCharAt = intrinsic.uncurry(String.prototype.charAt);
|
|
81
|
+
var _estIndexOf = intrinsic.stringIndexOf;
|
|
79
82
|
/** @internal Each verb gates its body on a resolved promise and returns its refusals as rejected ones,
|
|
80
83
|
* and both build from the RECEIVER, so reading the global binding at the call decides whether the body
|
|
81
84
|
* runs and whether a refusal stays a refusal. Taken at load. */
|
|
@@ -250,7 +253,7 @@ function _isMultipartMixedPrefix(ct) {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
function _multipartBoundary(contentType) {
|
|
253
|
-
var ct =
|
|
256
|
+
var ct = _stringOf(contentType || "");
|
|
254
257
|
if (!_isMultipartMixedPrefix(ct)) return null;
|
|
255
258
|
var bp = _ctParam(ct, "boundary");
|
|
256
259
|
if (bp.duplicated) {
|
|
@@ -356,12 +359,16 @@ function splitMultipartMixed(body, contentType) {
|
|
|
356
359
|
var lines = _splitCrlf(_unfoldHeaders(rawHeaders));
|
|
357
360
|
for (var li = 0; li < lines.length; li++) {
|
|
358
361
|
var line = lines[li];
|
|
359
|
-
|
|
360
|
-
|
|
362
|
+
/** @internal A part's header name and value are cut, trimmed and folded through the captured
|
|
363
|
+
* operations. One of those headers is the part's own content type, which selects the reader
|
|
364
|
+
* its body goes to: measured, a part stating `application/pkcs8` was read as
|
|
365
|
+
* `application/pkcs7-mime` with a `smime-type` of `certs-only`. */
|
|
366
|
+
var col = _estIndexOf(line, ":");
|
|
367
|
+
if (col > 0) headers[_toLowerCase(_estTrim(_strSlice(line, 0, col)))] = _estTrim(_strSlice(line, col + 1));
|
|
361
368
|
}
|
|
362
369
|
var partCt = headers["content-type"] || "";
|
|
363
370
|
if (_ciStartsWith(partCt, "MULTIPART/")) throw E("est/bad-multipart", "a nested multipart part is not permitted");
|
|
364
|
-
|
|
371
|
+
intrinsic.append(parts, { headers: headers, contentType: partCt, body: partBody });
|
|
365
372
|
}
|
|
366
373
|
return parts;
|
|
367
374
|
}
|
|
@@ -457,29 +464,37 @@ function _recipientMatches(recipientInfos, opts) {
|
|
|
457
464
|
return false;
|
|
458
465
|
}
|
|
459
466
|
|
|
467
|
+
/** @internal The segments are collected through the captured append. Read off the prototype, a
|
|
468
|
+
* replacement decides which segments a parameter is then looked for in, and one of those parameters
|
|
469
|
+
* is the multipart boundary a response body is divided on: measured, a `boundary=wire` header
|
|
470
|
+
* yielded `forged`. */
|
|
460
471
|
function _splitContentTypeParams(s) {
|
|
461
472
|
var segs = [], cur = "", inQuote = false;
|
|
462
473
|
for (var i = 0; i < s.length; i++) {
|
|
463
|
-
var ch = s
|
|
474
|
+
var ch = _estCharAt(s, i);
|
|
464
475
|
if (ch === '"') { inQuote = !inQuote; cur += ch; }
|
|
465
|
-
else if (ch === ";" && !inQuote) {
|
|
476
|
+
else if (ch === ";" && !inQuote) { intrinsic.append(segs, cur); cur = ""; }
|
|
466
477
|
else cur += ch;
|
|
467
478
|
}
|
|
468
|
-
|
|
479
|
+
intrinsic.append(segs, cur);
|
|
469
480
|
return segs;
|
|
470
481
|
}
|
|
471
482
|
|
|
472
483
|
function _ctParam(contentType, name) {
|
|
473
|
-
var segs = _splitContentTypeParams(
|
|
484
|
+
var segs = _splitContentTypeParams(_stringOf(contentType || ""));
|
|
474
485
|
var value = null, count = 0;
|
|
486
|
+
/** @internal Each parameter is read through the captured operations. The name decides which
|
|
487
|
+
* parameter this is, and the value it returns is the one a caller acts on: for the RFC 7030
|
|
488
|
+
* multipart boundary, it is where a response body is split. */
|
|
475
489
|
for (var i = 1; i < segs.length; i++) {
|
|
476
|
-
var
|
|
490
|
+
var seg = segs[i];
|
|
491
|
+
var eq = _estIndexOf(seg, "=");
|
|
477
492
|
if (eq === -1) continue;
|
|
478
|
-
if (
|
|
493
|
+
if (_toLowerCase(_estTrim(_strSlice(seg, 0, eq))) !== name) continue;
|
|
479
494
|
count++;
|
|
480
495
|
if (count > 1) continue;
|
|
481
|
-
var val =
|
|
482
|
-
if (val.length >= 2 && val
|
|
496
|
+
var val = _estTrim(_strSlice(seg, eq + 1));
|
|
497
|
+
if (val.length >= 2 && _estCharAt(val, 0) === '"' && _estCharAt(val, val.length - 1) === '"') val = _strSlice(val, 1, -1);
|
|
483
498
|
value = val;
|
|
484
499
|
}
|
|
485
500
|
return { value: value, duplicated: count > 1 };
|
|
@@ -501,12 +516,15 @@ function _mediaType(seg) {
|
|
|
501
516
|
return _toLowerCase(_strSlice(seg, a, b));
|
|
502
517
|
}
|
|
503
518
|
|
|
519
|
+
/** @internal The media type and the S/MIME type are produced through the captured conversions. The
|
|
520
|
+
* media type selects which reader a part's body goes to, and read through the live global `String`
|
|
521
|
+
* a replacement chose it: measured, `text/plain` arrived as `application/pkcs8`. */
|
|
504
522
|
function _partMediaType(contentType) {
|
|
505
|
-
var segs = _splitContentTypeParams(
|
|
523
|
+
var segs = _splitContentTypeParams(_stringOf(contentType || ""));
|
|
506
524
|
var st = _ctParam(contentType, "smime-type");
|
|
507
525
|
return {
|
|
508
526
|
media: _mediaType(segs[0]),
|
|
509
|
-
smimeType: st.value === null ? null : st.value
|
|
527
|
+
smimeType: st.value === null ? null : _toLowerCase(st.value),
|
|
510
528
|
ambiguous: st.duplicated,
|
|
511
529
|
};
|
|
512
530
|
}
|
|
@@ -1279,7 +1297,7 @@ function _requestBinding(der) {
|
|
|
1279
1297
|
var body = cmcFmt.parse(der);
|
|
1280
1298
|
var ids = [];
|
|
1281
1299
|
var paths = [];
|
|
1282
|
-
[body.requests, body.controls, body.cmsSequence, body.otherMsgs]
|
|
1300
|
+
intrinsic.forEach([body.requests, body.controls, body.cmsSequence, body.otherMsgs], function (list) {
|
|
1283
1301
|
(list || []).forEach(function (el) {
|
|
1284
1302
|
if (el && el.bodyPartID != null) { ids.push(el.bodyPartID); paths.push([el.bodyPartID]); }
|
|
1285
1303
|
});
|
|
@@ -1325,7 +1343,7 @@ function _collectNestedPaths(list, prefix, out, depth) {
|
|
|
1325
1343
|
catch (_e) { /* allow:swallow-unverified the outer request must itself parse for this to run, which already rejects a malformed nested ContentInfo; a payload that survives that and still fails to read back lands on the same no-path refusal the non-pkiData route below takes, which the vectors drive */ return; }
|
|
1326
1344
|
if (!inner || inner.kind !== "pkiData") return;
|
|
1327
1345
|
var path = prefix.concat([el.bodyPartID]);
|
|
1328
|
-
[inner.requests, inner.controls, inner.cmsSequence, inner.otherMsgs]
|
|
1346
|
+
intrinsic.forEach([inner.requests, inner.controls, inner.cmsSequence, inner.otherMsgs], function (l) {
|
|
1329
1347
|
(l || []).forEach(function (e2) { if (e2 && e2.bodyPartID != null) out.push(path.concat([e2.bodyPartID])); });
|
|
1330
1348
|
});
|
|
1331
1349
|
_collectNestedPaths(inner.cmsSequence, path, out, depth + 1);
|
|
@@ -1405,7 +1423,7 @@ function _fullcmcResult(res, opts, wanted, sent) {
|
|
|
1405
1423
|
}
|
|
1406
1424
|
var smimeType = pt200.smimeType;
|
|
1407
1425
|
if (smimeType === "certs-only") {
|
|
1408
|
-
var unecho = ["transactionId", "senderNonce", "dataReturn"]
|
|
1426
|
+
var unecho = intrinsic.selectList(["transactionId", "senderNonce", "dataReturn"], function (k) {
|
|
1409
1427
|
return sent[k] != null;
|
|
1410
1428
|
});
|
|
1411
1429
|
if (unecho.length) {
|
|
@@ -1731,21 +1749,23 @@ function _assertConfidentialCipher(res) {
|
|
|
1731
1749
|
|
|
1732
1750
|
function _ciHeaderList(headers, name) {
|
|
1733
1751
|
headers = headers || {};
|
|
1734
|
-
|
|
1752
|
+
/** @internal The header NAME a value is selected under is folded through the captured conversion on
|
|
1753
|
+
* both sides of the comparison: the match decides which header this response is read from. */
|
|
1754
|
+
var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), parts = [];
|
|
1735
1755
|
for (var i = 0; i < keys.length; i++) {
|
|
1736
|
-
if (keys[i]
|
|
1756
|
+
if (_toLowerCase(keys[i]) !== lname) continue;
|
|
1737
1757
|
var v = headers[keys[i]];
|
|
1738
1758
|
if (v == null) continue;
|
|
1739
|
-
|
|
1759
|
+
intrinsic.append(parts, intrinsic.isArray(v) ? intrinsic.join(v, ", ") : _stringOf(v));
|
|
1740
1760
|
}
|
|
1741
|
-
return parts.length ?
|
|
1761
|
+
return parts.length ? intrinsic.join(parts, ", ") : null;
|
|
1742
1762
|
}
|
|
1743
1763
|
|
|
1744
1764
|
function _ciHeader(headers, name) {
|
|
1745
1765
|
headers = headers || {};
|
|
1746
|
-
var lname = name
|
|
1766
|
+
var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), found = null, n = 0;
|
|
1747
1767
|
for (var i = 0; i < keys.length; i++) {
|
|
1748
|
-
if (keys[i]
|
|
1768
|
+
if (_toLowerCase(keys[i]) !== lname) continue;
|
|
1749
1769
|
n += 1;
|
|
1750
1770
|
if (n === 1) found = headers[keys[i]];
|
|
1751
1771
|
}
|
package/lib/guard-identifier.js
CHANGED
|
@@ -257,7 +257,7 @@ var _CONCRETE_TYPED_MEMBERS = ["BYTES_PER_ELEMENT"];
|
|
|
257
257
|
var _BUFFER_PROTO_MEMBERS = ["parent", "offset"];
|
|
258
258
|
|
|
259
259
|
var _ARRAY_MEMBERS = _ARRAY_PROTO_MEMBERS;
|
|
260
|
-
var _REGEXP_MEMBERS = ["lastIndex"]
|
|
260
|
+
var _REGEXP_MEMBERS = intrinsic.concatList(["lastIndex"], _REGEXP_PROTO_MEMBERS);
|
|
261
261
|
var _VIEW_MEMBERS = _TYPED_PROTO_MEMBERS.concat(_CONCRETE_TYPED_MEMBERS);
|
|
262
262
|
var _BUFFER_KIND_MEMBERS = _VIEW_MEMBERS.concat(_BUFFER_PROTO_MEMBERS);
|
|
263
263
|
var _DATAVIEW_MEMBERS = _DATAVIEW_PROTO_MEMBERS;
|
package/lib/guard-limits.js
CHANGED
|
@@ -16,13 +16,19 @@ function _monotonicNow() { return _Number(_hrtimeBigint() / 1000000n); }
|
|
|
16
16
|
function cap(value, key, dflt, opts) {
|
|
17
17
|
if (value === undefined) return dflt;
|
|
18
18
|
opts = opts || {};
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/** @internal Each bound is read ONCE, into the value the integer check and the comparison share.
|
|
20
|
+
* Read twice, the check saw one bound and the comparison another, so an `opts` answering a valid
|
|
21
|
+
* small integer to the check and a large one to the comparison configured a cap that admitted a
|
|
22
|
+
* value above the bound it had just been validated against. */
|
|
23
|
+
var rawMin = opts.min, rawMax = opts.max;
|
|
24
|
+
var min = rawMin === undefined ? 0 : rawMin;
|
|
25
|
+
var hasMax = rawMax !== undefined;
|
|
26
|
+
if (!_isInteger(min) || (hasMax && !_isInteger(rawMax))) {
|
|
21
27
|
throw new TypeError("guard.limits.cap: the min/max bounds for " + key + " must be integers");
|
|
22
28
|
}
|
|
23
|
-
if (!_isInteger(value) || value < min || (
|
|
24
|
-
var want = "an integer >= " + min + (
|
|
25
|
-
if (min === 0 &&
|
|
29
|
+
if (!_isInteger(value) || value < min || (hasMax && value > rawMax)) {
|
|
30
|
+
var want = "an integer >= " + min + (hasMax ? " and <= " + rawMax : "");
|
|
31
|
+
if (min === 0 && !hasMax) want = "a non-negative integer";
|
|
26
32
|
if (opts.E) throw opts.E(opts.code, (opts.label || key) + " must be " + want);
|
|
27
33
|
throw new TypeError("decode: " + key + " must be " + want);
|
|
28
34
|
}
|
|
@@ -136,7 +142,8 @@ function budget(limits) {
|
|
|
136
142
|
// @enforced-by behavioral -- an elapsed-time comparison has no rename-proof shape that separates a deadline from any other subtraction; the vectors driving a checker to its bound are the guard
|
|
137
143
|
function deadline(totalMs, opts) {
|
|
138
144
|
opts = opts || {};
|
|
139
|
-
var
|
|
145
|
+
var rawNow = opts.now;
|
|
146
|
+
var now = typeof rawNow === "function" ? rawNow : _monotonicNow;
|
|
140
147
|
if (!_isInteger(totalMs) || totalMs < 0) {
|
|
141
148
|
throw new TypeError("guard.limits.deadline: the total must be a non-negative integer of milliseconds");
|
|
142
149
|
}
|