@blamejs/pki 0.6.13 → 0.6.15
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 +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-compress.js
CHANGED
|
@@ -1,97 +1,32 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// consumers whose decompression composes this guard (pki.cms.decompress,
|
|
7
|
-
// pki.tls.decompressCertificate).
|
|
8
|
-
//
|
|
9
|
-
// guard-compress -- bounded, non-malleable decompression of an untrusted stream.
|
|
10
|
-
// Node's zlib / brotli / zstd decompressors are by default both UNBOUNDED and
|
|
11
|
-
// PERMISSIVE, and each is a distinct vulnerability class:
|
|
12
|
-
//
|
|
13
|
-
// * Unbounded output (CWE-409, decompression bomb) -- a few hundred bytes
|
|
14
|
-
// expand to gigabytes. `maxOutputLength` refuses the moment the output WOULD
|
|
15
|
-
// exceed the bound; it does not allocate the whole output and then measure.
|
|
16
|
-
// So the cap has to be applied AT the call and can never be a check on the
|
|
17
|
-
// returned value.
|
|
18
|
-
// * Trailing input silently ignored (CWE-20, canonicalization malleability) --
|
|
19
|
-
// all three stop at the end of the first complete frame and discard whatever
|
|
20
|
-
// follows, so a caller may append arbitrary bytes, or a second entire frame,
|
|
21
|
-
// and still recover byte-identical output. One content would then have
|
|
22
|
-
// unboundedly many encodings, and a digest over the compressed object would
|
|
23
|
-
// stop identifying what it decompresses to.
|
|
24
|
-
//
|
|
25
|
-
// Both defenses are applied here, once, for every algorithm, so a new consumer
|
|
26
|
-
// cannot pick up one and miss the other. Verified uniform across zlib, brotli
|
|
27
|
-
// and zstd: each reports consumed input as `engine.bytesWritten` under
|
|
28
|
-
// `info: true`, each raises ERR_BUFFER_TOO_LARGE on a cap breach, and each
|
|
29
|
-
// accepts trailing bytes when left unchecked.
|
|
30
|
-
//
|
|
31
|
-
// E is the caller's (code, message, cause) typed-error factory, never an error
|
|
32
|
-
// class. Passing a class here crashes on the error path ("class cannot be invoked
|
|
33
|
-
// without new"), which converts a rejection into a fail-open. `codes` names the
|
|
34
|
-
// two verdicts a caller distinguishes: `tooLarge` when the output would exceed
|
|
35
|
-
// the cap, `failed` for every other fault. Every non-cap fault collapses to one
|
|
36
|
-
// code deliberately -- a per-errno surface is attack telemetry.
|
|
4
|
+
// @internal
|
|
37
5
|
|
|
38
6
|
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
7
|
var _intrinsic = require("./guard-intrinsic");
|
|
43
8
|
var _bufferFrom = _intrinsic.bufferFrom;
|
|
44
9
|
var _isBuffer = _intrinsic.isBuffer;
|
|
45
10
|
var _isInteger = _intrinsic.isInteger;
|
|
46
11
|
var _objectKeys = _intrinsic.keys;
|
|
47
12
|
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
13
|
var _compare = _intrinsic.compare;
|
|
52
14
|
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
15
|
var _subarray = _intrinsic.uncurry(Uint8Array.prototype.subarray);
|
|
57
16
|
var _hasOwn = _intrinsic.hasOwn;
|
|
58
17
|
|
|
59
|
-
// The decompressor per algorithm: a registry, so a new algorithm is a row rather
|
|
60
|
-
// than a branch and an unrecognized name fails closed.
|
|
61
18
|
var DECOMPRESS = {
|
|
62
|
-
zlib: zlib.inflateSync,
|
|
63
|
-
brotli: zlib.brotliDecompressSync,
|
|
64
|
-
zstd: zlib.zstdDecompressSync,
|
|
19
|
+
zlib: zlib.inflateSync,
|
|
20
|
+
brotli: zlib.brotliDecompressSync,
|
|
21
|
+
zstd: zlib.zstdDecompressSync,
|
|
65
22
|
};
|
|
66
23
|
|
|
67
|
-
// The compressors, used only by the truncation probe below. Producing a compressed stream is
|
|
68
|
-
// not the guarded shape; decompressing an untrusted one is.
|
|
69
24
|
var _PROBE_COMPRESS = {
|
|
70
25
|
zlib: zlib.deflateSync,
|
|
71
26
|
brotli: zlib.brotliCompressSync,
|
|
72
27
|
zstd: zlib.zstdCompressSync,
|
|
73
28
|
};
|
|
74
29
|
|
|
75
|
-
// A decompressor must REPORT a frame it could not finish. Not all of them do: on some supported
|
|
76
|
-
// runtimes the zstd binding returns a SHORT (often empty) result for a truncated frame instead
|
|
77
|
-
// of faulting, and reports the entire input as consumed, so neither the returned bytes nor the
|
|
78
|
-
// consumed-length check below can see that the frame was cut. That is a silent truncation: a peer
|
|
79
|
-
// strips a frame's tail and the receiver processes a prefix as though it were the whole message.
|
|
80
|
-
//
|
|
81
|
-
// An algorithm whose runtime cannot report this cannot be decompressed safely here, so it is
|
|
82
|
-
// dropped from the advertised set at load instead of accepted with a defect. Consumers build
|
|
83
|
-
// their wire registries by intersecting with algorithms(), so a protocol simply does not offer
|
|
84
|
-
// or accept it, and it returns on its own once the runtime reports truncation.
|
|
85
|
-
// Nothing here is enabled by an exception alone: an algorithm qualifies only by positive proof
|
|
86
|
-
// that a whole frame round-trips and that a cut frame is refused. Every other outcome (a
|
|
87
|
-
// missing function, a compressor fault, a wrong round-trip, an unrelated error) leaves it out.
|
|
88
|
-
//
|
|
89
|
-
// Coverage residual: the early-return branches are reachable only on a runtime whose own
|
|
90
|
-
// compression library is broken (a missing export, a compressor that throws on 46 ASCII bytes, a
|
|
91
|
-
// round-trip that does not reproduce its input). The tables are captured at module load, so those
|
|
92
|
-
// states cannot be induced from a test without replacing node:zlib beforehand, and forcing them
|
|
93
|
-
// would assert nothing about the property this function exists to establish. They stay because
|
|
94
|
-
// their absence is what would make a broken runtime silently qualify an algorithm.
|
|
95
30
|
function _reportsTruncation(name) {
|
|
96
31
|
var compress = _PROBE_COMPRESS[name];
|
|
97
32
|
var decompress = DECOMPRESS[name];
|
|
@@ -100,53 +35,32 @@ function _reportsTruncation(name) {
|
|
|
100
35
|
var frame, whole;
|
|
101
36
|
try { frame = compress(sample); }
|
|
102
37
|
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; }
|
|
103
|
-
// A cut of 2 removes the frame's end marker; the sample is long enough that what remains is
|
|
104
|
-
// still a plausible frame head and not nothing at all.
|
|
105
38
|
if (!_isBuffer(frame) || _sizeOf(frame) < 4) return false;
|
|
106
39
|
try { whole = decompress(frame, { maxOutputLength: 4096 }); }
|
|
107
40
|
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; }
|
|
108
41
|
if (!_isBuffer(whole) || _compare(whole, sample) !== 0) return false;
|
|
109
|
-
// The cut frame must be refused. A returned value of any length, including the empty buffer
|
|
110
|
-
// some bindings hand back, means a truncation would pass through unseen.
|
|
111
42
|
var cutAccepted = false;
|
|
112
43
|
try {
|
|
113
44
|
decompress(_subarray(frame, 0, _sizeOf(frame) - 2), { maxOutputLength: 4096 });
|
|
114
45
|
cutAccepted = true;
|
|
115
|
-
} catch (_e3) {
|
|
46
|
+
} catch (_e3) { }
|
|
116
47
|
return !cutAccepted;
|
|
117
48
|
}
|
|
118
49
|
|
|
119
50
|
var SAFE = {};
|
|
120
51
|
_intrinsic.forEach(_objectKeys(DECOMPRESS), function (n) { if (_reportsTruncation(n)) SAFE[n] = true; });
|
|
121
52
|
|
|
122
|
-
// bounded(algorithm, stream, cap, E, codes, label) -> Buffer. Decompress `stream`
|
|
123
|
-
// under `algorithm`, refusing any output that would exceed `cap` bytes and any
|
|
124
|
-
// input carrying bytes past the end of the compressed frame.
|
|
125
|
-
// The shape is the DECOMPRESSOR CALL ITSELF, not the presence of a cap: an
|
|
126
|
-
// uncapped call is precisely the dangerous form, so keying on `maxOutputLength`
|
|
127
|
-
// would fire on the safe re-inline and stay silent on the unsafe one. Every
|
|
128
|
-
// decompression in lib/ therefore belongs to this module or routes through it.
|
|
129
53
|
// @enforced-by guard-shape-reinlined
|
|
130
54
|
// @guard-shape \bzlib\.(inflate|brotliDecompress|zstdDecompress)Sync\b
|
|
131
55
|
// @guard-via \bguard\.compress\.bounded\s*\(
|
|
132
56
|
function bounded(algorithm, stream, cap, E, codes, label) {
|
|
133
57
|
var decompress = DECOMPRESS[algorithm];
|
|
134
|
-
// An unknown algorithm -- or one this runtime cannot decompress safely -- is an authoring
|
|
135
|
-
// fault, not untrusted input: a caller resolves a wire value to a name against algorithms()
|
|
136
|
-
// and rejects one it does not find, before reaching here. Arriving with a name outside that
|
|
137
|
-
// set means the consumer skipped the step, so it faults loudly and never silently.
|
|
138
58
|
if (!_hasOwn(SAFE, algorithm) || !decompress) {
|
|
139
59
|
throw new TypeError("guard.compress.bounded: unknown or unsafe algorithm " + _stringify(algorithm));
|
|
140
60
|
}
|
|
141
|
-
// The byte-source normalization is guard.bytes.view's job and belongs at the
|
|
142
|
-
// consumer's boundary; reaching here with anything else means it was skipped.
|
|
143
61
|
if (!_isBuffer(stream)) {
|
|
144
62
|
throw new TypeError("guard.compress.bounded: stream must be a Buffer (route the input through guard.bytes.view first)");
|
|
145
63
|
}
|
|
146
|
-
// The ceiling is an authoring input: an undefined / NaN / fractional / negative
|
|
147
|
-
// cap would silently disable the bomb defense. It must also be at least 1 --
|
|
148
|
-
// node rejects maxOutputLength 0 with a different fault than a cap breach, so a
|
|
149
|
-
// zero cap would surface as a malformed-stream verdict instead of a config error.
|
|
150
64
|
if (!_isInteger(cap) || cap < 1) {
|
|
151
65
|
throw new TypeError("guard.compress.bounded: cap must be a positive integer");
|
|
152
66
|
}
|
|
@@ -160,9 +74,6 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
160
74
|
}
|
|
161
75
|
throw E(codes.failed, what + " could not be decompressed", e);
|
|
162
76
|
}
|
|
163
|
-
// `bytesWritten` is the count of INPUT bytes the engine consumed. A frame that
|
|
164
|
-
// ends before the input does means the remainder was ignored, which is the
|
|
165
|
-
// malleability above, so the whole input must be exactly one frame.
|
|
166
77
|
var consumed = res.engine.bytesWritten;
|
|
167
78
|
var streamLen = _sizeOf(stream);
|
|
168
79
|
if (consumed !== streamLen) {
|
|
@@ -172,15 +83,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
172
83
|
return res.buffer;
|
|
173
84
|
}
|
|
174
85
|
|
|
175
|
-
// The algorithm names this guard can decompress safely on this runtime: the registry
|
|
176
|
-
// minus any whose decompressor cannot report an unfinished frame. A consumer intersects
|
|
177
|
-
// its protocol's algorithm registry with this, so it never advertises or accepts something
|
|
178
|
-
// it could not refuse a truncation of.
|
|
179
86
|
// @enforced-by behavioral -- a read-only list of the safe registry's keys has no
|
|
180
|
-
// rename-proof code shape to re-inline; the guard's reject path is the contract,
|
|
181
|
-
// and a consumer offering an algorithm this list omits fails closed at `bounded`.
|
|
182
87
|
function algorithms() { return _objectKeys(SAFE); }
|
|
183
88
|
|
|
184
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
185
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
186
89
|
module.exports = _intrinsic.freeze({ bounded: bounded, algorithms: algorithms });
|
package/lib/guard-crypto.js
CHANGED
|
@@ -1,60 +1,23 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// verify paths whose comparison composes this guard (pki.merkle,
|
|
7
|
-
// pki.webcrypto.subtle.verify).
|
|
8
|
-
//
|
|
9
|
-
// guard-crypto -- fail-closed cryptographic-invariant guards. Enforced choke
|
|
10
|
-
// point: a codebase-patterns detector requires every constant-time byte
|
|
11
|
-
// comparison to route through here, so no verify path re-implements it with a
|
|
12
|
-
// timing leak.
|
|
13
|
-
//
|
|
14
|
-
// Defends the timing side-channel class (CWE-208 observable timing discrepancy;
|
|
15
|
-
// the Lucky13 / MAC-forgery family). A secret-dependent byte comparison with an
|
|
16
|
-
// early exit (=== or a short-circuited &&) leaks the position of the first
|
|
17
|
-
// differing byte, letting an attacker recover a MAC / tag / tree root
|
|
18
|
-
// byte-by-byte. constantTimeEqual runs the whole comparison: a public length
|
|
19
|
-
// gate (lengths are not secret; crypto.timingSafeEqual RangeErrors on unequal
|
|
20
|
-
// lengths, so the gate must precede it) then a single timingSafeEqual over the
|
|
21
|
-
// equal-length buffers -- both operands are always read, no data-dependent
|
|
22
|
-
// branch. A length mismatch is an honest false, never a throw.
|
|
23
|
-
//
|
|
24
|
-
// Also defends the signature-malleability class (CWE-347 / CWE-20): a signature /
|
|
25
|
-
// MAC / digest BIT STRING with a non-zero unused-bits count is not whole octets,
|
|
26
|
-
// so trailing sub-octet bits let two encodings carry "one" signature. Every RFC
|
|
27
|
-
// 9481/4211/6960/5280 signature output is a whole number of octets, so a value
|
|
28
|
-
// with unusedBits !== 0 fails closed before its bytes reach a verifier.
|
|
4
|
+
// @internal
|
|
29
5
|
|
|
30
6
|
var nodeCrypto = require("node:crypto");
|
|
31
7
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
32
8
|
|
|
33
|
-
// constantTimeEqual(a, b) -> boolean. Length-checked, then constant-time over
|
|
34
|
-
// equal lengths. a and b are Buffers.
|
|
35
9
|
// @enforced-by guard-shape-reinlined
|
|
36
10
|
// @guard-shape \.timingSafeEqual\s*\(
|
|
37
11
|
function constantTimeEqual(a, b) {
|
|
38
12
|
return a.length === b.length && nodeCrypto.timingSafeEqual(a, b);
|
|
39
13
|
}
|
|
40
14
|
|
|
41
|
-
// isOctetAligned(bitString) -> boolean. True when a decoded BIT STRING (a
|
|
42
|
-
// { bytes, unusedBits } node) is whole octets. For the never-throw verify paths
|
|
43
|
-
// (a boolean/continue verdict) so a malleable signature is skipped, not honored.
|
|
44
15
|
// @enforced-by behavioral -- shares the octet-alignment rule below; a bare
|
|
45
|
-
// .unusedBits check has legitimate non-signature siblings, so the RED vectors
|
|
46
|
-
// (a non-octet-aligned signature is skipped / rejected) are the guard.
|
|
47
16
|
function isOctetAligned(bitString) {
|
|
48
17
|
return !!bitString && bitString.unusedBits === 0;
|
|
49
18
|
}
|
|
50
19
|
|
|
51
|
-
// assertOctetAligned(bitString, E, code, label) -> bitString | throws E(code,...).
|
|
52
|
-
// The throwing form for a parse boundary: a signature / MAC / digest BIT STRING
|
|
53
|
-
// MUST be octet-aligned, else the malleable value is rejected fail-closed.
|
|
54
20
|
// @enforced-by behavioral -- .unusedBits !== 0 is a per-field RFC rule, and the
|
|
55
|
-
// same token legitimately tests a DIFFERENT invariant (NamedBitList minimality,
|
|
56
|
-
// schema-engine assertMinimalNamedBits), so a blanket detector would false-fire;
|
|
57
|
-
// the RED vectors (a non-octet-aligned signature/digest rejects) are the guard.
|
|
58
21
|
function assertOctetAligned(bitString, E, code, label) {
|
|
59
22
|
if (!isOctetAligned(bitString)) {
|
|
60
23
|
throw E(code, (label || "signature") + " BIT STRING must be octet-aligned (0 unused bits)");
|
|
@@ -62,6 +25,4 @@ function assertOctetAligned(bitString, E, code, label) {
|
|
|
62
25
|
return bitString;
|
|
63
26
|
}
|
|
64
27
|
|
|
65
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
66
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
67
28
|
module.exports = _freeze({ constantTimeEqual: constantTimeEqual, isOctetAligned: isOctetAligned, assertOctetAligned: assertOctetAligned });
|
package/lib/guard-der.js
CHANGED
|
@@ -1,73 +1,24 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the consumers
|
|
6
|
-
// whose encoding integrity composes this guard (pki.schema.c509 reconstruct sites,
|
|
7
|
-
// pki.x509.sign and its sibling builders on any subjectAltName otherName value).
|
|
8
|
-
//
|
|
9
|
-
// guard-der: the single choke point for "these raw bytes are exactly one strictly-valid
|
|
10
|
-
// DER element", for every site that splices caller-supplied ANY bytes verbatim into
|
|
11
|
-
// something it then signs or reconstructs.
|
|
12
|
-
//
|
|
13
|
-
// The bug class is framing mistaken for validation. asn1.decode checks TLV framing and
|
|
14
|
-
// rejects trailing data, and stopping there feels sufficient. It is not: framing accepts
|
|
15
|
-
// a BOOLEAN whose content octet is 0x01 where DER requires 0xFF, a NumericString holding
|
|
16
|
-
// "@", and a SET whose members sit in no canonical order. None of those is DER. Spliced
|
|
17
|
-
// into a certificate and signed, the issuer emits a structure that strict relying parties
|
|
18
|
-
// reject, under a real signature, while the producing verb reported success (CWE-20).
|
|
19
|
-
//
|
|
20
|
-
// The rule is validate-or-refuse. A universal primitive runs through its strict content
|
|
21
|
-
// reader, and a universal type with no reader here is REFUSED, so the table is exhaustive
|
|
22
|
-
// by refusal and adding a type is a deliberate act. A non-universal element (a
|
|
23
|
-
// legitimately context- or application-tagged ANY) passes on its framing because no
|
|
24
|
-
// content rule is knowable for it, and its constructed children are still walked.
|
|
25
|
-
//
|
|
26
|
-
// Every entry point takes the CALLER's error factory and code, so a c509 splice reports in
|
|
27
|
-
// the c509 domain and a certificate builder in the x509 domain.
|
|
28
|
-
// asn1-der composes this guard family, so requiring the codec at module scope would read its
|
|
29
|
-
// exports mid-initialization and see an empty object. The require and the table it builds are
|
|
30
|
-
// therefore both deferred to first use -- the documented circular-load exception to top-of-file
|
|
31
|
-
// requires. Guards sit BELOW the codec in the dependency order; this is the one place that
|
|
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.
|
|
4
|
+
// @internal
|
|
35
5
|
var _compare = require("./guard-intrinsic").compare;
|
|
36
6
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
37
7
|
var _forEach = require("./guard-intrinsic").forEach;
|
|
38
|
-
// The emptiness gate below decides whether bytes reach the decoder at all, and on a byte view
|
|
39
|
-
// `length` is a replaceable accessor: one answering non-zero for an empty buffer sends nothing to
|
|
40
|
-
// asn1.decode as though it were an element.
|
|
41
8
|
var _sizeOf = require("./guard-intrinsic").sizeOf;
|
|
42
9
|
var _create = require("./guard-intrinsic").create;
|
|
43
10
|
|
|
44
11
|
var asn1 = null;
|
|
45
12
|
function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
|
|
46
13
|
|
|
47
|
-
// The strict content reader per universal primitive tag. A tag absent from this table has no
|
|
48
|
-
// validator here and is refused by `element` below -- that refusal is the point.
|
|
49
14
|
var VALUE_READERS = null;
|
|
50
15
|
function _readers() {
|
|
51
16
|
if (VALUE_READERS !== null) return VALUE_READERS;
|
|
52
17
|
var asn1 = _asn1();
|
|
53
|
-
// Null-prototype: this table IS the allowlist of universal tags with a strict content reader, and
|
|
54
|
-
// a tag it does not carry is refused. Built on a `{}`, an inherited numeric property supplies a
|
|
55
|
-
// reader for a tag that was deliberately left out, and the refusal that is the point stops.
|
|
56
18
|
var m = _create(null), R = asn1.read, T = asn1.TAGS;
|
|
57
|
-
// ENUMERATED shares INTEGER's content rules and is NOT constrained to non-negative
|
|
58
|
-
// values. X.680 (02/2021) sec. 20.2 requires each NamedNumber's SignedNumber to be
|
|
59
|
-
// distinct, and sec. 19 defines SignedNumber ::= number | "-" number; the non-negativity
|
|
60
|
-
// in sec. 20.3 governs only an EnumerationItem written as a bare identifier, which is
|
|
61
|
-
// auto-assigned. `ENUMERATED { lowPriority(-1), normal(0) }` is well-formed, so rejecting
|
|
62
|
-
// a negative here would refuse valid input. This guard validates the ENCODING; which
|
|
63
|
-
// values a particular ENUMERATED admits lives in a type definition an opaque ANY does
|
|
64
|
-
// not carry.
|
|
65
19
|
m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
|
|
66
20
|
m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
|
|
67
21
|
m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
|
|
68
|
-
// NumericString reads through its own reader: it is not a DirectoryString type, and routing
|
|
69
|
-
// it through read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity
|
|
70
|
-
// class (see asn1-der.js).
|
|
71
22
|
m[T.NUMERIC_STRING] = R.numericString;
|
|
72
23
|
_forEach([T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
|
|
73
24
|
T.BMP_STRING, T.UNIVERSAL_STRING], function (t) { m[t] = R.string; });
|
|
@@ -75,36 +26,12 @@ function _readers() {
|
|
|
75
26
|
return VALUE_READERS;
|
|
76
27
|
}
|
|
77
28
|
|
|
78
|
-
// A universal SET's required member order depends on a type the ANY does not carry: X.690
|
|
79
|
-
// sec. 11.6 orders a SET OF by the members' full encodings, while a structured SET is ordered
|
|
80
|
-
// by TAG (X.680 sec. 8.6), and the two differ whenever the constructed bit does (a SEQUENCE
|
|
81
|
-
// member, tag 16, sorts before a PrintableString, tag 19, by tag but after it by octets). A
|
|
82
|
-
// structured SET cannot repeat a tag, so a repeated tag proves SET OF and the octet rule binds;
|
|
83
|
-
// with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
|
|
84
|
-
// (rejecting only what is non-canonical under both readings: sound in both directions, never a
|
|
85
|
-
// guess).
|
|
86
|
-
//
|
|
87
|
-
// KNOWN LIMITATION, and the two consumers do not weigh it identically. The either-reading rule
|
|
88
|
-
// admits a SET OF that is tag-ordered but not octet-ordered, which is not canonical DER. On a
|
|
89
|
-
// RECONSTRUCT path the input already exists and the alternative would refuse a valid structured
|
|
90
|
-
// SET, so accepting is the sound direction. On a SIGNING path the caller composes the bytes and
|
|
91
|
-
// could be held to octet order, so the same permissiveness lets a signer emit a non-canonical
|
|
92
|
-
// SET under a real signature. Tightening it for signers alone would mean a mode flag on a
|
|
93
|
-
// security posture, which this family avoids, and tightening it for both would reject valid
|
|
94
|
-
// structured SETs that c509 round-trips today. The rule is therefore unchanged and the cost is
|
|
95
|
-
// written down rather than discovered later.
|
|
96
|
-
// Null-prototype: the rank decides canonical SET order, so an inherited property answering for an
|
|
97
|
-
// unrecognized tag class would rank it rather than leave it unranked, and a SET in no canonical
|
|
98
|
-
// order would compare as ordered.
|
|
99
29
|
var TAG_CLASS_RANK = _create(null);
|
|
100
30
|
TAG_CLASS_RANK.universal = 0;
|
|
101
31
|
TAG_CLASS_RANK.application = 1;
|
|
102
32
|
TAG_CLASS_RANK.context = 2;
|
|
103
33
|
TAG_CLASS_RANK["private"] = 3;
|
|
104
34
|
function setOrderOk(kids) {
|
|
105
|
-
// A null-prototype accumulator: on a `{}` a planted `Object.prototype["universal:16"]` answers
|
|
106
|
-
// this membership test for a tag the SET never carried, which reports a duplicate that is not
|
|
107
|
-
// there and selects the wrong canonical-order rule for the whole SET.
|
|
108
35
|
var i, dup = false, seen = _create(null);
|
|
109
36
|
for (i = 0; i < kids.length; i++) {
|
|
110
37
|
var key = kids[i].tagClass + ":" + kids[i].tagNumber;
|
|
@@ -114,23 +41,12 @@ function setOrderOk(kids) {
|
|
|
114
41
|
var octetAsc = true, tagAsc = true;
|
|
115
42
|
for (i = 1; i < kids.length; i++) {
|
|
116
43
|
if (_compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
|
|
117
|
-
// Tag order ranks by CLASS first (universal < application < context < private, X.680
|
|
118
|
-
// sec. 8.6), then by tag number. Compare the class's NUMBER: the names do not sort in
|
|
119
|
-
// class order.
|
|
120
44
|
var pc = TAG_CLASS_RANK[kids[i - 1].tagClass], cc = TAG_CLASS_RANK[kids[i].tagClass];
|
|
121
45
|
if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
|
|
122
46
|
}
|
|
123
47
|
return dup ? octetAsc : (octetAsc || tagAsc);
|
|
124
48
|
}
|
|
125
49
|
|
|
126
|
-
// element(node, E, code, label). Strict-validate an already-decoded DER element at ANY depth.
|
|
127
|
-
// Rejects the reserved EOC tag 0; runs a universal primitive through its strict content reader
|
|
128
|
-
// (or refuses a type with none); recurses into a constructed element's children; holds a
|
|
129
|
-
// universal SET to a canonical order. Only SEQUENCE and SET are accepted as universal
|
|
130
|
-
// CONSTRUCTED types -- an EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory components
|
|
131
|
-
// this gate cannot verify (the degenerate empty form is not a valid encoding of any of them),
|
|
132
|
-
// so it is refused for the same reason an unvalidatable primitive is.
|
|
133
|
-
//
|
|
134
50
|
// @enforced-by guard-shape-reinlined
|
|
135
51
|
// @guard-shape _ANY_VALUE_READERS\[
|
|
136
52
|
function element(node, E, code, label) {
|
|
@@ -142,7 +58,7 @@ function element(node, E, code, label) {
|
|
|
142
58
|
if (node.tagClass === "universal" && node.tagNumber !== T.SEQUENCE && node.tagNumber !== T.SET) {
|
|
143
59
|
throw E(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
|
|
144
60
|
}
|
|
145
|
-
var kids = node.children;
|
|
61
|
+
var kids = node.children;
|
|
146
62
|
for (var i = 0; i < kids.length; i++) element(kids[i], E, code, label);
|
|
147
63
|
if (node.tagClass === "universal" && node.tagNumber === T.SET && !setOrderOk(kids)) {
|
|
148
64
|
throw E(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
|
|
@@ -157,11 +73,6 @@ function element(node, E, code, label) {
|
|
|
157
73
|
}
|
|
158
74
|
}
|
|
159
75
|
|
|
160
|
-
// tlv(content, E, code, label) -> content. Raw ANY bytes about to be spliced verbatim must be
|
|
161
|
-
// exactly one non-empty, well-formed AND strictly-valid DER element: framing + no-trailing-data
|
|
162
|
-
// via asn1.decode, then content / structure / SET order via `element`. Returns the bytes so a
|
|
163
|
-
// call site can wrap a splice inline.
|
|
164
|
-
//
|
|
165
76
|
// @enforced-by guard-shape-reinlined
|
|
166
77
|
// @guard-shape must be exactly one well-formed DER element
|
|
167
78
|
function tlv(content, E, code, label) {
|
|
@@ -173,6 +84,4 @@ function tlv(content, E, code, label) {
|
|
|
173
84
|
return content;
|
|
174
85
|
}
|
|
175
86
|
|
|
176
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
177
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
178
87
|
module.exports = _freeze({ element: element, tlv: tlv });
|
package/lib/guard-encoding.js
CHANGED
|
@@ -1,49 +1,14 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// consumers whose textual-encoding integrity composes this guard (pki.jose
|
|
7
|
-
// base64url, pki.schema.* PEM bodies, pki.webcrypto JWK key material, pki.acme
|
|
8
|
-
// / pki.est transfer encodings).
|
|
9
|
-
//
|
|
10
|
-
// guard-encoding -- strict decode of an untrusted base64 / base64url / hex text
|
|
11
|
-
// to bytes. Node's Buffer.from(x, "base64"|"base64url"|"hex") is LENIENT: it
|
|
12
|
-
// silently drops the first invalid character (and everything after a stray one),
|
|
13
|
-
// accepts non-canonical trailing bits, and tolerates missing/extra padding. Two
|
|
14
|
-
// distinct texts can therefore alias one byte string, and a malformed text can
|
|
15
|
-
// decode to a shorter, different value than intended. And it allocates the whole
|
|
16
|
-
// decode before any size is checked. This is CWE-172 (encoding-transformation error) /
|
|
17
|
-
// CWE-20 (canonicalization malleability) + CWE-770 (allocate-before-cap): RFC
|
|
18
|
-
// 4648 sec. 3.5 / sec. 5 and RFC 8555 sec. 6.1 require the UNIQUE canonical
|
|
19
|
-
// encoding. Each decoder here gates the alphabet, rejects an impossible length,
|
|
20
|
-
// enforces the byte cap before the copy, decodes, and verifies canonicality by a
|
|
21
|
-
// re-encode round-trip, so a value that is not the one canonical encoding of
|
|
22
|
-
// its bytes fails closed with the caller's typed error.
|
|
23
|
-
//
|
|
24
|
-
// E is the caller's (code, message) typed-error factory; `code` the frozen
|
|
25
|
-
// domain/reason; `label` the field phrase; `maxBytes` an optional decoded-size
|
|
26
|
-
// cap (null/undefined = uncapped, for a PEM cert body already bounded upstream).
|
|
4
|
+
// @internal
|
|
27
5
|
|
|
28
|
-
// The alphabets are tables, walked one character at a time. Regular expressions
|
|
29
|
-
// would be the obvious alternative, but a guard runs on the most hostile input
|
|
30
|
-
// the toolkit sees, and a pattern engine's cost on a non-matching string is
|
|
31
|
-
// decided by the pattern, which is the one thing a bound cannot be placed on from
|
|
32
|
-
// the outside. A table lookup is one array index per character, so its cost
|
|
33
|
-
// follows the length, which the caller already caps. It is also the more honest
|
|
34
|
-
// statement of the rule: the set of permitted characters is the rule, written out.
|
|
35
6
|
function _alphabet(chars) {
|
|
36
7
|
// 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.
|
|
38
8
|
var t = new Uint8Array(128);
|
|
39
9
|
for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = 1;
|
|
40
10
|
return t;
|
|
41
11
|
}
|
|
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
12
|
var _intrinsic = require("./guard-intrinsic");
|
|
48
13
|
var _bufferFrom = _intrinsic.bufferFrom;
|
|
49
14
|
var _toString = _intrinsic.uncurry(Buffer.prototype.toString);
|
|
@@ -58,9 +23,6 @@ var B64URL_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "-_");
|
|
|
58
23
|
var B64_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "+/");
|
|
59
24
|
var HEX_ALPHABET = _alphabet(DIGITS + "abcdef" + "ABCDEF");
|
|
60
25
|
|
|
61
|
-
// Every character of `text` is in `table`. A code point outside Latin-1's low half
|
|
62
|
-
// (including every astral half) is outside all three alphabets, and the explicit
|
|
63
|
-
// bound rejects it, so no lookup can read undefined off the end of the table.
|
|
64
26
|
function _inAlphabet(text, table) {
|
|
65
27
|
for (var i = 0; i < text.length; i++) {
|
|
66
28
|
var c = _charCodeAt(text, i);
|
|
@@ -69,13 +31,8 @@ function _inAlphabet(text, table) {
|
|
|
69
31
|
return true;
|
|
70
32
|
}
|
|
71
33
|
|
|
72
|
-
// Reject before Buffer.from allocates: a base64 text of N chars decodes to at
|
|
73
|
-
// most floor(N*3/4) bytes, a hex text to N/2.
|
|
74
34
|
function _capBefore(nChars, perByteChars, maxBytes, E, code, label) {
|
|
75
|
-
if (maxBytes == null) return;
|
|
76
|
-
// The cap is an authoring input: a NaN / fractional / negative maxBytes makes
|
|
77
|
-
// the comparison below always false -- the cap silently disabled. Config-time
|
|
78
|
-
// TypeError instead (null/undefined stays the documented uncapped mode).
|
|
35
|
+
if (maxBytes == null) return;
|
|
79
36
|
if (!_isInteger(maxBytes) || maxBytes < 0) {
|
|
80
37
|
throw new TypeError("guard.encoding: maxBytes must be a non-negative integer or null");
|
|
81
38
|
}
|
|
@@ -84,8 +41,6 @@ function _capBefore(nChars, perByteChars, maxBytes, E, code, label) {
|
|
|
84
41
|
}
|
|
85
42
|
}
|
|
86
43
|
|
|
87
|
-
// base64url(text, maxBytes, E, code, label) -> Buffer. Unpadded base64url (RFC
|
|
88
|
-
// 4648 sec. 5 / RFC 7515): no "=" padding, no "+"/"/", canonical final char.
|
|
89
44
|
// @enforced-by base64-decode-not-via-guard
|
|
90
45
|
function base64url(text, maxBytes, E, code, label) {
|
|
91
46
|
if (typeof text !== "string") throw E(code, label + " must be a string");
|
|
@@ -97,14 +52,9 @@ function base64url(text, maxBytes, E, code, label) {
|
|
|
97
52
|
return buf;
|
|
98
53
|
}
|
|
99
54
|
|
|
100
|
-
// base64(text, maxBytes, E, code, label) -> Buffer. Padded base64 (RFC 4648
|
|
101
|
-
// sec. 4): whole 4-character groups, canonical padding + trailing bits.
|
|
102
55
|
// @enforced-by base64-decode-not-via-guard
|
|
103
56
|
function base64(text, maxBytes, E, code, label) {
|
|
104
57
|
if (typeof text !== "string") throw E(code, label + " must be a string");
|
|
105
|
-
// Padding is positional, not alphabetic: at most two "=" and only at the end, so
|
|
106
|
-
// the body is measured first and the alphabet applies to what remains. An "="
|
|
107
|
-
// anywhere else leaves a non-alphabet character in the body and rejects there.
|
|
108
58
|
var pad = 0;
|
|
109
59
|
while (pad < 2 && text.length > pad && _charCodeAt(text, text.length - 1 - pad) === 0x3d) pad++;
|
|
110
60
|
if (!_inAlphabet(_strSlice(text, 0, text.length - pad), B64_ALPHABET)) throw E(code, label + " is not base64 (a non-alphabet character)");
|
|
@@ -115,13 +65,7 @@ function base64(text, maxBytes, E, code, label) {
|
|
|
115
65
|
return buf;
|
|
116
66
|
}
|
|
117
67
|
|
|
118
|
-
// hex(text, maxBytes, E, code, label) -> Buffer. Even-length hex; canonical via
|
|
119
|
-
// a lower-case re-encode round-trip (RFC 4514 sec. 2.4 hex-string values).
|
|
120
68
|
// @enforced-by behavioral -- the hex Buffer.from token has legitimate non-decode
|
|
121
|
-
// siblings in the tree (a hard-coded AES-KW IV constant, an internal
|
|
122
|
-
// parser-emitted canonical serial, an internal BigInt->hex), so a blanket
|
|
123
|
-
// Buffer.from(x,"hex") detector would false-fire; the RED conformance vectors
|
|
124
|
-
// (a non-canonical / odd-length / non-hex #hex attribute value rejects) guard it.
|
|
125
69
|
function hex(text, maxBytes, E, code, label) {
|
|
126
70
|
if (typeof text !== "string") throw E(code, label + " must be a string");
|
|
127
71
|
if (!_inAlphabet(text, HEX_ALPHABET)) throw E(code, label + " is not hexadecimal");
|
|
@@ -132,6 +76,4 @@ function hex(text, maxBytes, E, code, label) {
|
|
|
132
76
|
return buf;
|
|
133
77
|
}
|
|
134
78
|
|
|
135
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
136
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
137
79
|
module.exports = _intrinsic.freeze({ base64url: base64url, base64: base64, hex: hex });
|
package/lib/guard-header.js
CHANGED
|
@@ -1,47 +1,16 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// consumer whose emitted-header integrity composes this guard (pki.smime header
|
|
7
|
-
// protection, RFC 9788).
|
|
8
|
-
//
|
|
9
|
-
// guard-header -- fail-closed header-field integrity for a MIME / RFC 5322 header
|
|
10
|
-
// a composer EMITS. Defends the HEADER INJECTION class (CWE-93): a caller-supplied
|
|
11
|
-
// header field name or value carrying a CR (0x0d) / LF (0x0a) / NUL (0x00) could
|
|
12
|
-
// inject an extra header (a Bcc, an HP-Outer record), split the message, or forge
|
|
13
|
-
// a multipart boundary; a field name outside RFC 5322 ftext could break the
|
|
14
|
-
// "Name: value" header grammar. It also caps the serialized line length (RFC 5322
|
|
15
|
-
// sec. 2.1.1, 998 octets) so an over-long emitted field cannot be re-folded by a
|
|
16
|
-
// relay into different signed bytes. Every field a composer inlines routes through
|
|
17
|
-
// this one guard, so an emission site cannot re-inline a weaker check and
|
|
18
|
-
// reintroduce the injection.
|
|
4
|
+
// @internal
|
|
19
5
|
|
|
20
6
|
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
7
|
var _byteLength = require("./guard-intrinsic").byteLength;
|
|
24
8
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
25
|
-
// The character-code read the RFC 5322 ftext and CR/LF/NUL checks are built on. One that never
|
|
26
|
-
// reports a control byte is a header-injection defense that inspects nothing.
|
|
27
9
|
var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
|
|
28
10
|
var _stringify = require("./guard-intrinsic").stringify;
|
|
29
|
-
// The conversion that produces the text this guard then scans for CR and LF. A replacement returns
|
|
30
|
-
// a clean string for a value carrying a line break, so the injection scan runs on text that is not
|
|
31
|
-
// what gets emitted.
|
|
32
11
|
var _String = require("./guard-intrinsic").String;
|
|
33
12
|
|
|
34
|
-
// assertField(name, value, E, code) -> value | throws new E(code, ...)
|
|
35
|
-
// name: RFC 5322 ftext, printable ASCII [0x21, 0x7e] except ':' (0x3a), non-empty
|
|
36
|
-
// (a space, control byte, or ':' in a field name breaks the "Name: value" grammar).
|
|
37
|
-
// value: reject CR (0x0d), LF (0x0a) and NUL (0x00), the injection bytes; TAB
|
|
38
|
-
// (0x09, folding whitespace) and printable non-ASCII (a UTF-8 header value, RFC
|
|
39
|
-
// 6532) pass. E is the caller's typed-error CLASS, thrown `new E(code, message)`
|
|
40
|
-
// -- the same convention lib/mime.js (this guard's caller) and guard.bytes/text use.
|
|
41
13
|
// @enforced-by behavioral -- the CR/LF/NUL value reject + the ftext name check have
|
|
42
|
-
// no rename-proof code shape distinct from the guard-name control-byte loop / the
|
|
43
|
-
// ASN.1 charset readers; the header-injection RED vectors (a CR / LF / NUL value
|
|
44
|
-
// or a non-ftext field name rejects smime/bad-header) are the guard.
|
|
45
14
|
function assertField(name, value, E, code) {
|
|
46
15
|
if (typeof name !== "string" || name.length === 0) throw new E(code, "a header field name must be a non-empty string");
|
|
47
16
|
for (var i = 0; i < name.length; i++) {
|
|
@@ -53,14 +22,10 @@ function assertField(name, value, E, code) {
|
|
|
53
22
|
var vc = _charCodeAt(v, j);
|
|
54
23
|
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));
|
|
55
24
|
}
|
|
56
|
-
// RFC 5322 sec. 2.1.1: the serialized "Name: value" line (UTF-8 octets, excluding CRLF) must not exceed
|
|
57
|
-
// the line limit, or a relay may re-fold it and silently change the signed bytes of a protected header.
|
|
58
25
|
if (_byteLength(name, "utf8") + 2 + _byteLength(v, "utf8") > C.LIMITS.HEADER_LINE_MAX_OCTETS) {
|
|
59
26
|
throw new E(code, "a header field line exceeds RFC 5322's " + C.LIMITS.HEADER_LINE_MAX_OCTETS + "-octet limit: " + _stringify(name));
|
|
60
27
|
}
|
|
61
28
|
return v;
|
|
62
29
|
}
|
|
63
30
|
|
|
64
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
65
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
66
31
|
module.exports = _freeze({ assertField: assertField });
|