@blamejs/pki 0.5.15 → 0.5.16
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 +27 -0
- package/lib/cmp-verify.js +425 -82
- package/lib/guard-all.js +2 -0
- package/lib/guard-async.js +14 -2
- package/lib/guard-bytes.js +183 -93
- package/lib/guard-compress.js +32 -12
- package/lib/guard-der.js +30 -7
- package/lib/guard-encoding.js +28 -12
- package/lib/guard-header.js +18 -7
- package/lib/guard-identifier.js +146 -79
- package/lib/guard-intrinsic.js +318 -0
- package/lib/guard-json.js +39 -12
- package/lib/guard-limits.js +15 -6
- package/lib/guard-name.js +72 -23
- package/lib/guard-parsed.js +71 -34
- package/lib/guard-range.js +17 -4
- package/lib/guard-secret.js +15 -1
- package/lib/guard-text.js +35 -6
- package/lib/guard-time.js +13 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-parsed.js
CHANGED
|
@@ -40,9 +40,41 @@
|
|
|
40
40
|
|
|
41
41
|
var bytes = require("./guard-bytes");
|
|
42
42
|
var time = require("./guard-time");
|
|
43
|
+
// The array test, taken at load. Every shape check below asks it whether a field IS a list, and
|
|
44
|
+
// one answering `false` makes each of those checks reject a well-formed parsed structure -- the
|
|
45
|
+
// fail-closed direction, and still a caller deciding what this guard recognizes.
|
|
46
|
+
var _isArray = require("./guard-intrinsic").isArray;
|
|
47
|
+
// The copy that becomes the recorded provenance bytes. A replacement returning a buffer over the
|
|
48
|
+
// caller's own memory would let those bytes change after they were recorded, which is the one
|
|
49
|
+
// thing provenance exists to prevent.
|
|
50
|
+
var _bufferFrom = require("./guard-intrinsic").bufferFrom;
|
|
51
|
+
// The sentinel the chain walk below stops at. Reached as a property of a replaceable global, a
|
|
52
|
+
// replacement makes the walk run past the top and report inherited names as the value's own.
|
|
53
|
+
var _ObjectProto = require("./guard-intrinsic").ObjectProto;
|
|
54
|
+
// The rest of what this module reads from the runtime, taken at load. The shape checks below ask
|
|
55
|
+
// these what a value IS and how it is put together, so a replacement decides what this guard
|
|
56
|
+
// recognizes as parser output. See guard-intrinsic for the whole captured set.
|
|
57
|
+
var _isBuffer = require("./guard-intrinsic").isBuffer;
|
|
58
|
+
var _create = require("./guard-intrinsic").create;
|
|
59
|
+
var _assign = require("./guard-intrinsic").assign;
|
|
60
|
+
var _getPrototypeOf = require("./guard-intrinsic").getPrototypeOf;
|
|
61
|
+
var _getOwnPropertyNames = require("./guard-intrinsic").getOwnPropertyNames;
|
|
62
|
+
// The array operations, uncurried. Each shape check below asks `every` whether a list's members
|
|
63
|
+
// are all well formed, so one replaced to return true makes every malformed list pass.
|
|
64
|
+
var _every = require("./guard-intrinsic").every;
|
|
65
|
+
// The provenance store's own operations. This WeakMap is what says a value came out of this
|
|
66
|
+
// toolkit's parser, so a `get` that answers for a key never stored hands the door a forged
|
|
67
|
+
// provenance record, and a `has` that answers true admits an object the parser never produced.
|
|
68
|
+
var _weakGet = require("./guard-intrinsic").weakGet;
|
|
69
|
+
var _weakSet = require("./guard-intrinsic").weakSet;
|
|
70
|
+
var _weakHas = require("./guard-intrinsic").weakHas;
|
|
71
|
+
var _WeakMap = require("./guard-intrinsic").WeakMap;
|
|
72
|
+
var _defineProperty = require("./guard-intrinsic").defineProperty;
|
|
73
|
+
// Appended by index rather than through `push`, so the growth reads no prototype method at all.
|
|
74
|
+
function _append(arr, v) { _defineProperty(arr, arr.length, { value: v, writable: true, enumerable: true, configurable: true }); }
|
|
43
75
|
|
|
44
76
|
// Every form the toolkit calls "bytes": a Buffer, any typed-array view, a DataView, or a bare
|
|
45
|
-
// ArrayBuffer. Testing `
|
|
77
|
+
// ArrayBuffer. Testing `_isBuffer(x) || x instanceof Uint8Array` is narrower than what the
|
|
46
78
|
// verbs document, and the narrowness is invisible in the good case: an ArrayBuffer would fall
|
|
47
79
|
// through to the object branch and be refused as a rebuilt structure, which reads to the caller as
|
|
48
80
|
// "your certificate is malformed" when what happened is that their container was not recognized.
|
|
@@ -92,15 +124,15 @@ function _isAttributeTypeAndValue(a) {
|
|
|
92
124
|
return !!a && typeof a.type === "string" && _isOptName(a.name) && typeof a.value === "string";
|
|
93
125
|
}
|
|
94
126
|
function _isRdnSequence(rdns) {
|
|
95
|
-
if (!
|
|
127
|
+
if (!_isArray(rdns)) return false;
|
|
96
128
|
for (var i = 0; i < rdns.length; i++) {
|
|
97
|
-
if (!
|
|
98
|
-
if (!rdns[i]
|
|
129
|
+
if (!_isArray(rdns[i]) || rdns[i].length === 0) return false;
|
|
130
|
+
if (!_every(rdns[i], _isAttributeTypeAndValue)) return false;
|
|
99
131
|
}
|
|
100
132
|
return true;
|
|
101
133
|
}
|
|
102
134
|
function _isName(n) {
|
|
103
|
-
return !!n && _isRdnSequence(n.rdns) &&
|
|
135
|
+
return !!n && _isRdnSequence(n.rdns) && _isBuffer(n.bytes) && typeof n.dn === "string";
|
|
104
136
|
}
|
|
105
137
|
|
|
106
138
|
// `name` is the registry lookup of the OID and is null for an OID the registry does
|
|
@@ -109,11 +141,11 @@ function _isOptName(v) { return typeof v === "string" || v === null; }
|
|
|
109
141
|
|
|
110
142
|
function _isAlgorithmIdentifier(a) {
|
|
111
143
|
return !!a && typeof a.oid === "string" && _isOptName(a.name) &&
|
|
112
|
-
(
|
|
144
|
+
(_isBuffer(a.parameters) || a.parameters === null);
|
|
113
145
|
}
|
|
114
146
|
|
|
115
147
|
function _isBitString(b) {
|
|
116
|
-
return !!b &&
|
|
148
|
+
return !!b && _isBuffer(b.bytes) && typeof b.unusedBits === "number";
|
|
117
149
|
}
|
|
118
150
|
|
|
119
151
|
// A parsed certificate extension entry: dispatched on `.oid`, gated on `.critical`,
|
|
@@ -124,7 +156,7 @@ function _isBitString(b) {
|
|
|
124
156
|
// skipped iteration.
|
|
125
157
|
function _isExtensionEntry(e) {
|
|
126
158
|
return !!e && typeof e.oid === "string" && _isOptName(e.name) &&
|
|
127
|
-
typeof e.critical === "boolean" &&
|
|
159
|
+
typeof e.critical === "boolean" && _isBuffer(e.value);
|
|
128
160
|
}
|
|
129
161
|
|
|
130
162
|
// A parsed CRL extension entry. The CRL parser decodes the values it knows (a
|
|
@@ -147,7 +179,7 @@ function _isCrlExtensionEntry(e) {
|
|
|
147
179
|
// testing tbsBytes and returning the object is the re-inline this replaces.
|
|
148
180
|
function isCert(o) {
|
|
149
181
|
return !!o && typeof o === "object" &&
|
|
150
|
-
|
|
182
|
+
_isBuffer(o.tbsBytes) &&
|
|
151
183
|
typeof o.version === "number" &&
|
|
152
184
|
typeof o.serialNumber === "bigint" &&
|
|
153
185
|
typeof o.serialNumberHex === "string" &&
|
|
@@ -157,10 +189,10 @@ function isCert(o) {
|
|
|
157
189
|
!!o.validity && time.isDate(o.validity.notBefore) && time.isDate(o.validity.notAfter) &&
|
|
158
190
|
_isName(o.issuer) &&
|
|
159
191
|
_isName(o.subject) &&
|
|
160
|
-
!!o.subjectPublicKeyInfo &&
|
|
192
|
+
!!o.subjectPublicKeyInfo && _isBuffer(o.subjectPublicKeyInfo.bytes) &&
|
|
161
193
|
_isAlgorithmIdentifier(o.subjectPublicKeyInfo.algorithm) &&
|
|
162
194
|
_isBitString(o.subjectPublicKeyInfo.publicKey) &&
|
|
163
|
-
|
|
195
|
+
_isArray(o.extensions) && _every(o.extensions, _isExtensionEntry);
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
// isCrl(o) -- the complete parsed-CertificateList shape. `crlExtensions` and each
|
|
@@ -173,19 +205,19 @@ function isCert(o) {
|
|
|
173
205
|
// @enforced-by guard-shape-reinlined -- shares the `accept` shape below.
|
|
174
206
|
function isCrl(o) {
|
|
175
207
|
return !!o && typeof o === "object" &&
|
|
176
|
-
|
|
208
|
+
_isBuffer(o.tbsBytes) &&
|
|
177
209
|
typeof o.version === "number" &&
|
|
178
210
|
_isAlgorithmIdentifier(o.signatureAlgorithm) &&
|
|
179
211
|
_isBitString(o.signatureValue) &&
|
|
180
212
|
_isName(o.issuer) &&
|
|
181
213
|
time.isDate(o.thisUpdate) &&
|
|
182
214
|
(o.nextUpdate === null || time.isDate(o.nextUpdate)) &&
|
|
183
|
-
|
|
184
|
-
|
|
215
|
+
_isArray(o.crlExtensions) && _every(o.crlExtensions, _isCrlExtensionEntry) &&
|
|
216
|
+
_isArray(o.revokedCertificates) && _every(o.revokedCertificates, function (e) {
|
|
185
217
|
return !!e && typeof e.serialNumber === "bigint" &&
|
|
186
218
|
typeof e.serialNumberHex === "string" &&
|
|
187
219
|
time.isDate(e.revocationDate) &&
|
|
188
|
-
|
|
220
|
+
_isArray(e.crlEntryExtensions) && _every(e.crlEntryExtensions, _isCrlExtensionEntry);
|
|
189
221
|
});
|
|
190
222
|
}
|
|
191
223
|
|
|
@@ -207,7 +239,10 @@ function _safe(shape) {
|
|
|
207
239
|
var certShape = _safe(isCert);
|
|
208
240
|
var crlShape = _safe(isCrl);
|
|
209
241
|
|
|
210
|
-
|
|
242
|
+
// Null-prototype: this table decides which kinds are recognized, and the lookup below is what the
|
|
243
|
+
// unknown-kind refusal rests on. Built on a `{}`, a planted `Object.prototype[kind]` supplies a
|
|
244
|
+
// predicate for a kind the table never carried, and an unrecognized structure is then authorized.
|
|
245
|
+
var _SHAPES = _assign(_create(null), { certificate: certShape, crl: crlShape });
|
|
211
246
|
|
|
212
247
|
// accept(input, kind, parse, E, code, label) -> the parsed structure | throws
|
|
213
248
|
//
|
|
@@ -288,8 +323,8 @@ function accept(input, kind, parse, E, code, label) {
|
|
|
288
323
|
// answered for), so a Proxy could claim any mark and name any bytes. A WeakMap
|
|
289
324
|
// lookup is on identity: a Proxy is a different object from the parser's result
|
|
290
325
|
// and simply is not a key.
|
|
291
|
-
// - `
|
|
292
|
-
// record, while `
|
|
326
|
+
// - `_create(parsed)` is likewise a different object, so it inherits no
|
|
327
|
+
// record, while `_assign({}, parsed)` and `{...parsed}` are different
|
|
293
328
|
// objects too. All three, the ways a mixed structure is actually assembled,
|
|
294
329
|
// fall out of the same rule, with no need for three checks.
|
|
295
330
|
//
|
|
@@ -301,7 +336,7 @@ function accept(input, kind, parse, E, code, label) {
|
|
|
301
336
|
// This is not a signature check and does not pretend to be. It says only "verify the
|
|
302
337
|
// bytes this object was parsed from", which is precisely the claim the three-part
|
|
303
338
|
// check was resting on without ever asking for it.
|
|
304
|
-
var PROVENANCE = new
|
|
339
|
+
var PROVENANCE = new _WeakMap();
|
|
305
340
|
|
|
306
341
|
// recordingParser(kind, parse) -> a parser that records what it parsed.
|
|
307
342
|
//
|
|
@@ -340,7 +375,7 @@ function recordingParser(kind, parse, ErrorClass, code, label) {
|
|
|
340
375
|
// re-derivation would faithfully reproduce whatever was written. A record the object can reach
|
|
341
376
|
// is not a record of anything. This copy is never handed out and nothing aliases it.
|
|
342
377
|
if (out && typeof out === "object") {
|
|
343
|
-
PROVENANCE
|
|
378
|
+
_weakSet(PROVENANCE, out, { kind: kind, source: isText ? snap : _bufferFrom(snap),
|
|
344
379
|
shape: _shapeOf(out) });
|
|
345
380
|
}
|
|
346
381
|
return out;
|
|
@@ -370,9 +405,9 @@ function recordingWalker(kind, walkNode, decodeBytes) {
|
|
|
370
405
|
return function (node) {
|
|
371
406
|
var out = walkNode(node);
|
|
372
407
|
if (out && typeof out === "object" && node && _isBytes(node.bytes)) {
|
|
373
|
-
PROVENANCE
|
|
408
|
+
_weakSet(PROVENANCE, out, {
|
|
374
409
|
kind: kind,
|
|
375
|
-
source:
|
|
410
|
+
source: _bufferFrom(node.bytes),
|
|
376
411
|
shape: _shapeOf(out),
|
|
377
412
|
derive: function (src) { return walkNode(decodeBytes(src)); },
|
|
378
413
|
});
|
|
@@ -385,7 +420,7 @@ function recordingWalker(kind, walkNode, decodeBytes) {
|
|
|
385
420
|
// property of `obj` at all -- the lookup is on the object's identity.
|
|
386
421
|
function _recordOf(obj, kind) {
|
|
387
422
|
if (!obj || typeof obj !== "object") return undefined;
|
|
388
|
-
var rec = PROVENANCE
|
|
423
|
+
var rec = _weakGet(PROVENANCE, obj);
|
|
389
424
|
return (rec && rec.kind === kind) ? rec : undefined;
|
|
390
425
|
}
|
|
391
426
|
|
|
@@ -400,7 +435,7 @@ function _recordOf(obj, kind) {
|
|
|
400
435
|
// is the RED vector that passes a parsed certificate through a producing verb's spec and asserts
|
|
401
436
|
// the integrity door still accepts it, which fails the moment a copy is taken instead.
|
|
402
437
|
function isRecorded(obj) {
|
|
403
|
-
return !!obj && typeof obj === "object" && PROVENANCE
|
|
438
|
+
return !!obj && typeof obj === "object" && _weakHas(PROVENANCE, obj);
|
|
404
439
|
}
|
|
405
440
|
|
|
406
441
|
// isRecordedAsProduced(obj) -> whether this is a recorded structure that still has the field set
|
|
@@ -423,7 +458,7 @@ function isRecorded(obj) {
|
|
|
423
458
|
// asserts the value the verb used is the one that was there at entry.
|
|
424
459
|
function isRecordedAsProduced(obj) {
|
|
425
460
|
if (!isRecorded(obj)) return false;
|
|
426
|
-
var shape = PROVENANCE
|
|
461
|
+
var shape = _weakGet(PROVENANCE, obj).shape;
|
|
427
462
|
var keys = _allNames(obj);
|
|
428
463
|
for (var i = 0; i < keys.length; i++) {
|
|
429
464
|
if (!shape[keys[i]]) return false;
|
|
@@ -432,7 +467,7 @@ function isRecordedAsProduced(obj) {
|
|
|
432
467
|
}
|
|
433
468
|
|
|
434
469
|
function _shapeOf(obj) {
|
|
435
|
-
var shape =
|
|
470
|
+
var shape = _create(null);
|
|
436
471
|
var keys = _allNames(obj);
|
|
437
472
|
for (var i = 0; i < keys.length; i++) shape[keys[i]] = true;
|
|
438
473
|
return shape;
|
|
@@ -443,13 +478,13 @@ function _shapeOf(obj) {
|
|
|
443
478
|
// `Object.keys` would miss a field added with defineProperty and call the object unchanged.
|
|
444
479
|
function _allNames(obj) {
|
|
445
480
|
var names = [];
|
|
446
|
-
var seen =
|
|
447
|
-
for (var o = obj; o && o !==
|
|
448
|
-
var own =
|
|
481
|
+
var seen = _create(null);
|
|
482
|
+
for (var o = obj; o && o !== _ObjectProto; o = _getPrototypeOf(o)) {
|
|
483
|
+
var own = _getOwnPropertyNames(o);
|
|
449
484
|
for (var i = 0; i < own.length; i++) {
|
|
450
485
|
if (seen[own[i]]) continue;
|
|
451
486
|
seen[own[i]] = true;
|
|
452
|
-
names
|
|
487
|
+
_append(names, own[i]);
|
|
453
488
|
}
|
|
454
489
|
}
|
|
455
490
|
return names;
|
|
@@ -516,22 +551,24 @@ function fromTrustedSource(input, kind, claimFields, parse, E, code, why) {
|
|
|
516
551
|
// security-relevant field would therefore change no verdict. What the list decides is narrower: it
|
|
517
552
|
// separates "an object claiming to be a parse result" from "bytes", so the first gets this domain's
|
|
518
553
|
// typed refusal instead of being handed to a byte parser.
|
|
519
|
-
|
|
554
|
+
// Null-prototype, for the same reason as _SHAPES: an inherited entry standing in for an absent one
|
|
555
|
+
// makes an unrecognized kind look recognized, and the refusal keyed on its absence never fires.
|
|
556
|
+
var _CLAIMS = _assign(_create(null), {
|
|
520
557
|
certificate: ["tbsBytes", "subjectPublicKeyInfo", "serialNumberHex"],
|
|
521
558
|
crl: ["tbsBytes", "revokedCertificates", "crlExtensions"],
|
|
522
559
|
cms: ["signerInfos", "encapContentInfo"],
|
|
523
560
|
csr: ["certificationRequestInfoBytes", "subjectPublicKeyInfo", "attributes"],
|
|
524
561
|
crmf: ["messages"],
|
|
525
562
|
attributeCertificate: ["tbsBytes", "holder", "attributes"],
|
|
526
|
-
};
|
|
527
|
-
var _WHY = {
|
|
563
|
+
});
|
|
564
|
+
var _WHY = _assign(_create(null), {
|
|
528
565
|
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",
|
|
529
566
|
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",
|
|
530
567
|
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",
|
|
531
568
|
csr: "the signed byte range, the key it proves possession of and the subject and requested extensions that range encodes are separate properties of a parsed object, so a rebuilt request could have them describe different requests: keep a genuine requester's signed bytes and signature, replace only the subject or the extensionRequest, and the proof of possession still verifies while the certificate a CA issues from those fields is for a name and a set of extensions nobody signed",
|
|
532
569
|
crmf: "the byte range a proof of possession covers, the key that possession is proven for and the subject a CA will issue to are separate properties of a parsed object, so a rebuilt message set could have them describe different requests: keep a genuine requester's signed range and signature, replace only the certTemplate subject or public key, and the proof still verifies while the certificate is issued for a name and a key nobody proved anything about",
|
|
533
570
|
attributeCertificate: "the signed byte range, the holder the privileges attach to and the attributes granting them are separate properties of a parsed object, so a rebuilt attribute certificate could have them describe different grants: keep a genuine issuer's signed bytes and signature, replace only the holder or an attribute, and the signature still verifies while the privileges are read for someone the issuer never granted them to",
|
|
534
|
-
};
|
|
571
|
+
});
|
|
535
572
|
// @enforced-by guard-shape-reinlined -- shares the fromTrustedSource shape it composes: a door that
|
|
536
573
|
// tests the claim fields itself, instead of routing here, is the re-inline both replace.
|
|
537
574
|
function acceptDerived(input, kind, parse, E, code, label) {
|
package/lib/guard-range.js
CHANGED
|
@@ -21,6 +21,19 @@
|
|
|
21
21
|
// 2^31 - 1, the shared skip-cert / path-length / salt / iteration ceiling.
|
|
22
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
|
+
// The safe-integer test, taken at load. It decides whether a decoded value may be narrowed to a
|
|
25
|
+
// Number at all, so one answering true for an unsafe magnitude reopens the precision loss this
|
|
26
|
+
// guard exists to prevent. See guard-intrinsic for the whole captured set.
|
|
27
|
+
var _isSafeInteger = require("./guard-intrinsic").isSafeInteger;
|
|
28
|
+
// The narrow itself. This module exists to turn a bounded BigInt into a Number without losing the
|
|
29
|
+
// bound, and `Number` is a writable property of globalThis, so a replacement returns whatever it
|
|
30
|
+
// likes from a value this function has just certified as in range.
|
|
31
|
+
var _Number = require("./guard-intrinsic").Number;
|
|
32
|
+
// The widening half of the same job. A replacement returns a value the bound below is then applied
|
|
33
|
+
// to instead of the caller's, so the number that passes the range test is not the number that came
|
|
34
|
+
// in and is not the number handed back.
|
|
35
|
+
var _BigInt = require("./guard-intrinsic").BigInt;
|
|
36
|
+
|
|
24
37
|
var UINT31_MAX = 2147483647n;
|
|
25
38
|
var SAFE_MAX = 9007199254740991n;
|
|
26
39
|
var SAFE_MIN = -9007199254740991n;
|
|
@@ -61,7 +74,7 @@ function int(value, min, max, E, code, label) {
|
|
|
61
74
|
if (typeof value !== "bigint" || value < min || value > max) {
|
|
62
75
|
throw E(code, label + " must be an integer within " + min + ".." + max);
|
|
63
76
|
}
|
|
64
|
-
return
|
|
77
|
+
return _Number(value);
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
// uint31(value, E, code, label) -> Number in [0, 2^31-1]. The DER-INTEGER
|
|
@@ -88,8 +101,8 @@ function uint64(value, E, code, label) {
|
|
|
88
101
|
var v;
|
|
89
102
|
if (typeof value === "bigint") {
|
|
90
103
|
v = value;
|
|
91
|
-
} else if (typeof value === "number" &&
|
|
92
|
-
v =
|
|
104
|
+
} else if (typeof value === "number" && _isSafeInteger(value) && value >= 0) {
|
|
105
|
+
v = _BigInt(value);
|
|
93
106
|
} else {
|
|
94
107
|
throw E(code, label + " must be a non-negative safe-integer Number or a BigInt (a uint64)");
|
|
95
108
|
}
|
|
@@ -114,7 +127,7 @@ function uint64(value, E, code, label) {
|
|
|
114
127
|
// @guard-shape Number\.isSafeInteger\(\s*[A-Za-z_$][\w$]*\s*\)\s*\)\s*return BigInt
|
|
115
128
|
function authoredInteger(value, E, code, label) {
|
|
116
129
|
if (typeof value === "bigint") return value;
|
|
117
|
-
if (typeof value === "number" &&
|
|
130
|
+
if (typeof value === "number" && _isSafeInteger(value)) return _BigInt(value);
|
|
118
131
|
throw E(code, label + " must be an integer (a safe-integer number, or a bigint for a large value)");
|
|
119
132
|
}
|
|
120
133
|
|
package/lib/guard-secret.js
CHANGED
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
// reaches for first, so the call sites pass only their own intermediates.
|
|
31
31
|
|
|
32
32
|
var bytes = require("./guard-bytes");
|
|
33
|
+
var intrinsic = require("./guard-intrinsic");
|
|
34
|
+
|
|
35
|
+
// The fill this guard performs, taken from the typed-array prototype at module load and uncurried
|
|
36
|
+
// so the wipe site reads no property at all. Buffer overrides the fill with a writable
|
|
37
|
+
// `Buffer.prototype.fill`, so reading the method off the value would let a caller decide what
|
|
38
|
+
// wiping means; reading `call` off the captured function would let them decide it again, since
|
|
39
|
+
// that function is still the one sitting on the prototype (see guard-intrinsic).
|
|
40
|
+
var _fill = intrinsic.uncurry(Uint8Array.prototype.fill);
|
|
33
41
|
|
|
34
42
|
// zeroize(value, ErrorClass, code, label) -> the same object, cleared.
|
|
35
43
|
// value : a Buffer / TypedArray the TOOLKIT allocated, or null / undefined
|
|
@@ -62,7 +70,13 @@ function zeroize(value, ErrorClass, code, label) {
|
|
|
62
70
|
// Re-view through the shared bytes guard: it is the single place that decides
|
|
63
71
|
// what counts as a writable BufferSource and rejects a detached one typed.
|
|
64
72
|
var view = bytes.view(value, ErrorClass, code, label);
|
|
65
|
-
view.fill(0)
|
|
73
|
+
// Through the fill captured at module load, never `view.fill(0)`. Buffer overrides the typed-array
|
|
74
|
+
// fill with its own, and that property is writable, so a caller who replaces it with a no-op turns
|
|
75
|
+
// every wipe in the toolkit into a silent nothing -- and nothing observable changes, because the
|
|
76
|
+
// wipe still runs, still walks the right buffer, and still returns. A secret that survives a call
|
|
77
|
+
// whose whole purpose was to clear it is the failure this guard exists to prevent, so the one
|
|
78
|
+
// operation it performs is the one operation a caller must not be able to reach.
|
|
79
|
+
_fill(view, 0);
|
|
66
80
|
return value;
|
|
67
81
|
}
|
|
68
82
|
|
package/lib/guard-text.js
CHANGED
|
@@ -20,6 +20,31 @@
|
|
|
20
20
|
// no boundary re-derives it (and drifts to cap-after, as some had).
|
|
21
21
|
|
|
22
22
|
var bytes = require("./guard-bytes");
|
|
23
|
+
var intrinsic = require("./guard-intrinsic");
|
|
24
|
+
|
|
25
|
+
// Everything this guard does to a caller's input, taken at module load. Each name below is an
|
|
26
|
+
// ordinary writable property of a global, so reading it at call time would let a caller decide what
|
|
27
|
+
// the guard does at the moment it does it. Between them these four cover both of the guard's
|
|
28
|
+
// contracts, and both of its input arms:
|
|
29
|
+
//
|
|
30
|
+
// _toString / _decode produce the guard's entire output on the Buffer arm. Replaced with a
|
|
31
|
+
// function returning a constant, they hand a PEM header, a JOSE segment or a
|
|
32
|
+
// DN attribute value a string the caller chose in place of the bytes that
|
|
33
|
+
// were checked -- while the cap, the detached-view refusal and the strict
|
|
34
|
+
// UTF-8 rule all still run and all still pass.
|
|
35
|
+
// _byteLength measures the string arm against the cap. One returning 0 admits a string
|
|
36
|
+
// of any size, which is the allocation-DoS this guard exists to bound.
|
|
37
|
+
// _isInteger decides whether the cap itself is usable, and _isBuffer decides which arm
|
|
38
|
+
// an input takes.
|
|
39
|
+
// The two method captures are uncurried, so their call sites read no property of them; see
|
|
40
|
+
// guard-intrinsic for why capturing a method and then reading `.call` on it reopens the hole.
|
|
41
|
+
var _toString = intrinsic.uncurry(Buffer.prototype.toString);
|
|
42
|
+
var _decode = intrinsic.uncurry(TextDecoder.prototype.decode);
|
|
43
|
+
// Already plain functions rather than methods, so these are invoked directly.
|
|
44
|
+
var _byteLength = Buffer.byteLength;
|
|
45
|
+
var _isBuffer = Buffer.isBuffer;
|
|
46
|
+
var _isInteger = Number.isInteger;
|
|
47
|
+
var _TextDecoder = TextDecoder;
|
|
23
48
|
|
|
24
49
|
var LATIN1 = "latin1";
|
|
25
50
|
|
|
@@ -43,27 +68,31 @@ function decode(input, maxBytes, ErrorClass, spec) {
|
|
|
43
68
|
// the `length > maxBytes` comparison always false, silently disabling the size
|
|
44
69
|
// cap on the guard whose whole contract is cap-before-copy. Config-time
|
|
45
70
|
// TypeError (every composing boundary passes a C.LIMITS constant).
|
|
46
|
-
if (!
|
|
71
|
+
if (!_isInteger(maxBytes) || maxBytes < 0) {
|
|
47
72
|
throw new TypeError("guard.text.decode: maxBytes must be a non-negative integer");
|
|
48
73
|
}
|
|
49
74
|
var charset = spec.charset || LATIN1;
|
|
50
|
-
if (
|
|
75
|
+
if (_isBuffer(input)) {
|
|
51
76
|
// Re-view through the byte guard first so a detached backing ArrayBuffer (a
|
|
52
77
|
// transferred / structuredClone'd Buffer, which reads as zero-length) fails
|
|
53
78
|
// closed here -- the same detached-buffer defense the byte boundaries get --
|
|
54
79
|
// instead of being decoded as an empty string. Then cap, then decode.
|
|
55
80
|
input = bytes.view(input, ErrorClass, spec.badInput, spec.label);
|
|
56
|
-
|
|
81
|
+
// Measured through the byte guard's captured intrinsic getter, never `input.length`. On a typed
|
|
82
|
+
// array `length` is an accessor on the prototype and it is configurable, so a replacement
|
|
83
|
+
// returning 0 lets a buffer of any size past the cap and straight into the decode -- the
|
|
84
|
+
// allocation this guard exists to bound, defeated by the one read that bounds it.
|
|
85
|
+
if (bytes.lengthOf(input) > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
57
86
|
if (spec.fatal) {
|
|
58
|
-
try { return new
|
|
87
|
+
try { return _decode(new _TextDecoder(charset, { fatal: true }), input); }
|
|
59
88
|
catch (e) { throw new ErrorClass(spec.badDecode, spec.label + " is not valid " + charset, e); }
|
|
60
89
|
}
|
|
61
|
-
return input
|
|
90
|
+
return _toString(input, charset);
|
|
62
91
|
}
|
|
63
92
|
if (typeof input === "string") {
|
|
64
93
|
// A latin1 string's char length equals its byte length; a UTF-8 string's
|
|
65
94
|
// does not, so bound the encoded byte length.
|
|
66
|
-
var byteLen = charset === LATIN1 ? input.length :
|
|
95
|
+
var byteLen = charset === LATIN1 ? input.length : _byteLength(input, "utf8");
|
|
67
96
|
if (byteLen > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
68
97
|
return input;
|
|
69
98
|
}
|
package/lib/guard-time.js
CHANGED
|
@@ -27,7 +27,12 @@
|
|
|
27
27
|
// OCSP / CRL currency callers that treat out-of-window as a `continue` skip keep
|
|
28
28
|
// their control flow while a NaN operand can never slip through as a false.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// The slot predicates and the NaN test, snapshotted at load. `util.types` is an ordinary object,
|
|
31
|
+
// so a later `util.types.isDate = () => true` would otherwise decide what this guard calls a Date;
|
|
32
|
+
// the global `isNaN` is writable, and one returning false makes an invalid Date pass the check
|
|
33
|
+
// below, which is the fail-open direction.
|
|
34
|
+
var util = { types: require("./guard-intrinsic").types };
|
|
35
|
+
var _isNaN = Number.isNaN;
|
|
31
36
|
|
|
32
37
|
// assertValid(value, E, code, label) -> the same Date, once proven valid.
|
|
33
38
|
// value : a Date (or an alleged one) from a caller boundary.
|
|
@@ -53,7 +58,11 @@ var util = require("util");
|
|
|
53
58
|
// runs before this module is inside the trust boundary already and can reach further than this
|
|
54
59
|
// one method, so there is no defense here to add; what the capture buys is immunity to every
|
|
55
60
|
// replacement that happens afterwards, which is the case a caller's argument can reach.
|
|
56
|
-
|
|
61
|
+
//
|
|
62
|
+
// The capture is uncurried through guard-intrinsic, so `instantOf` reads no property of it either.
|
|
63
|
+
// The captured function is still the one on `Date.prototype`, and an own `call` assigned to it
|
|
64
|
+
// would shadow `Function.prototype.call` and decide every instant this toolkit evaluates against.
|
|
65
|
+
var _dateGetTime = require("./guard-intrinsic").uncurry(Date.prototype.getTime);
|
|
57
66
|
|
|
58
67
|
// instantOf(value) -> the epoch milliseconds a Date holds, read through the intrinsic.
|
|
59
68
|
//
|
|
@@ -65,7 +74,7 @@ var _dateGetTime = Date.prototype.getTime;
|
|
|
65
74
|
//
|
|
66
75
|
// @enforced-by guard-shape-reinlined
|
|
67
76
|
// @guard-shape \.getTime\s*\(
|
|
68
|
-
function instantOf(value) { return _dateGetTime
|
|
77
|
+
function instantOf(value) { return _dateGetTime(value); }
|
|
69
78
|
|
|
70
79
|
// @enforced-by nan-date-comparison-unguarded
|
|
71
80
|
function assertValid(value, E, code, label) {
|
|
@@ -73,7 +82,7 @@ function assertValid(value, E, code, label) {
|
|
|
73
82
|
// `Object.create(Date.prototype)` inherits everything while holding no instant, so it passes
|
|
74
83
|
// that test and `getTime()` then throws a raw TypeError out of this guard. `util.types.isDate`
|
|
75
84
|
// reads the internal slot, which a caller cannot fake and which answers the same across realms.
|
|
76
|
-
if (!util.types.isDate(value) ||
|
|
85
|
+
if (!util.types.isDate(value) || _isNaN(instantOf(value))) {
|
|
77
86
|
throw E(code, (label || "value") + " must be a valid Date");
|
|
78
87
|
}
|
|
79
88
|
return value;
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:4977fc76-9318-4187-883f-52d642638d7f",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-20T03:58:27.675Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.5.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.5.16",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.16",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.5.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.5.16",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.5.
|
|
57
|
+
"ref": "@blamejs/pki@0.5.16",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|