@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-parsed.js
CHANGED
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
// Two distinct failures follow from accepting a claimed-parsed object on a partial
|
|
19
19
|
// duck-type test, and the toolkit has had both:
|
|
20
20
|
//
|
|
21
|
-
// - A
|
|
21
|
+
// - A missing field reads as an absent feature, never as a malformed input.
|
|
22
22
|
// `crlExtensions` omitted altogether made a scope-restricted CRL answer as an
|
|
23
23
|
// unrestricted one, because every scope guard reads the list as `(list || [])`.
|
|
24
24
|
// The guard cannot fire on a list that is not there.
|
|
25
25
|
// - A partial object reaches code that dereferences a field it lacks, and the
|
|
26
|
-
// verb throws a raw TypeError instead of its own typed error
|
|
26
|
+
// verb throws a raw TypeError instead of its own typed error: a fault an
|
|
27
27
|
// operator cannot catch by code and a fuzz harness reports as a finding.
|
|
28
28
|
//
|
|
29
|
-
// The rule is completeness, checked at the
|
|
30
|
-
// output must carry
|
|
31
|
-
// that code assumes. It is not a signature check and does not pretend to be
|
|
29
|
+
// The rule is completeness, checked at the door: an object claiming to be parser
|
|
30
|
+
// output must carry every field the consuming code dereferences, each with the type
|
|
31
|
+
// that code assumes. It is not a signature check and does not pretend to be. An
|
|
32
32
|
// object can still describe a certificate that never existed, which is why the
|
|
33
33
|
// fields a decision depends on are re-derived from `tbsBytes` where that matters.
|
|
34
34
|
// What it buys is that "parsed" means one thing at every door, so a shape refused
|
|
@@ -42,7 +42,7 @@ var bytes = require("./guard-bytes");
|
|
|
42
42
|
|
|
43
43
|
// Every form the toolkit calls "bytes": a Buffer, any typed-array view, a DataView, or a bare
|
|
44
44
|
// ArrayBuffer. Testing `Buffer.isBuffer(x) || x instanceof Uint8Array` is narrower than what the
|
|
45
|
-
// verbs document, and the narrowness is invisible in the good case
|
|
45
|
+
// verbs document, and the narrowness is invisible in the good case: an ArrayBuffer would fall
|
|
46
46
|
// through to the object branch and be refused as a rebuilt structure, which reads to the caller as
|
|
47
47
|
// "your certificate is malformed" when what happened is that their container was not recognised.
|
|
48
48
|
// guard.bytes is what finally reads them, and it accepts all four, so the door has to as well.
|
|
@@ -50,16 +50,16 @@ function _isBytes(x) {
|
|
|
50
50
|
return Buffer.isBuffer(x) || ArrayBuffer.isView(x) || x instanceof ArrayBuffer;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// The shapes below assert the
|
|
53
|
+
// The shapes below assert the parser's complete output: every field
|
|
54
54
|
// pki.schema.x509.parse and pki.schema.crl.parse assign, with the type each is
|
|
55
|
-
// assigned
|
|
55
|
+
// assigned, and not the subset some consumer happens to dereference today.
|
|
56
56
|
//
|
|
57
57
|
// The distinction is the whole point. A shape derived from one consumer's reads is
|
|
58
58
|
// correct for that consumer and silently incomplete at every other door it gets
|
|
59
59
|
// reused at, and the gap is invisible: the missing field is not named
|
|
60
60
|
// anywhere, so nothing fails until a caller supplies an object without it. This
|
|
61
61
|
// guard shipped derived from the path validator's reads, and three fields the other
|
|
62
|
-
// doors depend on were absent
|
|
62
|
+
// doors depend on were absent: `serialNumber`, which pki.ocsp.buildRequest encodes
|
|
63
63
|
// into the request; `issuer.bytes`, which the OCSP responder-identity comparison
|
|
64
64
|
// reads; and an extension entry's `critical`, whose ABSENCE reads as non-critical to
|
|
65
65
|
// `if (!ext.critical) continue`, so an unknown critical extension passes unhandled.
|
|
@@ -68,7 +68,7 @@ function _isBytes(x) {
|
|
|
68
68
|
// only sanctioned way to obtain one of these objects is to run the parser, the parser
|
|
69
69
|
// assigns every field unconditionally, and so "complete" needs no consumer census and
|
|
70
70
|
// does not drift when a new consumer reads a new field. Adding a field to a parser's
|
|
71
|
-
// output belongs here in the same change
|
|
71
|
+
// output belongs here in the same change; the api-snapshot pins that surface, so
|
|
72
72
|
// the two move together.
|
|
73
73
|
|
|
74
74
|
// A directory name. `dn` is the RFC 4514 string, `bytes` the raw encoded Name that a
|
|
@@ -78,10 +78,10 @@ function _isBytes(x) {
|
|
|
78
78
|
// The walk goes all the way to the attributes. `rdns` being an array is not the
|
|
79
79
|
// property anything downstream depends on: guard-name's dnEqual compares each
|
|
80
80
|
// attribute's `type` and canonicalizes its `value`, and the WebAuthn subject-CN
|
|
81
|
-
// lookup reads `name` and `value
|
|
81
|
+
// lookup reads `name` and `value`, so an array of anything would pass a
|
|
82
82
|
// shallow test and reach code that dereferences three fields it does not have.
|
|
83
83
|
// Name ::= RDNSequence ::= SEQUENCE OF RelativeDistinguishedName, and an RDN is a
|
|
84
|
-
// SET SIZE (1..MAX), so the parser produces an array of
|
|
84
|
+
// SET SIZE (1..MAX), so the parser produces an array of non-empty arrays; the
|
|
85
85
|
// attribute value is always a string, either the decoded directory string or the
|
|
86
86
|
// RFC 4514 "#hex" form the parser falls back to for a type it does not decode.
|
|
87
87
|
function _isAttributeTypeAndValue(a) {
|
|
@@ -112,8 +112,8 @@ function _isBitString(b) {
|
|
|
112
112
|
return !!b && Buffer.isBuffer(b.bytes) && typeof b.unusedBits === "number";
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// A parsed
|
|
116
|
-
// and its `.value` the
|
|
115
|
+
// A parsed certificate extension entry: dispatched on `.oid`, gated on `.critical`,
|
|
116
|
+
// and its `.value` the raw extension bytes, which the certificate parser leaves
|
|
117
117
|
// undecoded. An entry carrying a `.name` but no `.oid` is the shape that slips past
|
|
118
118
|
// an OID-keyed lookup while reading as present to a human; one carrying no
|
|
119
119
|
// `.critical` is the shape that turns a fail-closed unknown-critical check into a
|
|
@@ -123,9 +123,9 @@ function _isExtensionEntry(e) {
|
|
|
123
123
|
typeof e.critical === "boolean" && Buffer.isBuffer(e.value);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
// A parsed CRL extension entry. The CRL parser
|
|
127
|
-
// cRLNumber surfaces as a BigInt, a reasonCode as a Number
|
|
128
|
-
// varies by extension and only its
|
|
126
|
+
// A parsed CRL extension entry. The CRL parser decodes the values it knows (a
|
|
127
|
+
// cRLNumber surfaces as a BigInt, a reasonCode as a Number), so the value type
|
|
128
|
+
// varies by extension and only its presence can be asserted here. The dispatch key
|
|
129
129
|
// and the criticality flag are the same as a certificate's, and both decide.
|
|
130
130
|
function _isCrlExtensionEntry(e) {
|
|
131
131
|
return !!e && typeof e.oid === "string" && _isOptName(e.name) &&
|
|
@@ -134,8 +134,8 @@ function _isCrlExtensionEntry(e) {
|
|
|
134
134
|
|
|
135
135
|
// isCert(o) -- the complete parsed-certificate shape.
|
|
136
136
|
//
|
|
137
|
-
// Exported as a
|
|
138
|
-
//
|
|
137
|
+
// Exported as a predicate beside the throwing door because a caller deriving a
|
|
138
|
+
// dedupe key (cmp's extraCerts pool) must answer "not usable" and never throw:
|
|
139
139
|
// a non-deduped entry is a redundant slot, while a key derived from a partial object
|
|
140
140
|
// could collapse two different certificates onto one.
|
|
141
141
|
//
|
|
@@ -160,10 +160,10 @@ function isCert(o) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// isCrl(o) -- the complete parsed-CertificateList shape. `crlExtensions` and each
|
|
163
|
-
// entry's `crlEntryExtensions` are
|
|
163
|
+
// entry's `crlEntryExtensions` are required arrays, never optional ones: the
|
|
164
164
|
// scope guards read them as `(list || [])`, so an omitted list is indistinguishable
|
|
165
165
|
// from an empty one and the difference decides whether a serial may be answered.
|
|
166
|
-
// `nextUpdate` is the one genuinely optional field
|
|
166
|
+
// `nextUpdate` is the one genuinely optional field; the parser assigns null when
|
|
167
167
|
// the CRL omits it, and a validator distinguishes the two.
|
|
168
168
|
//
|
|
169
169
|
// @enforced-by guard-shape-reinlined -- shares the `accept` shape below.
|
|
@@ -185,12 +185,12 @@ function isCrl(o) {
|
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
// Neither predicate may
|
|
189
|
-
// throws on read, and a raw fault escaping a completeness
|
|
190
|
-
// checking
|
|
188
|
+
// Neither predicate may throw. A caller-supplied object can define an accessor that
|
|
189
|
+
// throws on read, and a raw fault escaping a completeness check defeats the point of
|
|
190
|
+
// checking: the composing verb's contract is that every failure is its own typed
|
|
191
191
|
// error, and a dedupe caller using the predicate directly would get a raw fault where
|
|
192
192
|
// it expects a boolean. A property that cannot be read is not one the parser produced,
|
|
193
|
-
// so a throw is simply "not the shape". Wrapped
|
|
193
|
+
// so a throw is simply "not the shape". Wrapped here, and not at the door, because
|
|
194
194
|
// both are exported and the rule has to hold for both.
|
|
195
195
|
function _safe(shape) {
|
|
196
196
|
return function (o) {
|
|
@@ -207,20 +207,20 @@ var _SHAPES = { certificate: certShape, crl: crlShape };
|
|
|
207
207
|
|
|
208
208
|
// accept(input, kind, parse, E, code, label) -> the parsed structure | throws
|
|
209
209
|
//
|
|
210
|
-
// The
|
|
211
|
-
// handed to `parse`; an object
|
|
210
|
+
// The door every "bytes or a parsed X" argument goes through. Bytes and PEM are
|
|
211
|
+
// handed to `parse`; an object claiming to be parser output (it carries the
|
|
212
212
|
// `tbsBytes` the parser always produces) must satisfy the complete shape for
|
|
213
213
|
// `kind`; anything else is a typed input fault naming what the argument accepts.
|
|
214
214
|
//
|
|
215
215
|
// The claim test is `tbsBytes !== undefined`, not a truthiness test: an object
|
|
216
216
|
// carrying `tbsBytes: null` is claiming to be parsed and failing, and must be told
|
|
217
|
-
// so
|
|
217
|
+
// so, never sent to a byte parser that reports something unrelated.
|
|
218
218
|
//
|
|
219
219
|
// Reading the claim is itself a read of a caller-supplied object, so it goes through
|
|
220
220
|
// the same try/catch the shape walk does. A getter or Proxy trap that throws would
|
|
221
|
-
// otherwise escape from
|
|
222
|
-
// fault at every door composing this
|
|
223
|
-
// one property earlier. An unreadable claim is treated as a claim
|
|
221
|
+
// otherwise escape from outside the guarded walk and reach the operator as a raw
|
|
222
|
+
// fault at every door composing this: the failure the walk is wrapped to prevent,
|
|
223
|
+
// one property earlier. An unreadable claim is treated as a claim made and failed,
|
|
224
224
|
// matching fromTrustedSource below: an object that cannot answer what it is has not
|
|
225
225
|
// come from the parser, and saying so is more useful than reporting its type.
|
|
226
226
|
//
|
|
@@ -266,30 +266,30 @@ function accept(input, kind, parse, E, code, label) {
|
|
|
266
266
|
// derived the object from, and the door derives the structure it verifies from those
|
|
267
267
|
// bytes again.
|
|
268
268
|
//
|
|
269
|
-
// Recording the bytes
|
|
269
|
+
// Recording the bytes, and not a boolean, is what makes this hold. A flag saying
|
|
270
270
|
// "this came from the parser" is defeated by keeping a legitimately parsed object and
|
|
271
|
-
// then assigning over its fields
|
|
271
|
+
// then assigning over its fields: the flag is still there and now describes
|
|
272
272
|
// something else. Re-deriving from the recorded bytes discards every later edit,
|
|
273
273
|
// because the verdict is computed from the byte string and not from the object the
|
|
274
|
-
// caller is holding. The object a caller passes is therefore a way of
|
|
274
|
+
// caller is holding. The object a caller passes is therefore a way of naming bytes,
|
|
275
275
|
// which is all it was ever safe for it to be.
|
|
276
276
|
//
|
|
277
|
-
// The record is kept
|
|
277
|
+
// The record is kept off the object, in a WeakMap the parser owns, keyed by the
|
|
278
278
|
// returned object itself. Nothing about the record is reachable through the object,
|
|
279
279
|
// which is what makes it unforgeable:
|
|
280
280
|
//
|
|
281
|
-
// - A property, however hidden, is
|
|
281
|
+
// - A property, however hidden, is read through the object, and every read of an
|
|
282
282
|
// object is interceptable. A Proxy's getOwnPropertyDescriptor and get traps are
|
|
283
|
-
// handed whatever key they are asked about
|
|
284
|
-
// answered for
|
|
285
|
-
// lookup is on
|
|
283
|
+
// handed whatever key they are asked about (the symbol need not be known to be
|
|
284
|
+
// answered for), so a Proxy could claim any mark and name any bytes. A WeakMap
|
|
285
|
+
// lookup is on identity: a Proxy is a different object from the parser's result
|
|
286
286
|
// and simply is not a key.
|
|
287
287
|
// - `Object.create(parsed)` is likewise a different object, so it inherits no
|
|
288
288
|
// record, while `Object.assign({}, parsed)` and `{...parsed}` are different
|
|
289
|
-
// objects too. All three
|
|
290
|
-
// fall out of the same rule
|
|
289
|
+
// objects too. All three, the ways a mixed structure is actually assembled,
|
|
290
|
+
// fall out of the same rule, with no need for three checks.
|
|
291
291
|
//
|
|
292
|
-
// The recorded bytes are
|
|
292
|
+
// The recorded bytes are copied. Recording the caller's Buffer by reference would let
|
|
293
293
|
// it be overwritten between the parse and the verify, which is the same defeat by
|
|
294
294
|
// another route: the door would faithfully re-derive from bytes that had changed
|
|
295
295
|
// underneath it. A PEM string needs no copy, strings being immutable.
|
|
@@ -310,10 +310,10 @@ var PROVENANCE = new WeakMap();
|
|
|
310
310
|
// object it did not parse, or overwrite the provenance of one it did not produce.
|
|
311
311
|
// The only way to obtain a record is therefore to actually run the parse.
|
|
312
312
|
// - An exported `sourceOf(obj)` would hand back the recorded Buffer, which the caller
|
|
313
|
-
// could then overwrite
|
|
313
|
+
// could then overwrite, defeating the copy by writing through the value the copy
|
|
314
314
|
// was made to protect.
|
|
315
315
|
//
|
|
316
|
-
// The
|
|
316
|
+
// The snapshot is taken before the parse and the parser is given the snapshot, so the
|
|
317
317
|
// bytes the parser reads and the bytes the record names are the same object by
|
|
318
318
|
// construction. Taking it afterwards from the caller's argument would leave a window:
|
|
319
319
|
// a typed-array subclass with stateful `byteOffset` / `byteLength` getters, or any
|
|
@@ -327,16 +327,17 @@ function recordingParser(kind, parse, ErrorClass, code, label) {
|
|
|
327
327
|
var isText = typeof input === "string"; // strings are immutable; nothing to snapshot
|
|
328
328
|
var snap = isText ? input : bytes.snapshotSource(input, ErrorClass, code, label);
|
|
329
329
|
var out = parse(snap);
|
|
330
|
-
// The
|
|
330
|
+
// The record gets its own copy, not the buffer the parser just read.
|
|
331
331
|
//
|
|
332
|
-
// A parser surfaces raw byte ranges
|
|
333
|
-
// as
|
|
332
|
+
// A parser surfaces raw byte ranges (tbsBytes, a Name's encoded form, an extension's value)
|
|
333
|
+
// as views onto the buffer it parsed, which is what makes them exact and cheap. So the object
|
|
334
334
|
// handed back reaches into that buffer, and a caller holding it can write through those views:
|
|
335
335
|
// `parsed.tbsBytes.fill(0)` would then overwrite the very bytes the record names, and the
|
|
336
336
|
// re-derivation would faithfully reproduce whatever was written. A record the object can reach
|
|
337
337
|
// is not a record of anything. This copy is never handed out and nothing aliases it.
|
|
338
338
|
if (out && typeof out === "object") {
|
|
339
|
-
PROVENANCE.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap)
|
|
339
|
+
PROVENANCE.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap),
|
|
340
|
+
shape: _shapeOf(out) });
|
|
340
341
|
}
|
|
341
342
|
return out;
|
|
342
343
|
};
|
|
@@ -344,18 +345,18 @@ function recordingParser(kind, parse, ErrorClass, code, label) {
|
|
|
344
345
|
|
|
345
346
|
// recordingWalker(kind, walkNode, decodeBytes) -> function (node) -> the walked structure.
|
|
346
347
|
//
|
|
347
|
-
// The recordingParser sibling for a producer handed an already-
|
|
348
|
+
// The recordingParser sibling for a producer handed an already-decoded node, not bytes.
|
|
348
349
|
// One structure needs it: the RFC 7292 authSafe's SignedData. A PFX is decoded BER-tolerantly
|
|
349
350
|
// because real stores carry the indefinite-length encoding, so its inner SignedData cannot be
|
|
350
|
-
// re-derived by the strict `parse` entry the byte doors use
|
|
351
|
-
// this toolkit accepts today. The record therefore names the bytes
|
|
351
|
+
// re-derived by the strict `parse` entry the byte doors use; that entry would refuse a store
|
|
352
|
+
// this toolkit accepts today. The record therefore names the bytes and how to walk them again.
|
|
352
353
|
//
|
|
353
354
|
// The same three properties hold as for recordingParser, and for the same reasons. The record can
|
|
354
355
|
// only be obtained by actually running the walk, so no code can assert provenance for a structure
|
|
355
|
-
// it did not produce. The recorded bytes are a private
|
|
356
|
+
// it did not produce. The recorded bytes are a private copy of the node's, because the walked
|
|
356
357
|
// result surfaces views onto the buffer the PFX was decoded from and a caller holding one could
|
|
357
|
-
// otherwise write through it into the very bytes the record names. And `derive` re-runs
|
|
358
|
-
// walker over that copy, so a re-derivation reproduces the structure exactly
|
|
358
|
+
// otherwise write through it into the very bytes the record names. And `derive` re-runs this same
|
|
359
|
+
// walker over that copy, so a re-derivation reproduces the structure exactly: the decode is the
|
|
359
360
|
// producer's own, never a stricter one substituted at the door.
|
|
360
361
|
//
|
|
361
362
|
// @enforced-by guard-shape-reinlined -- shares recordingParser's shape below: the PROVENANCE.set
|
|
@@ -368,6 +369,7 @@ function recordingWalker(kind, walkNode, decodeBytes) {
|
|
|
368
369
|
PROVENANCE.set(out, {
|
|
369
370
|
kind: kind,
|
|
370
371
|
source: Buffer.from(node.bytes),
|
|
372
|
+
shape: _shapeOf(out),
|
|
371
373
|
derive: function (src) { return walkNode(decodeBytes(src)); },
|
|
372
374
|
});
|
|
373
375
|
}
|
|
@@ -383,18 +385,84 @@ function _recordOf(obj, kind) {
|
|
|
383
385
|
return (rec && rec.kind === kind) ? rec : undefined;
|
|
384
386
|
}
|
|
385
387
|
|
|
388
|
+
// isRecorded(obj) -> whether this exact object carries a provenance record, of any kind.
|
|
389
|
+
//
|
|
390
|
+
// The record is keyed on identity, so a copy of a parsed structure, however faithful, is
|
|
391
|
+
// not the parsed structure: it carries no record and every door that decides integrity refuses
|
|
392
|
+
// it. That makes a recorded object a handle, not data, and anything that walks a caller's
|
|
393
|
+
// spec taking copies has to leave it alone. guard-bytes.snapshotDeep asks this before descending.
|
|
394
|
+
// @enforced-by behavioral -- a predicate has no rename-proof shape to detect, and re-inlining it is
|
|
395
|
+
// not possible outside this module: the registry it reads is a module-private WeakMap. The guard
|
|
396
|
+
// is the RED vector that passes a parsed certificate through a producing verb's spec and asserts
|
|
397
|
+
// the integrity door still accepts it, which fails the moment a copy is taken instead.
|
|
398
|
+
function isRecorded(obj) {
|
|
399
|
+
return !!obj && typeof obj === "object" && PROVENANCE.has(obj);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// isRecordedAsProduced(obj) -> whether this is a recorded structure that still has the field set
|
|
403
|
+
// the parser gave it.
|
|
404
|
+
//
|
|
405
|
+
// A recorded structure is safe to hand on by identity, because the doors that decide integrity
|
|
406
|
+
// re-derive it from the bytes the record names and ignore whatever the object says. That reasoning
|
|
407
|
+
// covers it as a parsed value. It does not cover a caller who takes a parse result, adds an option
|
|
408
|
+
// to it, and passes it where a verb reads options by name: those fields no door re-derives, so
|
|
409
|
+
// they stay the caller's to change. The record therefore also names the shape the parser produced,
|
|
410
|
+
// and a structure that has grown fields since is no longer only a parse result.
|
|
411
|
+
// Only an added field matters. Deleting one changes nothing (the door re-derives the structure
|
|
412
|
+
// from the bytes the record names, so a missing property is not a missing value), and a caller
|
|
413
|
+
// pruning a parse result is a case the shipped vectors cover deliberately. What re-derivation does
|
|
414
|
+
// not reach is a name the parser never produced, which is exactly what an option added to a parse
|
|
415
|
+
// result is.
|
|
416
|
+
// @enforced-by behavioral -- a predicate has no rename-proof shape to detect, and the registry it
|
|
417
|
+
// reads is a module-private WeakMap so nothing outside can re-inline it. The guard is the RED
|
|
418
|
+
// vector that adds an option to a parse result, passes it where options are read by name, and
|
|
419
|
+
// asserts the value the verb used is the one that was there at entry.
|
|
420
|
+
function isRecordedAsProduced(obj) {
|
|
421
|
+
if (!isRecorded(obj)) return false;
|
|
422
|
+
var shape = PROVENANCE.get(obj).shape;
|
|
423
|
+
var keys = _allNames(obj);
|
|
424
|
+
for (var i = 0; i < keys.length; i++) {
|
|
425
|
+
if (!shape[keys[i]]) return false;
|
|
426
|
+
}
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function _shapeOf(obj) {
|
|
431
|
+
var shape = Object.create(null);
|
|
432
|
+
var keys = _allNames(obj);
|
|
433
|
+
for (var i = 0; i < keys.length; i++) shape[keys[i]] = true;
|
|
434
|
+
return shape;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Every name a `obj.field` lookup could resolve: own and inherited, enumerable or not. A verb
|
|
438
|
+
// reads an option by name and does not care how it was defined, so a comparison that only saw
|
|
439
|
+
// `Object.keys` would miss a field added with defineProperty and call the object unchanged.
|
|
440
|
+
function _allNames(obj) {
|
|
441
|
+
var names = [];
|
|
442
|
+
var seen = Object.create(null);
|
|
443
|
+
for (var o = obj; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
444
|
+
var own = Object.getOwnPropertyNames(o);
|
|
445
|
+
for (var i = 0; i < own.length; i++) {
|
|
446
|
+
if (seen[own[i]]) continue;
|
|
447
|
+
seen[own[i]] = true;
|
|
448
|
+
names.push(own[i]);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return names;
|
|
452
|
+
}
|
|
453
|
+
|
|
386
454
|
// fromTrustedSource(input, kind, claimFields, parse, E, code, why) -> the parsed
|
|
387
455
|
// structure | throws.
|
|
388
456
|
//
|
|
389
|
-
// The door for a verb that decides
|
|
390
|
-
// the parser's record is
|
|
457
|
+
// The door for a verb that decides integrity. Bytes are parsed. An object carrying
|
|
458
|
+
// the parser's record is re-parsed from the bytes that record names, so a caller
|
|
391
459
|
// may keep passing the parsed form, and anything done to that object since it was
|
|
392
|
-
// parsed is discarded
|
|
393
|
-
//
|
|
460
|
+
// parsed is discarded, never trusted. An object that claims to be parser output
|
|
461
|
+
// (it carries a field only a parsed structure of this kind has) but carries no
|
|
394
462
|
// record was not produced by the parser, and is refused with the caller's own reason.
|
|
395
463
|
//
|
|
396
|
-
// `claimFields` exist so that refusal names what happened,
|
|
397
|
-
// object to a byte parser that would report something about its type
|
|
464
|
+
// `claimFields` exist so that refusal names what happened, instead of handing the
|
|
465
|
+
// object to a byte parser that would report something about its type.
|
|
398
466
|
//
|
|
399
467
|
// @enforced-by guard-shape-reinlined
|
|
400
468
|
// @guard-shape (?:responseStatus|integrityMode|macedBytes|tbsResponseDataBytes)\s*!==\s*undefined
|
|
@@ -422,38 +490,38 @@ function fromTrustedSource(input, kind, claimFields, parse, E, code, why) {
|
|
|
422
490
|
//
|
|
423
491
|
// acceptDerived(input, kind, parse, E, code, label) -> the parsed structure | throws.
|
|
424
492
|
//
|
|
425
|
-
//
|
|
426
|
-
// the parser's own result is re-derived from the bytes it recorded; an object that claims to
|
|
427
|
-
// without that record is refused.
|
|
493
|
+
// The single door for a certificate or a CRL, at every boundary that takes one. Bytes and PEM are
|
|
494
|
+
// parsed; the parser's own result is re-derived from the bytes it recorded; an object that claims to
|
|
495
|
+
// be one without that record is refused.
|
|
428
496
|
//
|
|
429
|
-
// It is one rule for every door
|
|
497
|
+
// It is one rule for every door, and never a rule for the doors that "reach a verdict", because
|
|
430
498
|
// that judgment is the thing that keeps being got wrong. A certificate is a trust decision wherever
|
|
431
499
|
// it appears: an issuer certificate handed to a signer names who the new certificate says issued it;
|
|
432
|
-
// a root handed to an attestation check
|
|
500
|
+
// a root handed to an attestation check is the anchor; a certificate handed to a request builder
|
|
433
501
|
// names what the request asks about. Sorting them into verdict and non-verdict doors invites exactly
|
|
434
|
-
// the substitution this prevents at whichever door was sorted wrong
|
|
502
|
+
// the substitution this prevents at whichever door was sorted wrong, so no door is sorted at all.
|
|
435
503
|
//
|
|
436
504
|
// The claim fields are the ones a parsed structure has and a byte input does not, so an object that
|
|
437
|
-
// was rebuilt is told what happened
|
|
438
|
-
// about its type
|
|
505
|
+
// was rebuilt is told what happened, never handed to a byte parser that would report something
|
|
506
|
+
// about its type.
|
|
439
507
|
var _CLAIMS = {
|
|
440
508
|
certificate: ["tbsBytes", "subjectPublicKeyInfo", "serialNumberHex"],
|
|
441
509
|
crl: ["tbsBytes", "revokedCertificates", "crlExtensions"],
|
|
442
510
|
cms: ["signerInfos", "encapContentInfo"],
|
|
443
511
|
};
|
|
444
512
|
var _WHY = {
|
|
445
|
-
certificate: "the signed byte range, the signature and the fields that range encodes are separate properties of a parsed object, so a
|
|
446
|
-
crl: "the signed byte range, the revocation list and the scope extensions are separate properties of a parsed object, so a
|
|
447
|
-
cms: "the signed attribute bytes, the signature, the encapsulated content and the certificates that verify it are separate properties of a parsed object, so a
|
|
513
|
+
certificate: "the signed byte range, the signature and the fields that range encodes are separate properties of a parsed object, so a rebuilt certificate (Object.assign, spread, a JSON round-trip) could have them describe different certificates: keep a real CA certificate's signed bytes and signature, replace only its public key, and every field is still well-formed",
|
|
514
|
+
crl: "the signed byte range, the revocation list and the scope extensions are separate properties of a parsed object, so a rebuilt CRL could have them describe different CRLs: empty the revocation list and a correctly signed CRL reports a revoked certificate as good",
|
|
515
|
+
cms: "the signed attribute bytes, the signature, the encapsulated content and the certificates that verify it are separate properties of a parsed object, so a rebuilt SignedData could have them describe different messages: keep a genuine signer's signature and signed attributes, put other content beside them, and every part of the check passes for content that signer never signed",
|
|
448
516
|
};
|
|
449
517
|
// @enforced-by guard-shape-reinlined -- shares the fromTrustedSource shape it composes: a door that
|
|
450
|
-
// tests the claim fields itself,
|
|
518
|
+
// tests the claim fields itself, instead of routing here, is the re-inline both replace.
|
|
451
519
|
function acceptDerived(input, kind, parse, E, code, label) {
|
|
452
520
|
var claims = _CLAIMS[kind];
|
|
453
521
|
if (!claims) throw new TypeError("guard.parsed.acceptDerived: unknown kind " + kind);
|
|
454
522
|
var who = label || "the argument";
|
|
455
|
-
// An object that is neither bytes nor a claim is named as the wrong
|
|
456
|
-
// to the byte parser. The parser would refuse it too, but with its own domain's code
|
|
523
|
+
// An object that is neither bytes nor a claim is named as the wrong type here, never handed
|
|
524
|
+
// to the byte parser. The parser would refuse it too, but with its own domain's code: a caller
|
|
457
525
|
// who passed the wrong thing to pki.path.validate should read a path/* fault, not an x509/* one
|
|
458
526
|
// from a layer they did not call.
|
|
459
527
|
if (input !== null && input !== undefined && typeof input === "object" && !_isBytes(input)) {
|
|
@@ -478,5 +546,6 @@ module.exports = {
|
|
|
478
546
|
accept: accept, acceptDerived: acceptDerived,
|
|
479
547
|
fromTrustedSource: fromTrustedSource, recordingParser: recordingParser,
|
|
480
548
|
recordingWalker: recordingWalker,
|
|
481
|
-
isCert: certShape, isCrl: crlShape,
|
|
549
|
+
isCert: certShape, isCrl: crlShape, isRecorded: isRecorded,
|
|
550
|
+
isRecordedAsProduced: isRecordedAsProduced,
|
|
482
551
|
};
|
package/lib/guard-range.js
CHANGED
|
@@ -6,25 +6,25 @@
|
|
|
6
6
|
// parsers whose decoded-integer bounds compose this guard (pki.schema.x509 /
|
|
7
7
|
// crl / pkcs12 / cmp, pki.path.validate).
|
|
8
8
|
//
|
|
9
|
-
// guard-range -- fail-closed bound of an untrusted
|
|
9
|
+
// guard-range -- fail-closed bound of an untrusted decoded integer before it is
|
|
10
10
|
// narrowed to a JS Number. Parse-time (Tier-2): the value is malformed content,
|
|
11
|
-
// so it throws the caller's typed PkiError
|
|
11
|
+
// so it throws the caller's typed PkiError, distinct from guard-limits, which
|
|
12
12
|
// validates an operator-supplied option at config time (Tier-1, TypeError).
|
|
13
13
|
//
|
|
14
14
|
// Defends the silent-narrowing class (a decoded ASN.1 INTEGER past 2^53 rounds
|
|
15
|
-
// when coerced to a Number, so a caller acting on the result
|
|
16
|
-
// skipCerts
|
|
15
|
+
// when coerced to a Number, so a caller acting on the result, a pathLen,
|
|
16
|
+
// skipCerts, saltLength or iteration counter, acts on the wrong number;
|
|
17
17
|
// CWE-190 integer overflow / CWE-681 incorrect conversion). The bound and the
|
|
18
|
-
// narrow are
|
|
18
|
+
// narrow are atomic here: a caller cannot obtain the Number without the range
|
|
19
19
|
// having been enforced, so the value can never round silently and be acted on.
|
|
20
20
|
|
|
21
|
-
// 2^31 - 1
|
|
22
|
-
// This literal lives
|
|
21
|
+
// 2^31 - 1, the shared skip-cert / path-length / salt / iteration ceiling.
|
|
22
|
+
// This literal lives only here (a re-inline anywhere else is flagged: the
|
|
23
23
|
// guard-shape walk anchors on the literal). 2^53 - 1 is the safe-narrow ceiling.
|
|
24
24
|
var UINT31_MAX = 2147483647n;
|
|
25
25
|
var SAFE_MAX = 9007199254740991n;
|
|
26
26
|
var SAFE_MIN = -9007199254740991n;
|
|
27
|
-
// 2^64 - 1 (also spelled 0xffffffffffffffffn). The uint64 ceiling lives
|
|
27
|
+
// 2^64 - 1 (also spelled 0xffffffffffffffffn). The uint64 ceiling lives only here
|
|
28
28
|
// (both spellings are flagged as a re-inline; see uint64's @guard-shape lines).
|
|
29
29
|
var UINT64_MAX = 18446744073709551615n;
|
|
30
30
|
|
|
@@ -40,14 +40,14 @@ var UINT64_MAX = 18446744073709551615n;
|
|
|
40
40
|
// @enforced-by guard-shape-reinlined
|
|
41
41
|
// @guard-shape 2147483647n
|
|
42
42
|
function int(value, min, max, E, code, label) {
|
|
43
|
-
// Config-time authoring guard (Tier-1): the bounds must be BigInt
|
|
43
|
+
// Config-time authoring guard (Tier-1): the bounds must be BigInt. A Number
|
|
44
44
|
// bound (worst: NaN, which compares false against every BigInt) silently
|
|
45
|
-
// disables the range so
|
|
46
|
-
// is lossless only when the
|
|
47
|
-
// [-(2^53-1), 2^53-1]. Both ends bind
|
|
45
|
+
// disables the range so any decoded value narrows. Then: narrowing to Number
|
|
46
|
+
// is lossless only when the whole range sits inside the safe-integer band
|
|
47
|
+
// [-(2^53-1), 2^53-1]. Both ends bind, since a min below the floor rounds a
|
|
48
48
|
// decoded value near it just as a max above the ceiling does. A wider range
|
|
49
|
-
// needs a BigInt-preserving guard, not this one
|
|
50
|
-
// Merkle-coordinate domain must
|
|
49
|
+
// needs a BigInt-preserving guard, not this one, which is why the uint64
|
|
50
|
+
// Merkle-coordinate domain must not route here.
|
|
51
51
|
if (typeof min !== "bigint" || typeof max !== "bigint") {
|
|
52
52
|
throw new TypeError("guard.range.int: the min/max bounds must be BigInt");
|
|
53
53
|
}
|
|
@@ -74,12 +74,12 @@ function uint31(value, E, code, label) { return int(value, 0n, UINT31_MAX, E, co
|
|
|
74
74
|
// @enforced-by guard-shape-reinlined (shares the 2147483647n shape declared on int)
|
|
75
75
|
function positiveInt31(value, E, code, label) { return int(value, 1n, UINT31_MAX, E, code, label); }
|
|
76
76
|
|
|
77
|
-
// uint64(value, E, code, label) -> a BigInt in [0, 2^64-1],
|
|
77
|
+
// uint64(value, E, code, label) -> a BigInt in [0, 2^64-1], without narrowing to
|
|
78
78
|
// Number. Accepts a non-negative safe-integer Number (widened to BigInt) or a
|
|
79
79
|
// BigInt; a Number past 2^53 (precision already lost), a negative, a non-integer,
|
|
80
80
|
// or a non-number-non-bigint throws the caller's typed verdict. The uint64 domain
|
|
81
|
-
// (a Merkle tree coordinate RFC 9162, an SCT timestamp RFC 6962) must
|
|
82
|
-
// through int()
|
|
81
|
+
// (a Merkle tree coordinate RFC 9162, an SCT timestamp RFC 6962) must not route
|
|
82
|
+
// through int(), since narrowing to Number would corrupt the very value it guards, so
|
|
83
83
|
// this is the BigInt-preserving sibling int()'s authoring guard points callers to.
|
|
84
84
|
// @enforced-by guard-shape-reinlined
|
|
85
85
|
// @guard-shape 18446744073709551615n
|
|
@@ -102,9 +102,9 @@ function uint64(value, E, code, label) {
|
|
|
102
102
|
// authoredInteger(value, E, code, label) -> BigInt. The AUTHORING counterpart to
|
|
103
103
|
// int(): a value a CALLER supplies for an ASN.1 INTEGER with no upper bound (a CMC
|
|
104
104
|
// Transaction Identifier, a CRMF certReqId), normalized to the BigInt the builders
|
|
105
|
-
// take. Accepts a bigint, or a Number that is a
|
|
105
|
+
// take. Accepts a bigint, or a Number that is a safe integer (isSafeInteger, not
|
|
106
106
|
// isInteger, because a Number above 2^53 has already lost precision by the time it
|
|
107
|
-
// arrives, so a large identifier MUST come as a bigint
|
|
107
|
+
// arrives), so a large identifier MUST come as a bigint and cannot be silently
|
|
108
108
|
// rounded to a neighbour. Everything else is a config-time reject.
|
|
109
109
|
//
|
|
110
110
|
// Distinct from int() on purpose: int() BOUNDS a value the wire decoded and narrows
|
package/lib/guard-secret.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// window. It does not support a claim that a secret never persists in memory,
|
|
24
24
|
// and no operator-facing text may imply that it does.
|
|
25
25
|
//
|
|
26
|
-
//
|
|
26
|
+
// Ownership is the contract. Only a buffer the toolkit allocated may be wiped:
|
|
27
27
|
// never a caller's opts.key / opts.cert / opts.kek / opts.password, and never the
|
|
28
28
|
// input DER. Silently destroying a caller's own memory is a worse defect than
|
|
29
29
|
// leaving a secret readable, and it is the failure mode a zeroization patch
|
|
@@ -38,7 +38,7 @@ var bytes = require("./guard-bytes");
|
|
|
38
38
|
// `{ withCause: true }`. The guard family carries two currencies --
|
|
39
39
|
// most guards take a (code, message) factory and call it without
|
|
40
40
|
// `new`, while guard-bytes / guard-header take the class and
|
|
41
|
-
// construct it. This module's
|
|
41
|
+
// construct it. This module's one throw is the delegated re-view
|
|
42
42
|
// below, so it must pass what guard-bytes expects: the class, and
|
|
43
43
|
// one that accepts a cause, because guard-bytes threads the raw
|
|
44
44
|
// detach fault through as one. A plain class fails to construct at
|
|
@@ -47,13 +47,14 @@ var bytes = require("./guard-bytes");
|
|
|
47
47
|
// label : field phrase for the message.
|
|
48
48
|
//
|
|
49
49
|
// A detached ArrayBuffer cannot be written, and reaching one here means a caller
|
|
50
|
-
// handed over memory that was transferred away
|
|
51
|
-
// swallow, so it routes through the shared re-view guard and throws
|
|
50
|
+
// handed over memory that was transferred away. That is a real fault and not
|
|
51
|
+
// something to swallow, so it routes through the shared re-view guard and throws
|
|
52
|
+
// typed.
|
|
52
53
|
//
|
|
53
|
-
// The `.fill(0)` shape lives
|
|
54
|
-
// lib
|
|
55
|
-
//
|
|
56
|
-
//
|
|
54
|
+
// The `.fill(0)` shape lives only in this module. A wipe re-inlined anywhere in
|
|
55
|
+
// lib/, including a module not yet written, is flagged, so the safe implementation
|
|
56
|
+
// is also the tripwire that stops the next consumer from rolling its own partial
|
|
57
|
+
// one.
|
|
57
58
|
// @enforced-by guard-shape-reinlined
|
|
58
59
|
// @guard-shape \.fill\s*\(\s*0\s*[,)]
|
|
59
60
|
function zeroize(value, ErrorClass, code, label) {
|
|
@@ -65,14 +66,14 @@ function zeroize(value, ErrorClass, code, label) {
|
|
|
65
66
|
return value;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
// zeroizeAll(list, ErrorClass, code, label)
|
|
69
|
+
// zeroizeAll(list, ErrorClass, code, label) wipes every present member, tolerating
|
|
69
70
|
// holes so a `finally` can name intermediates that may not have been reached.
|
|
70
71
|
//
|
|
71
72
|
// @enforced-by behavioral -- this is a loop over zeroize, which carries the family's only
|
|
72
73
|
// rename-proof shape (the `.fill(0)` above). It introduces no shape of its own, so a lexical
|
|
73
74
|
// detector here would anchor on a renameable symbol and go silently green (drift rule sec. 3).
|
|
74
75
|
// The behavioural guards are guard-secret.test.js (holes tolerated, every member cleared) and the
|
|
75
|
-
// CMS vectors that assert the shared secret and KEK are wiped on
|
|
76
|
+
// CMS vectors that assert the shared secret and KEK are wiped on both the success and failure paths.
|
|
76
77
|
function zeroizeAll(list, ErrorClass, code, label) {
|
|
77
78
|
if (!list) return list;
|
|
78
79
|
for (var i = 0; i < list.length; i++) zeroize(list[i], ErrorClass, code, label);
|
package/lib/guard-text.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// PEM decoders, the EST transfer decoders).
|
|
8
8
|
//
|
|
9
9
|
// guard-text -- fail-closed decode of an untrusted byte-source input to a
|
|
10
|
-
// string, capping the
|
|
10
|
+
// string, capping the raw byte length before the string is materialized.
|
|
11
11
|
// Enforced choke point for the byte->string boundary.
|
|
12
12
|
//
|
|
13
13
|
// Defends the parser-DoS string-allocation class (CWE-770 allocation without
|
|
@@ -25,10 +25,10 @@ var LATIN1 = "latin1";
|
|
|
25
25
|
|
|
26
26
|
// decode(input, maxBytes, ErrorClass, spec) -> string.
|
|
27
27
|
// input : a Buffer (decoded per spec.charset) or a string (taken as-is).
|
|
28
|
-
// maxBytes : the raw byte-length ceiling, checked
|
|
28
|
+
// maxBytes : the raw byte-length ceiling, checked before any string copy.
|
|
29
29
|
// ErrorClass: a PkiError subclass (withCause where spec.fatal is set).
|
|
30
30
|
// spec : { charset: "latin1"|"utf-8" (default latin1),
|
|
31
|
-
// fatal: boolean
|
|
31
|
+
// fatal: boolean, strict UTF-8 (a lone continuation /
|
|
32
32
|
// truncated sequence throws, never substitutes U+FFFD),
|
|
33
33
|
// tooLarge: code for the over-cap reject,
|
|
34
34
|
// badDecode: code for a fatal-charset (bad-UTF-8) reject
|
|
@@ -40,15 +40,15 @@ var LATIN1 = "latin1";
|
|
|
40
40
|
// enforced) are the guard.
|
|
41
41
|
function decode(input, maxBytes, ErrorClass, spec) {
|
|
42
42
|
// maxBytes is an authoring input: an undefined / NaN / fractional cap makes
|
|
43
|
-
// the `length > maxBytes` comparison always false
|
|
44
|
-
//
|
|
43
|
+
// the `length > maxBytes` comparison always false, silently disabling the size
|
|
44
|
+
// cap on the guard whose whole contract is cap-before-copy. Config-time
|
|
45
45
|
// TypeError (every composing boundary passes a C.LIMITS constant).
|
|
46
46
|
if (!Number.isInteger(maxBytes) || maxBytes < 0) {
|
|
47
47
|
throw new TypeError("guard.text.decode: maxBytes must be a non-negative integer");
|
|
48
48
|
}
|
|
49
49
|
var charset = spec.charset || LATIN1;
|
|
50
50
|
if (Buffer.isBuffer(input)) {
|
|
51
|
-
// Re-view through the byte guard
|
|
51
|
+
// Re-view through the byte guard first so a detached backing ArrayBuffer (a
|
|
52
52
|
// transferred / structuredClone'd Buffer, which reads as zero-length) fails
|
|
53
53
|
// closed here -- the same detached-buffer defence the byte boundaries get --
|
|
54
54
|
// instead of being decoded as an empty string. Then cap, then decode.
|