@blamejs/pki 0.5.7 → 0.5.8
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 +405 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +21 -19
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/guard-bytes.js
CHANGED
|
@@ -21,22 +21,22 @@ var async = require("./guard-async");
|
|
|
21
21
|
// validation feeding a CWE-347-style verification-of-nothing). Always re-viewing
|
|
22
22
|
// through Buffer.from(x.buffer, x.byteOffset, x.byteLength) turns the detached
|
|
23
23
|
// read into a typed reject at the boundary. Size / length-field allocation
|
|
24
|
-
// bounds (CWE-770 / CWE-400, the parser-DoS class) are
|
|
24
|
+
// bounds (CWE-770 / CWE-400, the parser-DoS class) are not enforced here. They
|
|
25
25
|
// are per-format (a multi-MB CRL is legitimate, a Merkle proof is tiny), so they
|
|
26
26
|
// live in guard-params and each decoder's own cap.
|
|
27
27
|
|
|
28
28
|
// Choosing between `view` and `snapshot` at a boundary: a byte argument that is read only
|
|
29
|
-
// during the synchronous pass that received it can be re-
|
|
30
|
-
// between the check and the use. One that survives that pass
|
|
31
|
-
// after an await, embedded into output the verb assembles later
|
|
29
|
+
// during the synchronous pass that received it can be re-viewed, because nothing can run
|
|
30
|
+
// between the check and the use. One that survives that pass (stored in state, compared
|
|
31
|
+
// after an await, embedded into output the verb assembles later) must be snapshotted, or the
|
|
32
32
|
// caller can still rewrite the bytes after they were validated. The exceptions are the two
|
|
33
33
|
// places holding a caller's SECRET, where a copy is the worse defect: a password or a private
|
|
34
34
|
// key is re-viewed and left borrowed only when this module also clears the copy it made, so
|
|
35
35
|
// no plaintext duplicate outlives the derivation.
|
|
36
36
|
|
|
37
37
|
// The guard family threads a caller's typed error under two conventions: most guards take a
|
|
38
|
-
// FACTORY (`E(code, message, cause)`, no `new`) because the engines that call them
|
|
39
|
-
// composite-sig, pki-build
|
|
38
|
+
// FACTORY (`E(code, message, cause)`, no `new`) because the engines that call them (sign-scheme,
|
|
39
|
+
// composite-sig, pki-build) carry a bound factory that prefixes the caller's domain onto the
|
|
40
40
|
// code; guard-bytes and guard-header take the error CLASS. A boundary that has only one of the two
|
|
41
41
|
// would otherwise have to hand-roll the re-view to reach this guard at all, which is precisely what
|
|
42
42
|
// the re-inline detector forbids, so accept either: a class (its prototype is an Error) is
|
|
@@ -64,12 +64,12 @@ function view(input, ErrorClass, code, label) {
|
|
|
64
64
|
|
|
65
65
|
// source(input, ErrorClass, code, label) -> Buffer | throws ErrorClass
|
|
66
66
|
// Accepts the full W3C BufferSource (Buffer / TypedArray view / raw ArrayBuffer)
|
|
67
|
-
// -- the WebCrypto input contract. Re-
|
|
68
|
-
// memory
|
|
67
|
+
// -- the WebCrypto input contract. Re-views in every case, sharing the caller's
|
|
68
|
+
// memory instead of taking a copy: `Buffer.from(arrayBuffer)` wraps the backing
|
|
69
69
|
// store, it does not duplicate it. That is what makes this safe to call before a
|
|
70
|
-
// size ceiling has been applied
|
|
70
|
+
// size ceiling has been applied (nothing is materialized), and it is also why a
|
|
71
71
|
// caller that must hold the bytes across an await wants `snapshotSource`, which
|
|
72
|
-
// copies
|
|
72
|
+
// copies. Throws on a detached backing store.
|
|
73
73
|
// @enforced-by guard-shape-reinlined (the re-view shape is declared on view above)
|
|
74
74
|
function source(input, ErrorClass, code, label) {
|
|
75
75
|
var isAb = input instanceof ArrayBuffer;
|
|
@@ -88,15 +88,15 @@ function source(input, ErrorClass, code, label) {
|
|
|
88
88
|
// The parse-then-verify time-of-check/time-of-use defence. A verification entry
|
|
89
89
|
// point that PARSES its input synchronously and then VERIFIES a signature over
|
|
90
90
|
// the same bytes in a later promise turn is reading the caller's memory twice
|
|
91
|
-
// with an await in between. Every byte range the parse surfaced
|
|
92
|
-
// content, the signer set, the values the verdict is built from
|
|
91
|
+
// with an await in between. Every byte range the parse surfaced (the signed
|
|
92
|
+
// content, the signer set, the values the verdict is built from) is a view into
|
|
93
93
|
// that memory, so anything that rewrites the buffer in the gap makes the verdict
|
|
94
94
|
// describe bytes other than the ones the signature was checked against
|
|
95
95
|
// (CWE-367 TOCTOU reaching a CWE-347 wrong-verdict). The window is real without
|
|
96
96
|
// an attacker in the process: a caller recycling a pooled read buffer across
|
|
97
97
|
// concurrent verifies hits it by accident.
|
|
98
98
|
//
|
|
99
|
-
// So take one private copy at the boundary and read
|
|
99
|
+
// So take one private copy at the boundary and read everything from it. `view`
|
|
100
100
|
// re-views and is the right guard where the input is consumed in one synchronous
|
|
101
101
|
// pass; this is its sibling for the boundary that spans an await.
|
|
102
102
|
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any
|
|
@@ -108,8 +108,8 @@ function snapshot(input, ErrorClass, code, label) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// snapshotSource(input, ErrorClass, code, label) -> private Buffer copy | throws
|
|
111
|
-
// The same parse-then-verify defence as `snapshot`, over the
|
|
112
|
-
// (raw ArrayBuffer / DataView / any typed-array view)
|
|
111
|
+
// The same parse-then-verify defence as `snapshot`, over the whole W3C BufferSource
|
|
112
|
+
// (raw ArrayBuffer / DataView / any typed-array view) and not only the
|
|
113
113
|
// Buffer / Uint8Array contract. A parser that accepts a BufferSource must snapshot
|
|
114
114
|
// the same set: leaving an ArrayBuffer or a DataView aliased reopens the window for
|
|
115
115
|
// exactly the inputs that took the wider path in.
|
|
@@ -121,25 +121,25 @@ function snapshotSource(input, ErrorClass, code, label) {
|
|
|
121
121
|
|
|
122
122
|
// snapshotDeep(value, ErrorClass, code, label) -> a private copy of every byte leaf | throws
|
|
123
123
|
//
|
|
124
|
-
// `snapshot` closes the parse-then-verify window for
|
|
125
|
-
// caller-supplied
|
|
124
|
+
// `snapshot` closes the parse-then-verify window for one buffer. This closes it for a whole
|
|
125
|
+
// caller-supplied spec: the object a producing verb is handed and then reads across several
|
|
126
126
|
// promise turns while it resolves a key, hashes, and signs. Every byte the verb ends up
|
|
127
127
|
// encoding reaches it through some field of that object, and the caller still owns all of them,
|
|
128
128
|
// so a spec validated at entry and read again after the first turn need not describe the same
|
|
129
129
|
// certificate, CRL, or request. It is the same defect as an aliased buffer, one level up: the
|
|
130
130
|
// value that passed the checks and the value that gets signed are two different reads.
|
|
131
131
|
//
|
|
132
|
-
// Copies byte views (through `snapshotSource`, so a detached backing store is refused
|
|
133
|
-
//
|
|
134
|
-
// bigint, boolean, a CryptoKey, any class instance
|
|
132
|
+
// Copies byte views (through `snapshotSource`, so a detached backing store is refused instead
|
|
133
|
+
// of copied as empty), arrays, dates, and plain objects. Everything else (a string, number,
|
|
134
|
+
// bigint, boolean, a CryptoKey, any class instance) is passed through by reference, because a
|
|
135
135
|
// non-plain object is a platform or library handle whose identity is what makes it work; cloning
|
|
136
136
|
// one would break it. `opts.maxDepth` bounds a cyclic or hostile structure; the cap is set far
|
|
137
137
|
// above any real spec (the deepest is a certificate policy's UserNotice noticeRef, around 12).
|
|
138
138
|
//
|
|
139
|
-
//
|
|
139
|
+
// A spec that carries secret material must pass `opts.collect`, an array this fills with every
|
|
140
140
|
// buffer it copied. Copying a password or a private key produces a plaintext duplicate, and a
|
|
141
141
|
// module whose ownership rules say "a caller's Buffer is borrowed, so leave it alone" will not
|
|
142
|
-
// clear it
|
|
142
|
+
// clear it: the copy outlives the operation with no one accountable for it. Collect the copies
|
|
143
143
|
// and zeroize them in a `finally`, so the fix for one defect does not open another.
|
|
144
144
|
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any `Buffer.from(x)`
|
|
145
145
|
// is one). The guards are the RED vector that rewrites a field of the caller's spec after the
|
|
@@ -151,21 +151,21 @@ function snapshotDeep(value, ErrorClass, code, label, opts) {
|
|
|
151
151
|
return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
// Is this object a
|
|
154
|
+
// Is this object a handle, meaning something a copy would break instead of duplicate?
|
|
155
155
|
//
|
|
156
156
|
// The line is not "does it have Object.prototype". A caller's own class instance used as an
|
|
157
157
|
// options bag is data, and passing it through by reference on the strength of its prototype left
|
|
158
158
|
// exactly the window this exists to close: `cms.sign` accepts any non-Buffer object as options, so
|
|
159
159
|
// an instance whose `signedAttributes` flipped to false after the call still reached the signing
|
|
160
160
|
// turn. What a copy genuinely breaks is an object whose meaning lives somewhere other than its own
|
|
161
|
-
// properties
|
|
161
|
+
// properties: a key handle, a live collection, a pending result. Those are named here, never
|
|
162
162
|
// inferred, because inference gets it wrong in both directions: this engine's own CryptoKey carries
|
|
163
163
|
// its Node key handle as an own property, so "has no own keys" would have copied it into a shell
|
|
164
164
|
// that cannot sign, while a URL has none and would have copied into one that cannot be read.
|
|
165
|
-
// isCryptoKeyLike is the toolkit's own answer to "is this a key handle"
|
|
166
|
-
// jose.js ask
|
|
167
|
-
// The WebCrypto surface a CryptoKey presents, and nothing else. A real one
|
|
168
|
-
// platform's, or another implementation's
|
|
165
|
+
// isCryptoKeyLike is the toolkit's own answer to "is this a key handle", the same one key.js and
|
|
166
|
+
// jose.js ask, so a foreign implementation's CryptoKey is recognized here too.
|
|
167
|
+
// The WebCrypto surface a CryptoKey presents, and nothing else. A real one (this engine's, the
|
|
168
|
+
// platform's, or another implementation's) carries exactly these (its key material lives in an
|
|
169
169
|
// internal slot or a non-enumerable own property). A caller's options bag that happens to satisfy
|
|
170
170
|
// the structural predicate carries its own fields alongside, and those are what give it away.
|
|
171
171
|
var _CRYPTO_KEY_SURFACE = { type: 1, extractable: 1, algorithm: 1, usages: 1 };
|
|
@@ -181,9 +181,9 @@ var _THENABLE_SURFACE = { then: 1, catch: 1, finally: 1 };
|
|
|
181
181
|
// Everything else is copied. Enumerating "handle shapes" to pass through was the wrong shape of
|
|
182
182
|
// answer and lost repeatedly: every kind named as a handle turned out to be usable as an options
|
|
183
183
|
// bag with fields glued on, and each new shape reopened the window for exactly the fields that
|
|
184
|
-
// were glued on. There are only two honest outcomes for a caller's argument
|
|
185
|
-
// it
|
|
186
|
-
// rule for all of them at once
|
|
184
|
+
// were glued on. There are only two honest outcomes for a caller's argument: copy it, or refuse
|
|
185
|
+
// it. This names the small set where copying is impossible, so the refusal below can be the
|
|
186
|
+
// rule for all of them at once, never a shape to be found later.
|
|
187
187
|
// The surface an opaque kind is defined by, or null when the value is not one of them. A plain
|
|
188
188
|
// object literal is never opaque whatever it looks like: it is data this module can read and copy,
|
|
189
189
|
// and isCryptoKeyLike is structural by design so a literal can wear the key shape.
|
|
@@ -194,8 +194,8 @@ function _opaqueSurface(v, ErrorClass, code, label) {
|
|
|
194
194
|
if (v instanceof Error) return _ERROR_SURFACE;
|
|
195
195
|
if (v instanceof RegExp) return _REGEXP_SURFACE;
|
|
196
196
|
if (require("./webcrypto").isCryptoKeyLike(v)) return _CRYPTO_KEY_SURFACE; // allow:inline-require -- circular load: webcrypto requires this module
|
|
197
|
-
// Asking whether it is a thenable
|
|
198
|
-
// same fault the copy itself types, so it is typed here too
|
|
197
|
+
// Asking whether it is a thenable reads a property, and a caller's accessor can throw: the
|
|
198
|
+
// same fault the copy itself types, so it is typed here too and never escapes raw from a
|
|
199
199
|
// question this module asked on its own behalf.
|
|
200
200
|
var then;
|
|
201
201
|
try { then = v.then; }
|
|
@@ -204,17 +204,17 @@ function _opaqueSurface(v, ErrorClass, code, label) {
|
|
|
204
204
|
return null;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Is this opaque object safe to pass through
|
|
207
|
+
// Is this opaque object safe to pass through, carrying nothing beyond the surface its kind is
|
|
208
208
|
// defined by? Anything more is the case with no safe answer: the object cannot be copied, and
|
|
209
209
|
// passing it through would leave that data the caller's to change after the checks have read it.
|
|
210
|
-
// Every reachable name is examined, not only the own ones
|
|
210
|
+
// Every reachable name is examined, not only the own ones; a verb reads a field by name and does
|
|
211
211
|
// not care where on the chain it sits.
|
|
212
212
|
function _opaqueIsSafeToPass(v, surface) {
|
|
213
213
|
var keys = _reachableKeys(v);
|
|
214
214
|
for (var i = 0; i < keys.length; i++) {
|
|
215
215
|
if (surface[keys[i]]) continue;
|
|
216
|
-
// Only what the
|
|
217
|
-
// construction
|
|
216
|
+
// Only what the caller put there. An implementation's own internals are non-enumerable by
|
|
217
|
+
// construction (this engine's key handle, a platform object's slots), and so are the methods
|
|
218
218
|
// on any prototype, built-in or class-declared. What a caller adds, they add by assignment,
|
|
219
219
|
// which is enumerable; that is the case the refusal is for, whatever the value's type. A
|
|
220
220
|
// callable value is no exemption: an option is read for what it is, and `signedAttributes`
|
|
@@ -228,9 +228,9 @@ function _opaqueIsSafeToPass(v, surface) {
|
|
|
228
228
|
// Every property name a `v.field` lookup could resolve: own and inherited, enumerable or not.
|
|
229
229
|
//
|
|
230
230
|
// The enumerable-only version of this was wrong three times running, each time in the same
|
|
231
|
-
// direction
|
|
231
|
+
// direction (own keys missed an inherited field, enumerable keys missed a non-enumerable one),
|
|
232
232
|
// and each miss left the caller's object reachable behind a copy that looked complete. A verb reads
|
|
233
|
-
// an option
|
|
233
|
+
// an option by name, so the set that has to be fixed is the set a name lookup can reach, and
|
|
234
234
|
// nothing narrower. Object.prototype's own members are excluded: they are the language's, not the
|
|
235
235
|
// caller's, and copying them onto every spec would shadow the prototype for no gain.
|
|
236
236
|
function _reachableKeys(v) {
|
|
@@ -247,11 +247,11 @@ function _reachableKeys(v) {
|
|
|
247
247
|
return names;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// A private copy of a byte value
|
|
250
|
+
// A private copy of a byte value of the same kind the caller passed. The kind is
|
|
251
251
|
// load-bearing: each verb's own field validators decide which byte forms that field accepts, and
|
|
252
252
|
// most accept only Buffer / Uint8Array. Handing them a Buffer made from a DataView or a
|
|
253
|
-
// Uint16Array would have those inputs quietly accepted
|
|
254
|
-
// element layout
|
|
253
|
+
// Uint16Array would have those inputs quietly accepted, reinterpreted through a platform's own
|
|
254
|
+
// element layout, where they were rejected before. Copying is not the place to decide what a
|
|
255
255
|
// field takes, so the copy preserves the type and the validator still sees what it was given.
|
|
256
256
|
function _copyBytesSameKind(v, ErrorClass, code, label, collect) {
|
|
257
257
|
var src = source(v, ErrorClass, code, label); // re-views; a detached backing store is refused
|
|
@@ -276,30 +276,30 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
|
276
276
|
_copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect);
|
|
277
277
|
return bytesCopy;
|
|
278
278
|
}
|
|
279
|
-
// A parsed structure carrying guard-parsed's provenance record is a
|
|
279
|
+
// A parsed structure carrying guard-parsed's provenance record is a handle: the record is keyed
|
|
280
280
|
// on the object's identity, so a copy of it carries none and every door that decides integrity
|
|
281
|
-
// refuses the copy. It is also safe to leave alone
|
|
282
|
-
// record names, so nothing done to the object since matters. That holds for it
|
|
283
|
-
//
|
|
284
|
-
// added a field no door re-derives, so `AsProduced` is what is asked
|
|
281
|
+
// refuses the copy. It is also safe to leave alone, since that door re-derives from the bytes the
|
|
282
|
+
// record names, so nothing done to the object since matters. That holds for it as a parse
|
|
283
|
+
// result; a caller who adds an option to one and passes it where options are read by name has
|
|
284
|
+
// added a field no door re-derives, so `AsProduced` is what is asked, not `isRecorded`.
|
|
285
285
|
// Required inline because guard-parsed requires this module; at call time it is fully loaded.
|
|
286
286
|
if (require("./guard-parsed").isRecordedAsProduced(v)) return v; // allow:inline-require -- circular load: guard-parsed requires this module
|
|
287
287
|
if (v instanceof Date) return new Date(v.getTime());
|
|
288
288
|
if (Array.isArray(v)) {
|
|
289
289
|
var arr = [];
|
|
290
290
|
for (var i = 0; i < v.length; i++) arr.push(_deep(v[i], ErrorClass, code, label, cap, depth + 1, collect));
|
|
291
|
-
// An array can carry
|
|
291
|
+
// An array can carry named properties too, and a verb reads an option by name whatever the
|
|
292
292
|
// argument's type: `opts = []; opts.pem = true` is an options object as far as `opts.pem` is
|
|
293
293
|
// concerned. Copying only the indexed elements dropped those fields, which silently changed
|
|
294
|
-
// what the verb was asked to do
|
|
294
|
+
// what the verb was asked to do instead of failing.
|
|
295
295
|
_copyNamed(v, arr, ErrorClass, code, label, cap, depth, collect);
|
|
296
296
|
return arr;
|
|
297
297
|
}
|
|
298
298
|
// The only kinds this module cannot read the state of. One carrying nothing of its own is passed
|
|
299
|
-
// through
|
|
299
|
+
// through: there is no data on it to fix, and a copy would break it. One carrying its own
|
|
300
300
|
// fields has no safe handling at all: it cannot be copied, and passing it through would leave
|
|
301
|
-
// those fields the caller's to rewrite after the checks read them. That is refused
|
|
302
|
-
// half-handled, which is what keeps this from being another shape to be found later.
|
|
301
|
+
// those fields the caller's to rewrite after the checks read them. That case is refused outright,
|
|
302
|
+
// never half-handled, which is what keeps this from being another shape to be found later.
|
|
303
303
|
var surface = _opaqueSurface(v, ErrorClass, code, label);
|
|
304
304
|
if (surface) {
|
|
305
305
|
if (_opaqueIsSafeToPass(v, surface)) return v;
|
|
@@ -309,10 +309,10 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
|
309
309
|
}
|
|
310
310
|
if (v instanceof Map) return _copyEntries(v, new Map(), ErrorClass, code, label, cap, depth, collect);
|
|
311
311
|
if (v instanceof Set) return _copyEntries(v, new Set(), ErrorClass, code, label, cap, depth, collect);
|
|
312
|
-
// The prototype is kept so an instance's methods still resolve. A null-prototype dictionary
|
|
313
|
-
// what `JSON.parse` or an explicit `Object.create(null)` produces
|
|
314
|
-
// inheriting from Object.prototype either, which is why the prototype is carried across
|
|
315
|
-
//
|
|
312
|
+
// The prototype is kept so an instance's methods still resolve. A null-prototype dictionary
|
|
313
|
+
// (what `JSON.parse` or an explicit `Object.create(null)` produces) must not come back
|
|
314
|
+
// inheriting from Object.prototype either, which is why the prototype is carried across and
|
|
315
|
+
// never assumed.
|
|
316
316
|
var out = Object.create(Object.getPrototypeOf(v));
|
|
317
317
|
_copyNamed(v, out, ErrorClass, code, label, cap, depth, collect);
|
|
318
318
|
return out;
|
|
@@ -320,13 +320,13 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
|
320
320
|
|
|
321
321
|
// Which names to carry across, given what the copy is.
|
|
322
322
|
//
|
|
323
|
-
// For a plain object or a class instance
|
|
323
|
+
// For a plain object or a class instance (the shapes an options bag actually takes) it is every
|
|
324
324
|
// name a lookup could resolve, inherited included, because that is what the verb reads.
|
|
325
325
|
//
|
|
326
|
-
// For an array, a byte view, a Map or a Set, it is the caller's
|
|
327
|
-
// prototypes are full of accessors that describe the kind
|
|
326
|
+
// For an array, a byte view, a Map or a Set, it is the caller's own added names only. Their
|
|
327
|
+
// prototypes are full of accessors that describe the kind, not the caller (`length`,
|
|
328
328
|
// `buffer`, `size`, and Node's `parent`, which hands back the 64 KiB arena a small Buffer was
|
|
329
|
-
// allocated from)
|
|
329
|
+
// allocated from). Every one of those is already correct on the copy, and reading them would
|
|
330
330
|
// copy things the caller never passed. Enumerating the accessors to skip is the wrong way round;
|
|
331
331
|
// what the caller added is exactly what is own and not an index.
|
|
332
332
|
function _namesToCopy(src, dst) {
|
|
@@ -343,7 +343,7 @@ function _namesToCopy(src, dst) {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// A Map or a Set is data the caller can still change, entry by entry, so it is copied like any
|
|
346
|
-
// other
|
|
346
|
+
// other: entries first, then the named properties one can carry alongside them.
|
|
347
347
|
function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
348
348
|
src.forEach(function (value, key) {
|
|
349
349
|
var copiedValue = _deep(value, ErrorClass, code, label, cap, depth + 1, collect);
|
|
@@ -354,25 +354,25 @@ function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
|
354
354
|
return dst;
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
// Copy every value a name lookup on `src` could reach onto `dst`, as an
|
|
357
|
+
// Copy every value a name lookup on `src` could reach onto `dst`, as an own data property.
|
|
358
358
|
//
|
|
359
359
|
// Own matters: a key literally named `__proto__` would otherwise re-point `dst`, so the field the
|
|
360
360
|
// caller passed disappears from Object.keys and an unknown-option check walks a different object
|
|
361
|
-
// than the verb goes on to read. Shadowing matters for the same reason it is done at all
|
|
361
|
+
// than the verb goes on to read. Shadowing matters for the same reason it is done at all: an own
|
|
362
362
|
// copy is what makes an inherited value stop tracking the caller's prototype. A method is left
|
|
363
|
-
// where it is
|
|
364
|
-
//
|
|
363
|
+
// where it is, since copying a function would only move it, and it is the prototype's behaviour,
|
|
364
|
+
// not the caller's data.
|
|
365
365
|
function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
366
366
|
var keys = _namesToCopy(src, dst);
|
|
367
367
|
for (var k = 0; k < keys.length; k++) {
|
|
368
368
|
var value;
|
|
369
369
|
// Reading a caller's property can run a caller's accessor, and one that throws is a bad input
|
|
370
|
-
// like any other
|
|
371
|
-
//
|
|
370
|
+
// like any other: the fault gets this boundary's typed code with the raw error as its cause,
|
|
371
|
+
// and never escapes as itself from inside a verb the caller called for something else.
|
|
372
372
|
try { value = src[keys[k]]; }
|
|
373
373
|
catch (e) { throw _raise(ErrorClass, code, label + ": reading " + JSON.stringify(keys[k]) + " threw", e); }
|
|
374
|
-
// A function is carried across by reference
|
|
375
|
-
//
|
|
374
|
+
// A function is carried across by reference, since there is nothing in it to copy, but it is
|
|
375
|
+
// still carried. Dropping it changed what the caller passed: an unknown spec
|
|
376
376
|
// field whose value happened to be a function stopped reaching the verb's own key check, so a
|
|
377
377
|
// typo that used to be refused was silently accepted.
|
|
378
378
|
Object.defineProperty(dst, keys[k], {
|
|
@@ -396,17 +396,17 @@ function _wasEnumerable(v, name) {
|
|
|
396
396
|
// fixArguments(ErrorClass, code, args) -> { values, release }
|
|
397
397
|
//
|
|
398
398
|
// The whole rule for a verb that spans a promise turn, in one call. `args` is a list of
|
|
399
|
-
// `[value, label]` pairs
|
|
399
|
+
// `[value, label]` pairs, one per argument the verb was handed. Each is deep-copied, so no field
|
|
400
400
|
// of any of them, at any depth, is still the caller's to change once the verb has begun; `release`
|
|
401
401
|
// clears every copy that was made and belongs in a `finally`, so a copied password or private key
|
|
402
402
|
// does not outlive the call.
|
|
403
403
|
//
|
|
404
404
|
// Both halves are needed and neither is optional. Copying only the argument that looks like data
|
|
405
|
-
// leaves a secret nested inside the one that looks like a handle
|
|
406
|
-
// level below a shallow copy
|
|
405
|
+
// leaves a secret nested inside the one that looks like a handle (`opts.mac.secret` sitting a
|
|
406
|
+
// level below a shallow copy) readable and rewritable across the turn. Copying without releasing
|
|
407
407
|
// answers that by duplicating the secret instead. Deciding per field which is which is the judgment
|
|
408
408
|
// that keeps going wrong, so there is no per-field decision here: everything is copied, everything
|
|
409
|
-
// copied is cleared, and the only things left alone are the ones a copy would
|
|
409
|
+
// copied is cleared, and the only things left alone are the ones a copy would break: a CryptoKey
|
|
410
410
|
// or any class instance (its identity is what makes it work) and a recorded parse result (its
|
|
411
411
|
// provenance is keyed to the object).
|
|
412
412
|
// @enforced-by behavioral -- there is no rename-proof code shape for "copied every argument". The
|
|
@@ -415,7 +415,7 @@ function _wasEnumerable(v, name) {
|
|
|
415
415
|
function fixArguments(ErrorClass, code, args) {
|
|
416
416
|
var copies = [];
|
|
417
417
|
var values = [];
|
|
418
|
-
// Through guard.secret, which owns what clearing a secret means
|
|
418
|
+
// Through guard.secret, which owns what clearing a secret means. Required inline because
|
|
419
419
|
// guard-secret requires this module; at call time it is fully loaded.
|
|
420
420
|
function release() {
|
|
421
421
|
require("./guard-secret").zeroizeAll(copies, ErrorClass, code, // allow:inline-require -- circular load: guard-secret requires this module
|
|
@@ -426,8 +426,8 @@ function fixArguments(ErrorClass, code, args) {
|
|
|
426
426
|
values.push(snapshotDeep(args[i][0], ErrorClass, code, args[i][1], { collect: copies }));
|
|
427
427
|
}
|
|
428
428
|
} catch (e) {
|
|
429
|
-
// The copying itself can fail partway
|
|
430
|
-
// getter that throws
|
|
429
|
+
// The copying itself can fail partway (a detached leaf, a structure past the depth cap, a
|
|
430
|
+
// getter that throws), and the arguments already copied by then are just as much ours as if
|
|
431
431
|
// the call had gone on to succeed. Returning nothing would leave the caller with no handle to
|
|
432
432
|
// release them, so a signer key or a password copied before the fault would stay readable for
|
|
433
433
|
// as long as the heap held it. Clear what was made, then let the fault out unchanged.
|
|
@@ -440,9 +440,9 @@ function fixArguments(ErrorClass, code, args) {
|
|
|
440
440
|
// fixedCall(ErrorClass, code, args, body) -> Promise
|
|
441
441
|
//
|
|
442
442
|
// The form every Promise-returning producing verb uses, because the halves have to be arranged in
|
|
443
|
-
// exactly one way and this is it. The copy runs
|
|
444
|
-
// copy itself
|
|
445
|
-
// as a
|
|
443
|
+
// exactly one way and this is it. The copy runs inside guard-async's boundary, so a fault in the
|
|
444
|
+
// copy itself (a detached view, a structure nested past the cap, a getter that throws) leaves
|
|
445
|
+
// as a rejection like every other fault of a verb documented `-> Promise<...>`, never as a
|
|
446
446
|
// synchronous throw past the caller's `.catch`. It still runs at the call, before any turn passes,
|
|
447
447
|
// which is what fixes the arguments. And the release runs whether the body resolved, rejected, or
|
|
448
448
|
// never ran, so a copied secret is cleared on every path out.
|
package/lib/guard-compress.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// `info: true`, each raises ERR_BUFFER_TOO_LARGE on a cap breach, and each
|
|
29
29
|
// accepts trailing bytes when left unchecked.
|
|
30
30
|
//
|
|
31
|
-
// E is the caller's (code, message, cause) typed-error
|
|
31
|
+
// E is the caller's (code, message, cause) typed-error factory, never an error
|
|
32
32
|
// class. Passing a class here crashes on the error path ("class cannot be invoked
|
|
33
33
|
// without new"), which converts a rejection into a fail-open. `codes` names the
|
|
34
34
|
// two verdicts a caller distinguishes: `tooLarge` when the output would exceed
|
|
@@ -45,8 +45,8 @@ var DECOMPRESS = {
|
|
|
45
45
|
zstd: zlib.zstdDecompressSync, // RFC 8478
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
// The compressors, used
|
|
49
|
-
// not the guarded shape
|
|
48
|
+
// The compressors, used only by the truncation probe below. Producing a compressed stream is
|
|
49
|
+
// not the guarded shape; decompressing an untrusted one is.
|
|
50
50
|
var _PROBE_COMPRESS = {
|
|
51
51
|
zlib: zlib.deflateSync,
|
|
52
52
|
brotli: zlib.brotliCompressSync,
|
|
@@ -55,22 +55,22 @@ var _PROBE_COMPRESS = {
|
|
|
55
55
|
|
|
56
56
|
// A decompressor must REPORT a frame it could not finish. Not all of them do: on some supported
|
|
57
57
|
// runtimes the zstd binding returns a SHORT (often empty) result for a truncated frame instead
|
|
58
|
-
// of faulting, and reports the entire input as consumed
|
|
58
|
+
// of faulting, and reports the entire input as consumed, so neither the returned bytes nor the
|
|
59
59
|
// consumed-length check below can see that the frame was cut. That is a silent truncation: a peer
|
|
60
60
|
// strips a frame's tail and the receiver processes a prefix as though it were the whole message.
|
|
61
61
|
//
|
|
62
62
|
// An algorithm whose runtime cannot report this cannot be decompressed safely here, so it is
|
|
63
|
-
// dropped from the advertised set at load
|
|
63
|
+
// dropped from the advertised set at load instead of accepted with a defect. Consumers build
|
|
64
64
|
// their wire registries by intersecting with algorithms(), so a protocol simply does not offer
|
|
65
65
|
// or accept it, and it returns on its own once the runtime reports truncation.
|
|
66
|
-
// Nothing here is enabled by an exception alone: an algorithm qualifies only by
|
|
67
|
-
// that a whole frame round-trips
|
|
68
|
-
// missing function, a compressor fault, a wrong round-trip, an unrelated error
|
|
66
|
+
// Nothing here is enabled by an exception alone: an algorithm qualifies only by positive proof
|
|
67
|
+
// that a whole frame round-trips and that a cut frame is refused. Every other outcome (a
|
|
68
|
+
// missing function, a compressor fault, a wrong round-trip, an unrelated error) leaves it out.
|
|
69
69
|
//
|
|
70
70
|
// Coverage residual: the early-return branches are reachable only on a runtime whose own
|
|
71
71
|
// compression library is broken (a missing export, a compressor that throws on 46 ASCII bytes, a
|
|
72
72
|
// round-trip that does not reproduce its input). The tables are captured at module load, so those
|
|
73
|
-
// states cannot be induced from a test without replacing node:zlib beforehand
|
|
73
|
+
// states cannot be induced from a test without replacing node:zlib beforehand, and forcing them
|
|
74
74
|
// would assert nothing about the property this function exists to establish. They stay because
|
|
75
75
|
// their absence is what would make a broken runtime silently qualify an algorithm.
|
|
76
76
|
function _reportsTruncation(name) {
|
|
@@ -82,13 +82,13 @@ function _reportsTruncation(name) {
|
|
|
82
82
|
try { frame = compress(sample); }
|
|
83
83
|
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
84
|
// A cut of 2 removes the frame's end marker; the sample is long enough that what remains is
|
|
85
|
-
// still a plausible frame head
|
|
85
|
+
// still a plausible frame head and not nothing at all.
|
|
86
86
|
if (!Buffer.isBuffer(frame) || frame.length < 4) return false;
|
|
87
87
|
try { whole = decompress(frame, { maxOutputLength: 4096 }); }
|
|
88
88
|
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
89
|
if (!Buffer.isBuffer(whole) || !whole.equals(sample)) return false;
|
|
90
|
-
// The cut frame must be
|
|
91
|
-
// some bindings hand back
|
|
90
|
+
// The cut frame must be refused. A returned value of any length, including the empty buffer
|
|
91
|
+
// some bindings hand back, means a truncation would pass through unseen.
|
|
92
92
|
var cutAccepted = false;
|
|
93
93
|
try {
|
|
94
94
|
decompress(frame.subarray(0, frame.length - 2), { maxOutputLength: 4096 });
|
|
@@ -115,7 +115,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
115
115
|
// An unknown algorithm -- or one this runtime cannot decompress safely -- is an authoring
|
|
116
116
|
// fault, not untrusted input: a caller resolves a wire value to a name against algorithms()
|
|
117
117
|
// and rejects one it does not find, before reaching here. Arriving with a name outside that
|
|
118
|
-
// set means the consumer skipped the step, so it faults loudly
|
|
118
|
+
// set means the consumer skipped the step, so it faults loudly and never silently.
|
|
119
119
|
if (!Object.prototype.hasOwnProperty.call(SAFE, algorithm) || !decompress) {
|
|
120
120
|
throw new TypeError("guard.compress.bounded: unknown or unsafe algorithm " + JSON.stringify(algorithm));
|
|
121
121
|
}
|
|
@@ -127,7 +127,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
127
127
|
// The ceiling is an authoring input: an undefined / NaN / fractional / negative
|
|
128
128
|
// cap would silently disable the bomb defence. It must also be at least 1 --
|
|
129
129
|
// node rejects maxOutputLength 0 with a different fault than a cap breach, so a
|
|
130
|
-
// zero cap would surface as a malformed-stream verdict
|
|
130
|
+
// zero cap would surface as a malformed-stream verdict instead of a config error.
|
|
131
131
|
if (!Number.isInteger(cap) || cap < 1) {
|
|
132
132
|
throw new TypeError("guard.compress.bounded: cap must be a positive integer");
|
|
133
133
|
}
|
|
@@ -142,8 +142,8 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
142
142
|
throw E(codes.failed, what + " could not be decompressed", e);
|
|
143
143
|
}
|
|
144
144
|
// `bytesWritten` is the count of INPUT bytes the engine consumed. A frame that
|
|
145
|
-
// ends before the input does means the remainder was ignored
|
|
146
|
-
// above
|
|
145
|
+
// ends before the input does means the remainder was ignored, which is the
|
|
146
|
+
// malleability above, so the whole input must be exactly one frame.
|
|
147
147
|
var consumed = res.engine.bytesWritten;
|
|
148
148
|
if (consumed !== stream.length) {
|
|
149
149
|
throw E(codes.failed, what + " carries " + (stream.length - consumed) +
|
|
@@ -152,7 +152,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
152
152
|
return res.buffer;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// The algorithm names this guard can decompress
|
|
155
|
+
// The algorithm names this guard can decompress safely on this runtime: the registry
|
|
156
156
|
// minus any whose decompressor cannot report an unfinished frame. A consumer intersects
|
|
157
157
|
// its protocol's algorithm registry with this, so it never advertises or accepts something
|
|
158
158
|
// it could not refuse a truncation of.
|
package/lib/guard-crypto.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// MAC / digest BIT STRING with a non-zero unused-bits count is not whole octets,
|
|
26
26
|
// so trailing sub-octet bits let two encodings carry "one" signature. Every RFC
|
|
27
27
|
// 9481/4211/6960/5280 signature output is a whole number of octets, so a value
|
|
28
|
-
// with unusedBits !== 0 fails closed
|
|
28
|
+
// with unusedBits !== 0 fails closed before its bytes reach a verifier.
|
|
29
29
|
|
|
30
30
|
var nodeCrypto = require("node:crypto");
|
|
31
31
|
|
package/lib/guard-encoding.js
CHANGED
|
@@ -10,28 +10,28 @@
|
|
|
10
10
|
// guard-encoding -- strict decode of an untrusted base64 / base64url / hex text
|
|
11
11
|
// to bytes. Node's Buffer.from(x, "base64"|"base64url"|"hex") is LENIENT: it
|
|
12
12
|
// silently drops the first invalid character (and everything after a stray one),
|
|
13
|
-
// accepts non-canonical trailing bits, and tolerates missing/extra padding
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// before any size is checked. This is CWE-172 (encoding-transformation error) /
|
|
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
17
|
// CWE-20 (canonicalization malleability) + CWE-770 (allocate-before-cap): RFC
|
|
18
18
|
// 4648 sec. 3.5 / sec. 5 and RFC 8555 sec. 6.1 require the UNIQUE canonical
|
|
19
19
|
// encoding. Each decoder here gates the alphabet, rejects an impossible length,
|
|
20
|
-
// enforces the byte cap
|
|
21
|
-
// re-encode round-trip
|
|
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
22
|
// its bytes fails closed with the caller's typed error.
|
|
23
23
|
//
|
|
24
24
|
// E is the caller's (code, message) typed-error factory; `code` the frozen
|
|
25
25
|
// domain/reason; `label` the field phrase; `maxBytes` an optional decoded-size
|
|
26
26
|
// cap (null/undefined = uncapped, for a PEM cert body already bounded upstream).
|
|
27
27
|
|
|
28
|
-
// The alphabets are
|
|
29
|
-
//
|
|
30
|
-
// pattern engine's cost on a non-matching string is
|
|
31
|
-
//
|
|
32
|
-
// outside. A table lookup is one array index per character
|
|
33
|
-
// length, which the caller already caps. It is also the more honest
|
|
34
|
-
// the rule: the set of permitted characters
|
|
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
35
|
function _alphabet(chars) {
|
|
36
36
|
var t = new Uint8Array(128);
|
|
37
37
|
for (var i = 0; i < chars.length; i++) t[chars.charCodeAt(i)] = 1;
|
|
@@ -43,8 +43,8 @@ var B64_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "+/");
|
|
|
43
43
|
var HEX_ALPHABET = _alphabet(DIGITS + "abcdef" + "ABCDEF");
|
|
44
44
|
|
|
45
45
|
// Every character of `text` is in `table`. A code point outside Latin-1's low half
|
|
46
|
-
// (including every astral half) is outside all three alphabets,
|
|
47
|
-
//
|
|
46
|
+
// (including every astral half) is outside all three alphabets, and the explicit
|
|
47
|
+
// bound rejects it, so no lookup can read undefined off the end of the table.
|
|
48
48
|
function _inAlphabet(text, table) {
|
|
49
49
|
for (var i = 0; i < text.length; i++) {
|
|
50
50
|
var c = text.charCodeAt(i);
|
package/lib/guard-header.js
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
var C = require("./constants");
|
|
21
21
|
|
|
22
22
|
// assertField(name, value, E, code) -> value | throws new E(code, ...)
|
|
23
|
-
// name: RFC 5322 ftext
|
|
23
|
+
// name: RFC 5322 ftext, printable ASCII [0x21, 0x7e] except ':' (0x3a), non-empty
|
|
24
24
|
// (a space, control byte, or ':' in a field name breaks the "Name: value" grammar).
|
|
25
|
-
// value: reject CR (0x0d)
|
|
25
|
+
// value: reject CR (0x0d), LF (0x0a) and NUL (0x00), the injection bytes; TAB
|
|
26
26
|
// (0x09, folding whitespace) and printable non-ASCII (a UTF-8 header value, RFC
|
|
27
27
|
// 6532) pass. E is the caller's typed-error CLASS, thrown `new E(code, message)`
|
|
28
28
|
// -- the same convention lib/mime.js (this guard's caller) and guard.bytes/text use.
|
|
@@ -42,7 +42,7 @@ function assertField(name, value, E, code) {
|
|
|
42
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));
|
|
43
43
|
}
|
|
44
44
|
// RFC 5322 sec. 2.1.1: the serialized "Name: value" line (UTF-8 octets, excluding CRLF) must not exceed
|
|
45
|
-
// the line limit, or a relay may re-fold it
|
|
45
|
+
// the line limit, or a relay may re-fold it and silently change the signed bytes of a protected header.
|
|
46
46
|
if (Buffer.byteLength(name, "utf8") + 2 + Buffer.byteLength(v, "utf8") > C.LIMITS.HEADER_LINE_MAX_OCTETS) {
|
|
47
47
|
throw new E(code, "a header field line exceeds RFC 5322's " + C.LIMITS.HEADER_LINE_MAX_OCTETS + "-octet limit: " + JSON.stringify(name));
|
|
48
48
|
}
|