@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +419 -378
  2. package/MIGRATING.md +65 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +24 -20
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +104 -104
  11. package/lib/cmc-verify.js +108 -32
  12. package/lib/cmp-build.js +31 -26
  13. package/lib/cmp-session.js +74 -72
  14. package/lib/cmp-verify.js +72 -58
  15. package/lib/cms-compress.js +8 -9
  16. package/lib/cms-decrypt.js +92 -76
  17. package/lib/cms-encrypt.js +33 -34
  18. package/lib/cms-sign.js +100 -54
  19. package/lib/cms-verify.js +141 -82
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +31 -25
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +19 -15
  25. package/lib/csr-sign.js +13 -9
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +103 -59
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +3 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +378 -15
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-encoding.js +15 -15
  36. package/lib/guard-header.js +3 -3
  37. package/lib/guard-identifier.js +16 -16
  38. package/lib/guard-json.js +15 -15
  39. package/lib/guard-limits.js +7 -7
  40. package/lib/guard-name.js +81 -16
  41. package/lib/guard-parsed.js +144 -75
  42. package/lib/guard-range.js +19 -19
  43. package/lib/guard-secret.js +11 -10
  44. package/lib/guard-text.js +6 -6
  45. package/lib/guard-time.js +10 -10
  46. package/lib/hpke.js +18 -17
  47. package/lib/http-digest.js +35 -35
  48. package/lib/http-retry-after.js +13 -13
  49. package/lib/http-transport.js +20 -19
  50. package/lib/inspect.js +53 -53
  51. package/lib/ip-utils.js +2 -2
  52. package/lib/jose.js +13 -13
  53. package/lib/key.js +16 -16
  54. package/lib/lint.js +51 -51
  55. package/lib/merkle.js +51 -36
  56. package/lib/mime.js +18 -18
  57. package/lib/ocsp-verify.js +10 -10
  58. package/lib/ocsp.js +32 -20
  59. package/lib/oid.js +29 -29
  60. package/lib/path-validate.js +114 -113
  61. package/lib/pbes2.js +16 -16
  62. package/lib/pkcs12-build.js +71 -56
  63. package/lib/pki-build.js +23 -22
  64. package/lib/rc2.js +1 -1
  65. package/lib/rfc3339.js +5 -5
  66. package/lib/schema-all.js +31 -31
  67. package/lib/schema-attrcert.js +12 -12
  68. package/lib/schema-c509.js +144 -142
  69. package/lib/schema-cmc.js +58 -58
  70. package/lib/schema-cmp.js +43 -43
  71. package/lib/schema-cms.js +169 -36
  72. package/lib/schema-crl.js +7 -7
  73. package/lib/schema-crmf.js +28 -28
  74. package/lib/schema-csr.js +12 -12
  75. package/lib/schema-csrattrs.js +16 -16
  76. package/lib/schema-engine.js +18 -18
  77. package/lib/schema-ocsp.js +15 -15
  78. package/lib/schema-pkcs12.js +20 -20
  79. package/lib/schema-pkcs8.js +2 -2
  80. package/lib/schema-pkix.js +131 -126
  81. package/lib/schema-smime.js +19 -19
  82. package/lib/schema-tsp.js +12 -12
  83. package/lib/schema-x509.js +3 -3
  84. package/lib/shbs.js +18 -18
  85. package/lib/sign-scheme.js +19 -16
  86. package/lib/sigstore.js +10 -11
  87. package/lib/sleep.js +1 -1
  88. package/lib/smime.js +308 -96
  89. package/lib/tls-cert-compress.js +18 -18
  90. package/lib/trust.js +27 -27
  91. package/lib/tsp-sign.js +22 -18
  92. package/lib/validator-all.js +1 -1
  93. package/lib/validator-attcert.js +1 -1
  94. package/lib/validator-cose.js +43 -44
  95. package/lib/validator-keydesc.js +3 -3
  96. package/lib/validator-sig.js +13 -13
  97. package/lib/validator-tls.js +11 -11
  98. package/lib/validator-tpm.js +21 -20
  99. package/lib/webauthn-mds.js +67 -67
  100. package/lib/webauthn.js +34 -34
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +24 -15
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
@@ -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 NOT enforced here -- they
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 new ErrorClass(code, label + ": input is not a usable byte view (detached backing buffer?)", e);
59
+ throw _raise(ErrorClass, code, label + ": input is not a usable byte view (detached backing buffer?)", e);
38
60
  }
39
61
  }
40
- throw new ErrorClass(code, label + ": expected a Buffer / Uint8Array");
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-VIEWS in every case, sharing the caller's
46
- // memory rather than taking a copy: `Buffer.from(arrayBuffer)` wraps the backing
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 -- nothing is materialized -- and it is also why a
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, rather than this. Throws on a detached backing store.
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 new ErrorClass(code, label + ": input is not a usable byte source (detached backing buffer?)", e);
80
+ throw _raise(ErrorClass, code, label + ": input is not a usable byte source (detached backing buffer?)", e);
59
81
  }
60
82
  }
61
- throw new ErrorClass(code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
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 -- the signed
70
- // content, the signer set, the values the verdict is built from -- is a VIEW into
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 EVERYTHING from it. `view`
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 FULL W3C BufferSource
90
- // (raw ArrayBuffer / DataView / any typed-array view) rather than only the
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
- module.exports = { view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource };
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
+ };
@@ -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 FACTORY -- never an 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 ONLY by the truncation probe below. Producing a compressed stream is
49
- // not the guarded shape -- decompressing an untrusted one is.
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 -- so neither the returned bytes nor the
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 rather than accepted with a defect. Consumers build
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 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.
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 -- and forcing them
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 rather than nothing at all.
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 REFUSED. A returned value of any length -- including the empty buffer
91
- // some bindings hand back -- means a truncation would pass through unseen.
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 rather than silently.
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 rather than a config error.
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 -- the malleability
146
- // above -- so the whole input must be exactly one frame.
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 SAFELY on this runtime -- the registry
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.
@@ -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 BEFORE its bytes reach a verifier.
28
+ // with unusedBits !== 0 fails closed before its bytes reach a verifier.
29
29
 
30
30
  var nodeCrypto = require("node:crypto");
31
31
 
@@ -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, so
14
- // two distinct texts alias one byte string -- or a malformed text decodes to a
15
- // SHORTER, different value than intended. And it allocates the whole decode
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 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
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 TABLES, walked one character at a time, rather than regular
29
- // expressions. A guard runs on the most hostile input the toolkit sees, and a
30
- // pattern engine's cost on a non-matching string is a property of the pattern
31
- // rather than of the length -- the one thing a bound cannot be placed on from the
32
- // outside. A table lookup is one array index per character and its cost is the
33
- // length, which the caller already caps. It is also the more honest statement of
34
- // the rule: the set of permitted characters IS the rule, written out.
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, so the table bound
47
- // is the reject rather than an index that reads undefined.
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);