@blamejs/pki 0.5.6 → 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 +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- 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 +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- 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 +378 -15
- 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 +144 -75
- 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 +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- 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 +169 -36
- 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 +19 -16
- 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 +22 -18
- 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 +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/guard-bytes.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// primitives whose input boundaries compose these guards (pki.asn1.decode,
|
|
7
7
|
// pki.cbor.decode, pki.ct.parseSctList, pki.webcrypto.*).
|
|
8
8
|
//
|
|
9
|
+
var async = require("./guard-async");
|
|
10
|
+
|
|
9
11
|
// guard-bytes -- fail-closed coercion of an untrusted byte-source input to a
|
|
10
12
|
// Buffer view. One of the enforced choke points of the guard family: a
|
|
11
13
|
// codebase-patterns detector requires every byte-input boundary to route
|
|
@@ -19,10 +21,30 @@
|
|
|
19
21
|
// validation feeding a CWE-347-style verification-of-nothing). Always re-viewing
|
|
20
22
|
// through Buffer.from(x.buffer, x.byteOffset, x.byteLength) turns the detached
|
|
21
23
|
// read into a typed reject at the boundary. Size / length-field allocation
|
|
22
|
-
// 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
|
|
23
25
|
// are per-format (a multi-MB CRL is legitimate, a Merkle proof is tiny), so they
|
|
24
26
|
// live in guard-params and each decoder's own cap.
|
|
25
27
|
|
|
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-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
|
+
// caller can still rewrite the bytes after they were validated. The exceptions are the two
|
|
33
|
+
// places holding a caller's SECRET, where a copy is the worse defect: a password or a private
|
|
34
|
+
// key is re-viewed and left borrowed only when this module also clears the copy it made, so
|
|
35
|
+
// no plaintext duplicate outlives the derivation.
|
|
36
|
+
|
|
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 (sign-scheme,
|
|
39
|
+
// composite-sig, pki-build) carry a bound factory that prefixes the caller's domain onto the
|
|
40
|
+
// code; guard-bytes and guard-header take the error CLASS. A boundary that has only one of the two
|
|
41
|
+
// would otherwise have to hand-roll the re-view to reach this guard at all, which is precisely what
|
|
42
|
+
// the re-inline detector forbids, so accept either: a class (its prototype is an Error) is
|
|
43
|
+
// constructed, anything else is called.
|
|
44
|
+
function _raise(E, code, message, cause) {
|
|
45
|
+
return (E.prototype instanceof Error) ? new E(code, message, cause) : E(code, message, cause);
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
// view(input, ErrorClass, code, label) -> Buffer view | throws ErrorClass(code, msg, cause)
|
|
27
49
|
// Accepts a Buffer / Uint8Array -- the DER / CBOR / CT / Merkle input contract.
|
|
28
50
|
// ErrorClass MUST be a withCause PkiError subclass (the raw detach failure is
|
|
@@ -34,20 +56,20 @@ function view(input, ErrorClass, code, label) {
|
|
|
34
56
|
try {
|
|
35
57
|
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
36
58
|
} catch (e) {
|
|
37
|
-
throw
|
|
59
|
+
throw _raise(ErrorClass, code, label + ": input is not a usable byte view (detached backing buffer?)", e);
|
|
38
60
|
}
|
|
39
61
|
}
|
|
40
|
-
throw
|
|
62
|
+
throw _raise(ErrorClass, code, label + ": expected a Buffer / Uint8Array");
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
// source(input, ErrorClass, code, label) -> Buffer | throws ErrorClass
|
|
44
66
|
// Accepts the full W3C BufferSource (Buffer / TypedArray view / raw ArrayBuffer)
|
|
45
|
-
// -- the WebCrypto input contract. Re-
|
|
46
|
-
// 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
|
|
47
69
|
// store, it does not duplicate it. That is what makes this safe to call before a
|
|
48
|
-
// size ceiling has been applied
|
|
70
|
+
// size ceiling has been applied (nothing is materialized), and it is also why a
|
|
49
71
|
// caller that must hold the bytes across an await wants `snapshotSource`, which
|
|
50
|
-
// copies
|
|
72
|
+
// copies. Throws on a detached backing store.
|
|
51
73
|
// @enforced-by guard-shape-reinlined (the re-view shape is declared on view above)
|
|
52
74
|
function source(input, ErrorClass, code, label) {
|
|
53
75
|
var isAb = input instanceof ArrayBuffer;
|
|
@@ -55,10 +77,10 @@ function source(input, ErrorClass, code, label) {
|
|
|
55
77
|
try {
|
|
56
78
|
return isAb ? Buffer.from(input) : Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
57
79
|
} catch (e) {
|
|
58
|
-
throw
|
|
80
|
+
throw _raise(ErrorClass, code, label + ": input is not a usable byte source (detached backing buffer?)", e);
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
|
-
throw
|
|
83
|
+
throw _raise(ErrorClass, code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
// snapshot(input, ErrorClass, code, label) -> private Buffer copy | throws ErrorClass
|
|
@@ -66,15 +88,15 @@ function source(input, ErrorClass, code, label) {
|
|
|
66
88
|
// The parse-then-verify time-of-check/time-of-use defence. A verification entry
|
|
67
89
|
// point that PARSES its input synchronously and then VERIFIES a signature over
|
|
68
90
|
// the same bytes in a later promise turn is reading the caller's memory twice
|
|
69
|
-
// with an await in between. Every byte range the parse surfaced
|
|
70
|
-
// 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
|
|
71
93
|
// that memory, so anything that rewrites the buffer in the gap makes the verdict
|
|
72
94
|
// describe bytes other than the ones the signature was checked against
|
|
73
95
|
// (CWE-367 TOCTOU reaching a CWE-347 wrong-verdict). The window is real without
|
|
74
96
|
// an attacker in the process: a caller recycling a pooled read buffer across
|
|
75
97
|
// concurrent verifies hits it by accident.
|
|
76
98
|
//
|
|
77
|
-
// 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`
|
|
78
100
|
// re-views and is the right guard where the input is consumed in one synchronous
|
|
79
101
|
// pass; this is its sibling for the boundary that spans an await.
|
|
80
102
|
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any
|
|
@@ -86,8 +108,8 @@ function snapshot(input, ErrorClass, code, label) {
|
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
// snapshotSource(input, ErrorClass, code, label) -> private Buffer copy | throws
|
|
89
|
-
// The same parse-then-verify defence as `snapshot`, over the
|
|
90
|
-
// (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
|
|
91
113
|
// Buffer / Uint8Array contract. A parser that accepts a BufferSource must snapshot
|
|
92
114
|
// the same set: leaving an ArrayBuffer or a DataView aliased reopens the window for
|
|
93
115
|
// exactly the inputs that took the wider path in.
|
|
@@ -97,4 +119,345 @@ function snapshotSource(input, ErrorClass, code, label) {
|
|
|
97
119
|
return Buffer.from(source(input, ErrorClass, code, label));
|
|
98
120
|
}
|
|
99
121
|
|
|
100
|
-
|
|
122
|
+
// snapshotDeep(value, ErrorClass, code, label) -> a private copy of every byte leaf | throws
|
|
123
|
+
//
|
|
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
|
+
// promise turns while it resolves a key, hashes, and signs. Every byte the verb ends up
|
|
127
|
+
// encoding reaches it through some field of that object, and the caller still owns all of them,
|
|
128
|
+
// so a spec validated at entry and read again after the first turn need not describe the same
|
|
129
|
+
// certificate, CRL, or request. It is the same defect as an aliased buffer, one level up: the
|
|
130
|
+
// value that passed the checks and the value that gets signed are two different reads.
|
|
131
|
+
//
|
|
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
|
+
// non-plain object is a platform or library handle whose identity is what makes it work; cloning
|
|
136
|
+
// one would break it. `opts.maxDepth` bounds a cyclic or hostile structure; the cap is set far
|
|
137
|
+
// above any real spec (the deepest is a certificate policy's UserNotice noticeRef, around 12).
|
|
138
|
+
//
|
|
139
|
+
// A spec that carries secret material must pass `opts.collect`, an array this fills with every
|
|
140
|
+
// buffer it copied. Copying a password or a private key produces a plaintext duplicate, and a
|
|
141
|
+
// module whose ownership rules say "a caller's Buffer is borrowed, so leave it alone" will not
|
|
142
|
+
// clear it: the copy outlives the operation with no one accountable for it. Collect the copies
|
|
143
|
+
// and zeroize them in a `finally`, so the fix for one defect does not open another.
|
|
144
|
+
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any `Buffer.from(x)`
|
|
145
|
+
// is one). The guards are the RED vector that rewrites a field of the caller's spec after the
|
|
146
|
+
// verb has returned its promise and asserts the emitted artifact carries the entry value, and
|
|
147
|
+
// the vector that holds a reference to every copy and asserts it reads all-zero after the call.
|
|
148
|
+
function snapshotDeep(value, ErrorClass, code, label, opts) {
|
|
149
|
+
var o = opts || {};
|
|
150
|
+
var cap = o.maxDepth == null ? 64 : o.maxDepth;
|
|
151
|
+
return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Is this object a handle, meaning something a copy would break instead of duplicate?
|
|
155
|
+
//
|
|
156
|
+
// The line is not "does it have Object.prototype". A caller's own class instance used as an
|
|
157
|
+
// options bag is data, and passing it through by reference on the strength of its prototype left
|
|
158
|
+
// exactly the window this exists to close: `cms.sign` accepts any non-Buffer object as options, so
|
|
159
|
+
// an instance whose `signedAttributes` flipped to false after the call still reached the signing
|
|
160
|
+
// turn. What a copy genuinely breaks is an object whose meaning lives somewhere other than its own
|
|
161
|
+
// properties: a key handle, a live collection, a pending result. Those are named here, never
|
|
162
|
+
// inferred, because inference gets it wrong in both directions: this engine's own CryptoKey carries
|
|
163
|
+
// its Node key handle as an own property, so "has no own keys" would have copied it into a shell
|
|
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", 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
|
+
// internal slot or a non-enumerable own property). A caller's options bag that happens to satisfy
|
|
170
|
+
// the structural predicate carries its own fields alongside, and those are what give it away.
|
|
171
|
+
var _CRYPTO_KEY_SURFACE = { type: 1, extractable: 1, algorithm: 1, usages: 1 };
|
|
172
|
+
// The same, for the other kinds whose state cannot be copied: what belongs to the kind, so that
|
|
173
|
+
// anything else on the object is recognizably the caller's own and gets the refusal below.
|
|
174
|
+
var _ERROR_SURFACE = { message: 1, stack: 1, name: 1, cause: 1 };
|
|
175
|
+
var _REGEXP_SURFACE = { lastIndex: 1, source: 1, flags: 1, global: 1, ignoreCase: 1, multiline: 1,
|
|
176
|
+
sticky: 1, unicode: 1, unicodeSets: 1, hasIndices: 1, dotAll: 1 };
|
|
177
|
+
var _THENABLE_SURFACE = { then: 1, catch: 1, finally: 1 };
|
|
178
|
+
|
|
179
|
+
// Is this an object whose state this module cannot read, and therefore cannot copy?
|
|
180
|
+
//
|
|
181
|
+
// Everything else is copied. Enumerating "handle shapes" to pass through was the wrong shape of
|
|
182
|
+
// answer and lost repeatedly: every kind named as a handle turned out to be usable as an options
|
|
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: 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
|
+
// The surface an opaque kind is defined by, or null when the value is not one of them. A plain
|
|
188
|
+
// object literal is never opaque whatever it looks like: it is data this module can read and copy,
|
|
189
|
+
// and isCryptoKeyLike is structural by design so a literal can wear the key shape.
|
|
190
|
+
function _opaqueSurface(v, ErrorClass, code, label) {
|
|
191
|
+
var proto = Object.getPrototypeOf(v);
|
|
192
|
+
if (proto === Object.prototype || proto === null) return null;
|
|
193
|
+
if (v instanceof WeakMap || v instanceof WeakSet) return {}; // deliberately not enumerable
|
|
194
|
+
if (v instanceof Error) return _ERROR_SURFACE;
|
|
195
|
+
if (v instanceof RegExp) return _REGEXP_SURFACE;
|
|
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 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
|
+
// question this module asked on its own behalf.
|
|
200
|
+
var then;
|
|
201
|
+
try { then = v.then; }
|
|
202
|
+
catch (e) { throw _raise(ErrorClass, code, label + ": reading \"then\" threw", e); }
|
|
203
|
+
if (typeof then === "function") return _THENABLE_SURFACE; // a pending result, not a value
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Is this opaque object safe to pass through, carrying nothing beyond the surface its kind is
|
|
208
|
+
// defined by? Anything more is the case with no safe answer: the object cannot be copied, and
|
|
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; a verb reads a field by name and does
|
|
211
|
+
// not care where on the chain it sits.
|
|
212
|
+
function _opaqueIsSafeToPass(v, surface) {
|
|
213
|
+
var keys = _reachableKeys(v);
|
|
214
|
+
for (var i = 0; i < keys.length; i++) {
|
|
215
|
+
if (surface[keys[i]]) continue;
|
|
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
|
+
// on any prototype, built-in or class-declared. What a caller adds, they add by assignment,
|
|
219
|
+
// which is enumerable; that is the case the refusal is for, whatever the value's type. A
|
|
220
|
+
// callable value is no exemption: an option is read for what it is, and `signedAttributes`
|
|
221
|
+
// holding a function is neither false nor a method of anything.
|
|
222
|
+
if (!_wasEnumerable(v, keys[i])) continue;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Every property name a `v.field` lookup could resolve: own and inherited, enumerable or not.
|
|
229
|
+
//
|
|
230
|
+
// The enumerable-only version of this was wrong three times running, each time in the same
|
|
231
|
+
// direction (own keys missed an inherited field, enumerable keys missed a non-enumerable one),
|
|
232
|
+
// and each miss left the caller's object reachable behind a copy that looked complete. A verb reads
|
|
233
|
+
// an option by name, so the set that has to be fixed is the set a name lookup can reach, and
|
|
234
|
+
// nothing narrower. Object.prototype's own members are excluded: they are the language's, not the
|
|
235
|
+
// caller's, and copying them onto every spec would shadow the prototype for no gain.
|
|
236
|
+
function _reachableKeys(v) {
|
|
237
|
+
var names = [];
|
|
238
|
+
var seen = Object.create(null);
|
|
239
|
+
for (var o = v; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
240
|
+
var own = Object.getOwnPropertyNames(o);
|
|
241
|
+
for (var i = 0; i < own.length; i++) {
|
|
242
|
+
if (own[i] === "constructor" || seen[own[i]]) continue;
|
|
243
|
+
seen[own[i]] = true;
|
|
244
|
+
names.push(own[i]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return names;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// A private copy of a byte value of the same kind the caller passed. The kind is
|
|
251
|
+
// load-bearing: each verb's own field validators decide which byte forms that field accepts, and
|
|
252
|
+
// most accept only Buffer / Uint8Array. Handing them a Buffer made from a DataView or a
|
|
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
|
+
// field takes, so the copy preserves the type and the validator still sees what it was given.
|
|
256
|
+
function _copyBytesSameKind(v, ErrorClass, code, label, collect) {
|
|
257
|
+
var src = source(v, ErrorClass, code, label); // re-views; a detached backing store is refused
|
|
258
|
+
var owned = new ArrayBuffer(src.length);
|
|
259
|
+
new Uint8Array(owned).set(src);
|
|
260
|
+
// What `release` clears: a Buffer over the whole private store, whatever kind is handed back.
|
|
261
|
+
if (collect) collect.push(Buffer.from(owned, 0, src.length));
|
|
262
|
+
if (Buffer.isBuffer(v)) return Buffer.from(owned, 0, src.length);
|
|
263
|
+
if (v instanceof ArrayBuffer) return owned;
|
|
264
|
+
if (v instanceof DataView) return new DataView(owned);
|
|
265
|
+
return new v.constructor(owned, 0, src.length / v.BYTES_PER_ELEMENT);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
269
|
+
if (depth > cap) throw _raise(ErrorClass, code, label + " is nested too deeply to copy");
|
|
270
|
+
if (v == null || typeof v !== "object") return v;
|
|
271
|
+
if (Buffer.isBuffer(v) || ArrayBuffer.isView(v) || v instanceof ArrayBuffer) {
|
|
272
|
+
var bytesCopy = _copyBytesSameKind(v, ErrorClass, code, label, collect);
|
|
273
|
+
// A byte value can carry named properties too, and a verb reads an option by name whatever the
|
|
274
|
+
// argument's type: `opts = new Uint8Array(0); opts.pem = true` is an options object as far as
|
|
275
|
+
// `opts.pem` is concerned. An ArrayBuffer is extensible and takes them the same way.
|
|
276
|
+
_copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect);
|
|
277
|
+
return bytesCopy;
|
|
278
|
+
}
|
|
279
|
+
// A parsed structure carrying guard-parsed's provenance record is a handle: the record is keyed
|
|
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, 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
|
+
// Required inline because guard-parsed requires this module; at call time it is fully loaded.
|
|
286
|
+
if (require("./guard-parsed").isRecordedAsProduced(v)) return v; // allow:inline-require -- circular load: guard-parsed requires this module
|
|
287
|
+
if (v instanceof Date) return new Date(v.getTime());
|
|
288
|
+
if (Array.isArray(v)) {
|
|
289
|
+
var arr = [];
|
|
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 named properties too, and a verb reads an option by name whatever the
|
|
292
|
+
// argument's type: `opts = []; opts.pem = true` is an options object as far as `opts.pem` is
|
|
293
|
+
// concerned. Copying only the indexed elements dropped those fields, which silently changed
|
|
294
|
+
// what the verb was asked to do instead of failing.
|
|
295
|
+
_copyNamed(v, arr, ErrorClass, code, label, cap, depth, collect);
|
|
296
|
+
return arr;
|
|
297
|
+
}
|
|
298
|
+
// The only kinds this module cannot read the state of. One carrying nothing of its own is passed
|
|
299
|
+
// through: there is no data on it to fix, and a copy would break it. One carrying its own
|
|
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 case is refused outright,
|
|
302
|
+
// never half-handled, which is what keeps this from being another shape to be found later.
|
|
303
|
+
var surface = _opaqueSurface(v, ErrorClass, code, label);
|
|
304
|
+
if (surface) {
|
|
305
|
+
if (_opaqueIsSafeToPass(v, surface)) return v;
|
|
306
|
+
throw _raise(ErrorClass, code, label + ": a " + (v.constructor && v.constructor.name || "value") +
|
|
307
|
+
" carrying its own fields cannot be used here -- its state cannot be copied, so those fields " +
|
|
308
|
+
"would stay changeable after they were checked; pass the fields as a plain object");
|
|
309
|
+
}
|
|
310
|
+
if (v instanceof Map) return _copyEntries(v, new Map(), ErrorClass, code, label, cap, depth, collect);
|
|
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) must not come back
|
|
314
|
+
// inheriting from Object.prototype either, which is why the prototype is carried across and
|
|
315
|
+
// never assumed.
|
|
316
|
+
var out = Object.create(Object.getPrototypeOf(v));
|
|
317
|
+
_copyNamed(v, out, ErrorClass, code, label, cap, depth, collect);
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Which names to carry across, given what the copy is.
|
|
322
|
+
//
|
|
323
|
+
// For a plain object or a class instance (the shapes an options bag actually takes) it is every
|
|
324
|
+
// name a lookup could resolve, inherited included, because that is what the verb reads.
|
|
325
|
+
//
|
|
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
|
+
// `buffer`, `size`, and Node's `parent`, which hands back the 64 KiB arena a small Buffer was
|
|
329
|
+
// allocated from). Every one of those is already correct on the copy, and reading them would
|
|
330
|
+
// copy things the caller never passed. Enumerating the accessors to skip is the wrong way round;
|
|
331
|
+
// what the caller added is exactly what is own and not an index.
|
|
332
|
+
function _namesToCopy(src, dst) {
|
|
333
|
+
var indexed = Array.isArray(dst) || ArrayBuffer.isView(dst);
|
|
334
|
+
var kind = indexed || dst instanceof Map || dst instanceof Set || dst instanceof ArrayBuffer;
|
|
335
|
+
if (!kind) return _reachableKeys(src);
|
|
336
|
+
var own = Object.getOwnPropertyNames(src);
|
|
337
|
+
var out = [];
|
|
338
|
+
for (var i = 0; i < own.length; i++) {
|
|
339
|
+
if (own[i] === "length" || (indexed && String(Number(own[i])) === own[i])) continue;
|
|
340
|
+
out.push(own[i]);
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// A Map or a Set is data the caller can still change, entry by entry, so it is copied like any
|
|
346
|
+
// other: entries first, then the named properties one can carry alongside them.
|
|
347
|
+
function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
348
|
+
src.forEach(function (value, key) {
|
|
349
|
+
var copiedValue = _deep(value, ErrorClass, code, label, cap, depth + 1, collect);
|
|
350
|
+
if (dst instanceof Set) dst.add(copiedValue);
|
|
351
|
+
else dst.set(_deep(key, ErrorClass, code, label, cap, depth + 1, collect), copiedValue);
|
|
352
|
+
});
|
|
353
|
+
_copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect);
|
|
354
|
+
return dst;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Copy every value a name lookup on `src` could reach onto `dst`, as an own data property.
|
|
358
|
+
//
|
|
359
|
+
// Own matters: a key literally named `__proto__` would otherwise re-point `dst`, so the field the
|
|
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: an own
|
|
362
|
+
// copy is what makes an inherited value stop tracking the caller's prototype. A method is left
|
|
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
|
+
function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
366
|
+
var keys = _namesToCopy(src, dst);
|
|
367
|
+
for (var k = 0; k < keys.length; k++) {
|
|
368
|
+
var value;
|
|
369
|
+
// Reading a caller's property can run a caller's accessor, and one that throws is a bad input
|
|
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
|
+
try { value = src[keys[k]]; }
|
|
373
|
+
catch (e) { throw _raise(ErrorClass, code, label + ": reading " + JSON.stringify(keys[k]) + " threw", e); }
|
|
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
|
+
// field whose value happened to be a function stopped reaching the verb's own key check, so a
|
|
377
|
+
// typo that used to be refused was silently accepted.
|
|
378
|
+
Object.defineProperty(dst, keys[k], {
|
|
379
|
+
value: typeof value === "function" ? value
|
|
380
|
+
: _deep(value, ErrorClass, code, label, cap, depth + 1, collect),
|
|
381
|
+
writable: true, enumerable: _wasEnumerable(src, keys[k]), configurable: true,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Whether a name lookup on `v` would have found this property as an enumerable one, anywhere on
|
|
387
|
+
// the chain -- so the copy's own keys enumerate the way the original's did.
|
|
388
|
+
function _wasEnumerable(v, name) {
|
|
389
|
+
for (var o = v; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
390
|
+
var d = Object.getOwnPropertyDescriptor(o, name);
|
|
391
|
+
if (d) return !!d.enumerable;
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// fixArguments(ErrorClass, code, args) -> { values, release }
|
|
397
|
+
//
|
|
398
|
+
// The whole rule for a verb that spans a promise turn, in one call. `args` is a list of
|
|
399
|
+
// `[value, label]` pairs, one per argument the verb was handed. Each is deep-copied, so no field
|
|
400
|
+
// of any of them, at any depth, is still the caller's to change once the verb has begun; `release`
|
|
401
|
+
// clears every copy that was made and belongs in a `finally`, so a copied password or private key
|
|
402
|
+
// does not outlive the call.
|
|
403
|
+
//
|
|
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 (`opts.mac.secret` sitting a
|
|
406
|
+
// level below a shallow copy) readable and rewritable across the turn. Copying without releasing
|
|
407
|
+
// answers that by duplicating the secret instead. Deciding per field which is which is the judgment
|
|
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 break: a CryptoKey
|
|
410
|
+
// or any class instance (its identity is what makes it work) and a recorded parse result (its
|
|
411
|
+
// provenance is keyed to the object).
|
|
412
|
+
// @enforced-by behavioral -- there is no rename-proof code shape for "copied every argument". The
|
|
413
|
+
// guard is the per-verb RED vector that mutates each argument after the verb returns its promise
|
|
414
|
+
// and asserts the artifact carries the entry value.
|
|
415
|
+
function fixArguments(ErrorClass, code, args) {
|
|
416
|
+
var copies = [];
|
|
417
|
+
var values = [];
|
|
418
|
+
// Through guard.secret, which owns what clearing a secret means. Required inline because
|
|
419
|
+
// guard-secret requires this module; at call time it is fully loaded.
|
|
420
|
+
function release() {
|
|
421
|
+
require("./guard-secret").zeroizeAll(copies, ErrorClass, code, // allow:inline-require -- circular load: guard-secret requires this module
|
|
422
|
+
"a copy of a caller-supplied argument");
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
for (var i = 0; i < args.length; i++) {
|
|
426
|
+
values.push(snapshotDeep(args[i][0], ErrorClass, code, args[i][1], { collect: copies }));
|
|
427
|
+
}
|
|
428
|
+
} catch (e) {
|
|
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
|
+
// the call had gone on to succeed. Returning nothing would leave the caller with no handle to
|
|
432
|
+
// release them, so a signer key or a password copied before the fault would stay readable for
|
|
433
|
+
// as long as the heap held it. Clear what was made, then let the fault out unchanged.
|
|
434
|
+
release();
|
|
435
|
+
throw e;
|
|
436
|
+
}
|
|
437
|
+
return { values: values, release: release };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// fixedCall(ErrorClass, code, args, body) -> Promise
|
|
441
|
+
//
|
|
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 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
|
+
// synchronous throw past the caller's `.catch`. It still runs at the call, before any turn passes,
|
|
447
|
+
// which is what fixes the arguments. And the release runs whether the body resolved, rejected, or
|
|
448
|
+
// never ran, so a copied secret is cleared on every path out.
|
|
449
|
+
// @enforced-by behavioral -- an arrangement of two calls has no rename-proof shape to detect. The
|
|
450
|
+
// guards are promise-contract.test.js (every documented Promise verb refuses by rejecting, now
|
|
451
|
+
// including a fault raised by the copy itself) and the per-verb entry-value vectors.
|
|
452
|
+
function fixedCall(ErrorClass, code, args, body) {
|
|
453
|
+
var handle = null;
|
|
454
|
+
return async.deferred(function () {
|
|
455
|
+
handle = fixArguments(ErrorClass, code, args);
|
|
456
|
+
return body.apply(null, handle.values);
|
|
457
|
+
}).finally(function () { if (handle) handle.release(); });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
module.exports = {
|
|
461
|
+
view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource,
|
|
462
|
+
snapshotDeep: snapshotDeep, fixArguments: fixArguments, fixedCall: fixedCall,
|
|
463
|
+
};
|
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);
|