@blamejs/pki 0.5.14 → 0.5.16

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.
@@ -36,6 +36,25 @@
36
36
  // code deliberately -- a per-errno surface is attack telemetry.
37
37
 
38
38
  var zlib = require("zlib");
39
+ // Taken at load. The capability probe below decides which algorithms are safe to expose, and the
40
+ // bounded decompress measures its cap with these, so a replacement chooses both. See
41
+ // guard-intrinsic for the whole captured set.
42
+ var _intrinsic = require("./guard-intrinsic");
43
+ var _bufferFrom = _intrinsic.bufferFrom;
44
+ var _isBuffer = _intrinsic.isBuffer;
45
+ var _isInteger = _intrinsic.isInteger;
46
+ var _objectKeys = _intrinsic.keys;
47
+ var _stringify = _intrinsic.stringify;
48
+ // This probe decides which algorithms qualify as truncation-reporting, so both the round-trip
49
+ // comparison and the frame-size floor below settle whether a compressor is trusted. Reading either
50
+ // off a prototype lets a replacement qualify one that silently accepts a cut frame.
51
+ var _compare = _intrinsic.compare;
52
+ var _sizeOf = _intrinsic.sizeOf;
53
+ // The cut that makes the truncated frame. A replacement returning the whole frame means the
54
+ // decompressor is handed something intact, so an algorithm that ignores truncation qualifies on a
55
+ // test that never truncated anything.
56
+ var _subarray = _intrinsic.uncurry(Uint8Array.prototype.subarray);
57
+ var _hasOwn = _intrinsic.hasOwn;
39
58
 
40
59
  // The decompressor per algorithm: a registry, so a new algorithm is a row rather
41
60
  // than a branch and an unrecognized name fails closed.
@@ -77,28 +96,28 @@ function _reportsTruncation(name) {
77
96
  var compress = _PROBE_COMPRESS[name];
78
97
  var decompress = DECOMPRESS[name];
79
98
  if (typeof compress !== "function" || typeof decompress !== "function") return false;
80
- var sample = Buffer.from("0123456789abcdefghijklmnopqrstuvwxyz0123456789");
99
+ var sample = _bufferFrom("0123456789abcdefghijklmnopqrstuvwxyz0123456789");
81
100
  var frame, whole;
82
101
  try { frame = compress(sample); }
83
102
  catch (_e) { /* allow:swallow-unverified a compressor that cannot compress 46 ASCII bytes is a broken runtime; the algorithm simply does not qualify, which is the fail-closed direction */ return false; }
84
103
  // A cut of 2 removes the frame's end marker; the sample is long enough that what remains is
85
104
  // still a plausible frame head and not nothing at all.
86
- if (!Buffer.isBuffer(frame) || frame.length < 4) return false;
105
+ if (!_isBuffer(frame) || _sizeOf(frame) < 4) return false;
87
106
  try { whole = decompress(frame, { maxOutputLength: 4096 }); }
88
107
  catch (_e2) { /* allow:swallow-unverified a decompressor that rejects its own compressor's whole frame is a broken runtime; the algorithm does not qualify, again fail-closed */ return false; }
89
- if (!Buffer.isBuffer(whole) || !whole.equals(sample)) return false;
108
+ if (!_isBuffer(whole) || _compare(whole, sample) !== 0) return false;
90
109
  // The cut frame must be refused. A returned value of any length, including the empty buffer
91
110
  // some bindings hand back, means a truncation would pass through unseen.
92
111
  var cutAccepted = false;
93
112
  try {
94
- decompress(frame.subarray(0, frame.length - 2), { maxOutputLength: 4096 });
113
+ decompress(_subarray(frame, 0, _sizeOf(frame) - 2), { maxOutputLength: 4096 });
95
114
  cutAccepted = true;
96
115
  } catch (_e3) { /* refused, as a decompressor handed an unfinished frame must */ }
97
116
  return !cutAccepted;
98
117
  }
99
118
 
100
119
  var SAFE = {};
101
- Object.keys(DECOMPRESS).forEach(function (n) { if (_reportsTruncation(n)) SAFE[n] = true; });
120
+ _intrinsic.forEach(_objectKeys(DECOMPRESS), function (n) { if (_reportsTruncation(n)) SAFE[n] = true; });
102
121
 
103
122
  // bounded(algorithm, stream, cap, E, codes, label) -> Buffer. Decompress `stream`
104
123
  // under `algorithm`, refusing any output that would exceed `cap` bytes and any
@@ -116,19 +135,19 @@ function bounded(algorithm, stream, cap, E, codes, label) {
116
135
  // fault, not untrusted input: a caller resolves a wire value to a name against algorithms()
117
136
  // and rejects one it does not find, before reaching here. Arriving with a name outside that
118
137
  // set means the consumer skipped the step, so it faults loudly and never silently.
119
- if (!Object.prototype.hasOwnProperty.call(SAFE, algorithm) || !decompress) {
120
- throw new TypeError("guard.compress.bounded: unknown or unsafe algorithm " + JSON.stringify(algorithm));
138
+ if (!_hasOwn(SAFE, algorithm) || !decompress) {
139
+ throw new TypeError("guard.compress.bounded: unknown or unsafe algorithm " + _stringify(algorithm));
121
140
  }
122
141
  // The byte-source normalization is guard.bytes.view's job and belongs at the
123
142
  // consumer's boundary; reaching here with anything else means it was skipped.
124
- if (!Buffer.isBuffer(stream)) {
143
+ if (!_isBuffer(stream)) {
125
144
  throw new TypeError("guard.compress.bounded: stream must be a Buffer (route the input through guard.bytes.view first)");
126
145
  }
127
146
  // The ceiling is an authoring input: an undefined / NaN / fractional / negative
128
147
  // cap would silently disable the bomb defense. It must also be at least 1 --
129
148
  // node rejects maxOutputLength 0 with a different fault than a cap breach, so a
130
149
  // zero cap would surface as a malformed-stream verdict instead of a config error.
131
- if (!Number.isInteger(cap) || cap < 1) {
150
+ if (!_isInteger(cap) || cap < 1) {
132
151
  throw new TypeError("guard.compress.bounded: cap must be a positive integer");
133
152
  }
134
153
  var what = label || "the compressed input";
@@ -145,8 +164,9 @@ function bounded(algorithm, stream, cap, E, codes, label) {
145
164
  // ends before the input does means the remainder was ignored, which is the
146
165
  // malleability above, so the whole input must be exactly one frame.
147
166
  var consumed = res.engine.bytesWritten;
148
- if (consumed !== stream.length) {
149
- throw E(codes.failed, what + " carries " + (stream.length - consumed) +
167
+ var streamLen = _sizeOf(stream);
168
+ if (consumed !== streamLen) {
169
+ throw E(codes.failed, what + " carries " + (streamLen - consumed) +
150
170
  " trailing byte(s) after the end of the compressed stream");
151
171
  }
152
172
  return res.buffer;
@@ -159,6 +179,6 @@ function bounded(algorithm, stream, cap, E, codes, label) {
159
179
  // @enforced-by behavioral -- a read-only list of the safe registry's keys has no
160
180
  // rename-proof code shape to re-inline; the guard's reject path is the contract,
161
181
  // and a consumer offering an algorithm this list omits fails closed at `bounded`.
162
- function algorithms() { return Object.keys(SAFE); }
182
+ function algorithms() { return _objectKeys(SAFE); }
163
183
 
164
184
  module.exports = { bounded: bounded, algorithms: algorithms };
package/lib/guard-der.js CHANGED
@@ -30,6 +30,16 @@
30
30
  // therefore both deferred to first use -- the documented circular-load exception to top-of-file
31
31
  // requires. Guards sit BELOW the codec in the dependency order; this is the one place that
32
32
  // order is inverted, and it is inverted lazily.
33
+ // The byte comparison SET-OF ordering is decided by, taken at load. A replacement chooses which
34
+ // encodings this guard calls correctly ordered. See guard-intrinsic for the whole captured set.
35
+ var _compare = require("./guard-intrinsic").compare;
36
+ var _forEach = require("./guard-intrinsic").forEach;
37
+ // The emptiness gate below decides whether bytes reach the decoder at all, and on a byte view
38
+ // `length` is a replaceable accessor: one answering non-zero for an empty buffer sends nothing to
39
+ // asn1.decode as though it were an element.
40
+ var _sizeOf = require("./guard-intrinsic").sizeOf;
41
+ var _create = require("./guard-intrinsic").create;
42
+
33
43
  var asn1 = null;
34
44
  function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
35
45
 
@@ -39,7 +49,10 @@ var VALUE_READERS = null;
39
49
  function _readers() {
40
50
  if (VALUE_READERS !== null) return VALUE_READERS;
41
51
  var asn1 = _asn1();
42
- var m = {}, R = asn1.read, T = asn1.TAGS;
52
+ // Null-prototype: this table IS the allowlist of universal tags with a strict content reader, and
53
+ // a tag it does not carry is refused. Built on a `{}`, an inherited numeric property supplies a
54
+ // reader for a tag that was deliberately left out, and the refusal that is the point stops.
55
+ var m = _create(null), R = asn1.read, T = asn1.TAGS;
43
56
  // ENUMERATED shares INTEGER's content rules and is NOT constrained to non-negative
44
57
  // values. X.680 (02/2021) sec. 20.2 requires each NamedNumber's SignedNumber to be
45
58
  // distinct, and sec. 19 defines SignedNumber ::= number | "-" number; the non-negativity
@@ -55,8 +68,8 @@ function _readers() {
55
68
  // it through read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity
56
69
  // class (see asn1-der.js).
57
70
  m[T.NUMERIC_STRING] = R.numericString;
58
- [T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
59
- T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
71
+ _forEach([T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
72
+ T.BMP_STRING, T.UNIVERSAL_STRING], function (t) { m[t] = R.string; });
60
73
  VALUE_READERS = m;
61
74
  return VALUE_READERS;
62
75
  }
@@ -79,9 +92,19 @@ function _readers() {
79
92
  // security posture, which this family avoids, and tightening it for both would reject valid
80
93
  // structured SETs that c509 round-trips today. The rule is therefore unchanged and the cost is
81
94
  // written down rather than discovered later.
82
- var TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
95
+ // Null-prototype: the rank decides canonical SET order, so an inherited property answering for an
96
+ // unrecognized tag class would rank it rather than leave it unranked, and a SET in no canonical
97
+ // order would compare as ordered.
98
+ var TAG_CLASS_RANK = _create(null);
99
+ TAG_CLASS_RANK.universal = 0;
100
+ TAG_CLASS_RANK.application = 1;
101
+ TAG_CLASS_RANK.context = 2;
102
+ TAG_CLASS_RANK["private"] = 3;
83
103
  function setOrderOk(kids) {
84
- var i, dup = false, seen = {};
104
+ // A null-prototype accumulator: on a `{}` a planted `Object.prototype["universal:16"]` answers
105
+ // this membership test for a tag the SET never carried, which reports a duplicate that is not
106
+ // there and selects the wrong canonical-order rule for the whole SET.
107
+ var i, dup = false, seen = _create(null);
85
108
  for (i = 0; i < kids.length; i++) {
86
109
  var key = kids[i].tagClass + ":" + kids[i].tagNumber;
87
110
  if (seen[key]) { dup = true; break; }
@@ -89,7 +112,7 @@ function setOrderOk(kids) {
89
112
  }
90
113
  var octetAsc = true, tagAsc = true;
91
114
  for (i = 1; i < kids.length; i++) {
92
- if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
115
+ if (_compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
93
116
  // Tag order ranks by CLASS first (universal < application < context < private, X.680
94
117
  // sec. 8.6), then by tag number. Compare the class's NUMBER: the names do not sort in
95
118
  // class order.
@@ -141,7 +164,7 @@ function element(node, E, code, label) {
141
164
  // @enforced-by guard-shape-reinlined
142
165
  // @guard-shape must be exactly one well-formed DER element
143
166
  function tlv(content, E, code, label) {
144
- if (!content || content.length === 0) throw E(code, label + " must be a non-empty DER element");
167
+ if (!content || _sizeOf(content) === 0) throw E(code, label + " must be a non-empty DER element");
145
168
  var node;
146
169
  try { node = _asn1().decode(content); }
147
170
  catch (e) { throw E(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
@@ -33,10 +33,26 @@
33
33
  // follows the length, which the caller already caps. It is also the more honest
34
34
  // statement of the rule: the set of permitted characters is the rule, written out.
35
35
  function _alphabet(chars) {
36
+ // allow:guard-reads-runtime-live -- the three tables below are the only callers and they are
37
+ // built at module load, before any caller code has run. There is no live read to remove.
36
38
  var t = new Uint8Array(128);
37
- for (var i = 0; i < chars.length; i++) t[chars.charCodeAt(i)] = 1;
39
+ for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = 1;
38
40
  return t;
39
41
  }
42
+ // Both halves of the round trip, taken at load, along with the string reads the alphabet checks
43
+ // are built on. Canonicality here is decided by decoding the text and re-encoding the result, so
44
+ // either half is enough to decide the answer: a `toString` returning the input makes every
45
+ // non-canonical encoding canonical, and a `charCodeAt` deciding what is in the alphabet chooses
46
+ // which characters are there to be judged.
47
+ var _intrinsic = require("./guard-intrinsic");
48
+ var _bufferFrom = _intrinsic.bufferFrom;
49
+ var _toString = _intrinsic.uncurry(Buffer.prototype.toString);
50
+ var _charCodeAt = _intrinsic.uncurry(String.prototype.charCodeAt);
51
+ var _strSlice = _intrinsic.uncurry(String.prototype.slice);
52
+ var _toLowerCase = _intrinsic.uncurry(String.prototype.toLowerCase);
53
+ var _isInteger = _intrinsic.isInteger;
54
+ var _floor = _intrinsic.floor;
55
+
40
56
  var UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", LOWER = "abcdefghijklmnopqrstuvwxyz", DIGITS = "0123456789";
41
57
  var B64URL_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "-_");
42
58
  var B64_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "+/");
@@ -47,7 +63,7 @@ var HEX_ALPHABET = _alphabet(DIGITS + "abcdef" + "ABCDEF");
47
63
  // bound rejects it, so no lookup can read undefined off the end of the table.
48
64
  function _inAlphabet(text, table) {
49
65
  for (var i = 0; i < text.length; i++) {
50
- var c = text.charCodeAt(i);
66
+ var c = _charCodeAt(text, i);
51
67
  if (c > 127 || table[c] !== 1) return false;
52
68
  }
53
69
  return true;
@@ -60,10 +76,10 @@ function _capBefore(nChars, perByteChars, maxBytes, E, code, label) {
60
76
  // The cap is an authoring input: a NaN / fractional / negative maxBytes makes
61
77
  // the comparison below always false -- the cap silently disabled. Config-time
62
78
  // TypeError instead (null/undefined stays the documented uncapped mode).
63
- if (!Number.isInteger(maxBytes) || maxBytes < 0) {
79
+ if (!_isInteger(maxBytes) || maxBytes < 0) {
64
80
  throw new TypeError("guard.encoding: maxBytes must be a non-negative integer or null");
65
81
  }
66
- if (Math.floor(nChars / perByteChars) > maxBytes) {
82
+ if (_floor(nChars / perByteChars) > maxBytes) {
67
83
  throw E(code, label + " exceeds the maximum decoded size of " + maxBytes + " bytes");
68
84
  }
69
85
  }
@@ -76,8 +92,8 @@ function base64url(text, maxBytes, E, code, label) {
76
92
  if (!_inAlphabet(text, B64URL_ALPHABET)) throw E(code, label + " is not base64url (padding or a non-alphabet character)");
77
93
  if (text.length % 4 === 1) throw E(code, label + " has an impossible base64url length");
78
94
  _capBefore(text.length * 3, 4, maxBytes, E, code, label);
79
- var buf = Buffer.from(text, "base64url");
80
- if (buf.toString("base64url") !== text) throw E(code, label + " is not canonical base64url");
95
+ var buf = _bufferFrom(text,"base64url");
96
+ if (_toString(buf, "base64url") !== text) throw E(code, label + " is not canonical base64url");
81
97
  return buf;
82
98
  }
83
99
 
@@ -90,12 +106,12 @@ function base64(text, maxBytes, E, code, label) {
90
106
  // the body is measured first and the alphabet applies to what remains. An "="
91
107
  // anywhere else leaves a non-alphabet character in the body and rejects there.
92
108
  var pad = 0;
93
- while (pad < 2 && text.length > pad && text.charCodeAt(text.length - 1 - pad) === 0x3d) pad++;
94
- if (!_inAlphabet(text.slice(0, text.length - pad), B64_ALPHABET)) throw E(code, label + " is not base64 (a non-alphabet character)");
109
+ while (pad < 2 && text.length > pad && _charCodeAt(text, text.length - 1 - pad) === 0x3d) pad++;
110
+ if (!_inAlphabet(_strSlice(text, 0, text.length - pad), B64_ALPHABET)) throw E(code, label + " is not base64 (a non-alphabet character)");
95
111
  if (text.length % 4 !== 0) throw E(code, label + " must be whole 4-character base64 groups (RFC 4648 sec. 3.5)");
96
112
  _capBefore(text.length * 3, 4, maxBytes, E, code, label);
97
- var buf = Buffer.from(text, "base64");
98
- if (buf.toString("base64") !== text) throw E(code, label + " is not canonical base64 (RFC 4648 sec. 3.5)");
113
+ var buf = _bufferFrom(text,"base64");
114
+ if (_toString(buf, "base64") !== text) throw E(code, label + " is not canonical base64 (RFC 4648 sec. 3.5)");
99
115
  return buf;
100
116
  }
101
117
 
@@ -111,8 +127,8 @@ function hex(text, maxBytes, E, code, label) {
111
127
  if (!_inAlphabet(text, HEX_ALPHABET)) throw E(code, label + " is not hexadecimal");
112
128
  if (text.length % 2 !== 0) throw E(code, label + " must have an even number of hex digits");
113
129
  _capBefore(text.length, 2, maxBytes, E, code, label);
114
- var buf = Buffer.from(text, "hex");
115
- if (buf.toString("hex") !== text.toLowerCase()) throw E(code, label + " is not canonical hexadecimal");
130
+ var buf = _bufferFrom(text,"hex");
131
+ if (_toString(buf, "hex") !== _toLowerCase(text)) throw E(code, label + " is not canonical hexadecimal");
116
132
  return buf;
117
133
  }
118
134
 
@@ -18,6 +18,17 @@
18
18
  // reintroduce the injection.
19
19
 
20
20
  var C = require("./constants");
21
+ // Taken at load. `byteLength` is what the RFC 5322 line limit is measured with, so a replacement
22
+ // decides whether an over-long header line is refused. See guard-intrinsic for the whole set.
23
+ var _byteLength = require("./guard-intrinsic").byteLength;
24
+ // The character-code read the RFC 5322 ftext and CR/LF/NUL checks are built on. One that never
25
+ // reports a control byte is a header-injection defense that inspects nothing.
26
+ var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
27
+ var _stringify = require("./guard-intrinsic").stringify;
28
+ // The conversion that produces the text this guard then scans for CR and LF. A replacement returns
29
+ // a clean string for a value carrying a line break, so the injection scan runs on text that is not
30
+ // what gets emitted.
31
+ var _String = require("./guard-intrinsic").String;
21
32
 
22
33
  // assertField(name, value, E, code) -> value | throws new E(code, ...)
23
34
  // name: RFC 5322 ftext, printable ASCII [0x21, 0x7e] except ':' (0x3a), non-empty
@@ -33,18 +44,18 @@ var C = require("./constants");
33
44
  function assertField(name, value, E, code) {
34
45
  if (typeof name !== "string" || name.length === 0) throw new E(code, "a header field name must be a non-empty string");
35
46
  for (var i = 0; i < name.length; i++) {
36
- var nc = name.charCodeAt(i);
37
- if (nc < 0x21 || nc > 0x7e || nc === 0x3a) throw new E(code, "a header field name must be RFC 5322 ftext (printable ASCII, no space / ':' / control): " + JSON.stringify(name));
47
+ var nc = _charCodeAt(name, i);
48
+ if (nc < 0x21 || nc > 0x7e || nc === 0x3a) throw new E(code, "a header field name must be RFC 5322 ftext (printable ASCII, no space / ':' / control): " + _stringify(name));
38
49
  }
39
- var v = typeof value === "string" ? value : String(value);
50
+ var v = typeof value === "string" ? value : _String(value);
40
51
  for (var j = 0; j < v.length; j++) {
41
- var vc = v.charCodeAt(j);
42
- if (vc === 0x00 || vc === 0x0d || vc === 0x0a) throw new E(code, "a header field value must not contain CR / LF / NUL (header injection) in " + JSON.stringify(name));
52
+ var vc = _charCodeAt(v, j);
53
+ if (vc === 0x00 || vc === 0x0d || vc === 0x0a) throw new E(code, "a header field value must not contain CR / LF / NUL (header injection) in " + _stringify(name));
43
54
  }
44
55
  // RFC 5322 sec. 2.1.1: the serialized "Name: value" line (UTF-8 octets, excluding CRLF) must not exceed
45
56
  // the line limit, or a relay may re-fold it and silently change the signed bytes of a protected header.
46
- if (Buffer.byteLength(name, "utf8") + 2 + Buffer.byteLength(v, "utf8") > C.LIMITS.HEADER_LINE_MAX_OCTETS) {
47
- throw new E(code, "a header field line exceeds RFC 5322's " + C.LIMITS.HEADER_LINE_MAX_OCTETS + "-octet limit: " + JSON.stringify(name));
57
+ if (_byteLength(name, "utf8") + 2 + _byteLength(v, "utf8") > C.LIMITS.HEADER_LINE_MAX_OCTETS) {
58
+ throw new E(code, "a header field line exceeds RFC 5322's " + C.LIMITS.HEADER_LINE_MAX_OCTETS + "-octet limit: " + _stringify(name));
48
59
  }
49
60
  return v;
50
61
  }