@blamejs/pki 0.5.15 → 0.5.17

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 (67) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/lib/acme.js +1 -0
  3. package/lib/asn1-der.js +1 -0
  4. package/lib/attrcert-sign.js +1 -0
  5. package/lib/cbor-det.js +1 -0
  6. package/lib/cmc-verify.js +1 -0
  7. package/lib/cmp-build.js +1 -0
  8. package/lib/cmp-verify.js +425 -82
  9. package/lib/cms-decrypt.js +58 -29
  10. package/lib/cms-encrypt.js +45 -24
  11. package/lib/cms-sign.js +3 -3
  12. package/lib/cms-verify.js +5 -1
  13. package/lib/constants.js +1 -0
  14. package/lib/crl-sign.js +1 -0
  15. package/lib/crmf-sign.js +1 -0
  16. package/lib/csr-sign.js +1 -0
  17. package/lib/ct.js +1 -0
  18. package/lib/est.js +1 -0
  19. package/lib/framework-error.js +1 -0
  20. package/lib/guard-all.js +2 -0
  21. package/lib/guard-async.js +14 -2
  22. package/lib/guard-bytes.js +183 -93
  23. package/lib/guard-compress.js +32 -12
  24. package/lib/guard-der.js +30 -7
  25. package/lib/guard-encoding.js +28 -12
  26. package/lib/guard-header.js +18 -7
  27. package/lib/guard-identifier.js +146 -79
  28. package/lib/guard-intrinsic.js +318 -0
  29. package/lib/guard-json.js +39 -12
  30. package/lib/guard-limits.js +15 -6
  31. package/lib/guard-name.js +72 -23
  32. package/lib/guard-parsed.js +71 -34
  33. package/lib/guard-range.js +17 -4
  34. package/lib/guard-secret.js +60 -2
  35. package/lib/guard-text.js +35 -6
  36. package/lib/guard-time.js +13 -4
  37. package/lib/hpke.js +6 -2
  38. package/lib/http-transport.js +1 -0
  39. package/lib/inspect.js +1 -0
  40. package/lib/jose.js +1 -0
  41. package/lib/key.js +2 -1
  42. package/lib/lint.js +1 -0
  43. package/lib/merkle.js +1 -0
  44. package/lib/ocsp.js +1 -0
  45. package/lib/oid.js +1 -0
  46. package/lib/path-validate.js +1 -0
  47. package/lib/pbes2.js +16 -8
  48. package/lib/pkcs12-build.js +2 -1
  49. package/lib/schema-all.js +1 -0
  50. package/lib/schema-c509.js +1 -0
  51. package/lib/schema-csrattrs.js +1 -0
  52. package/lib/schema-engine.js +1 -0
  53. package/lib/schema-pkcs8.js +1 -0
  54. package/lib/schema-smime.js +1 -0
  55. package/lib/schema-tsp.js +1 -0
  56. package/lib/schema-x509.js +1 -0
  57. package/lib/shbs.js +1 -0
  58. package/lib/sigstore.js +1 -0
  59. package/lib/smime.js +1 -0
  60. package/lib/tls-cert-compress.js +1 -0
  61. package/lib/trust.js +1 -0
  62. package/lib/tsp-sign.js +1 -0
  63. package/lib/webauthn.js +1 -0
  64. package/lib/webcrypto.js +11 -8
  65. package/lib/x509-sign.js +1 -0
  66. package/package.json +1 -1
  67. package/sbom.cdx.json +6 -6
@@ -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 `Buffer.isBuffer(x) || x instanceof Uint8Array` is narrower than what the
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 (!Array.isArray(rdns)) return false;
127
+ if (!_isArray(rdns)) return false;
96
128
  for (var i = 0; i < rdns.length; i++) {
97
- if (!Array.isArray(rdns[i]) || rdns[i].length === 0) return false;
98
- if (!rdns[i].every(_isAttributeTypeAndValue)) return false;
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) && Buffer.isBuffer(n.bytes) && typeof n.dn === "string";
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
- (Buffer.isBuffer(a.parameters) || a.parameters === null);
144
+ (_isBuffer(a.parameters) || a.parameters === null);
113
145
  }
114
146
 
115
147
  function _isBitString(b) {
116
- return !!b && Buffer.isBuffer(b.bytes) && typeof b.unusedBits === "number";
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" && Buffer.isBuffer(e.value);
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
- Buffer.isBuffer(o.tbsBytes) &&
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 && Buffer.isBuffer(o.subjectPublicKeyInfo.bytes) &&
192
+ !!o.subjectPublicKeyInfo && _isBuffer(o.subjectPublicKeyInfo.bytes) &&
161
193
  _isAlgorithmIdentifier(o.subjectPublicKeyInfo.algorithm) &&
162
194
  _isBitString(o.subjectPublicKeyInfo.publicKey) &&
163
- Array.isArray(o.extensions) && o.extensions.every(_isExtensionEntry);
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
- Buffer.isBuffer(o.tbsBytes) &&
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
- Array.isArray(o.crlExtensions) && o.crlExtensions.every(_isCrlExtensionEntry) &&
184
- Array.isArray(o.revokedCertificates) && o.revokedCertificates.every(function (e) {
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
- Array.isArray(e.crlEntryExtensions) && e.crlEntryExtensions.every(_isCrlExtensionEntry);
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
- var _SHAPES = { certificate: certShape, crl: crlShape };
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
- // - `Object.create(parsed)` is likewise a different object, so it inherits no
292
- // record, while `Object.assign({}, parsed)` and `{...parsed}` are different
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 WeakMap();
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.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap),
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.set(out, {
408
+ _weakSet(PROVENANCE, out, {
374
409
  kind: kind,
375
- source: Buffer.from(node.bytes),
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.get(obj);
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.has(obj);
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.get(obj).shape;
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 = Object.create(null);
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 = Object.create(null);
447
- for (var o = obj; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
448
- var own = Object.getOwnPropertyNames(o);
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.push(own[i]);
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
- var _CLAIMS = {
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) {
@@ -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 Number(value);
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" && Number.isSafeInteger(value) && value >= 0) {
92
- v = BigInt(value);
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" && Number.isSafeInteger(value)) return BigInt(value);
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
 
@@ -30,6 +30,18 @@
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);
41
+ // The join that produces the buffer cipherFinish hands back. Captured for the same reason as
42
+ // the fill: a replaced `Buffer.concat` decides what the caller receives, and it would receive it
43
+ // while the two intermediates this module is responsible for clearing were cleared as promised.
44
+ var _concat = intrinsic.bufferConcat;
33
45
 
34
46
  // zeroize(value, ErrorClass, code, label) -> the same object, cleared.
35
47
  // value : a Buffer / TypedArray the TOOLKIT allocated, or null / undefined
@@ -62,7 +74,13 @@ function zeroize(value, ErrorClass, code, label) {
62
74
  // Re-view through the shared bytes guard: it is the single place that decides
63
75
  // what counts as a writable BufferSource and rejects a detached one typed.
64
76
  var view = bytes.view(value, ErrorClass, code, label);
65
- view.fill(0);
77
+ // Through the fill captured at module load, never `view.fill(0)`. Buffer overrides the typed-array
78
+ // fill with its own, and that property is writable, so a caller who replaces it with a no-op turns
79
+ // every wipe in the toolkit into a silent nothing -- and nothing observable changes, because the
80
+ // wipe still runs, still walks the right buffer, and still returns. A secret that survives a call
81
+ // whose whole purpose was to clear it is the failure this guard exists to prevent, so the one
82
+ // operation it performs is the one operation a caller must not be able to reach.
83
+ _fill(view, 0);
66
84
  return value;
67
85
  }
68
86
 
@@ -80,4 +98,44 @@ function zeroizeAll(list, ErrorClass, code, label) {
80
98
  return list;
81
99
  }
82
100
 
83
- module.exports = { zeroize: zeroize, zeroizeAll: zeroizeAll };
101
+ // cipherFinish(transform, input, ErrorClass, code, label) -> the output, as one fresh buffer
102
+ // transform : a node:crypto Cipher or Decipher the CALLER built and configured (key, iv, AAD,
103
+ // auth tag, padding mode). This guard performs the two calls and nothing else.
104
+ // input : the bytes to feed it.
105
+ //
106
+ // The single place this toolkit runs a configured cipher to completion, because
107
+ // `Buffer.concat([c.update(x), c.final()])` abandons a copy on BOTH of its exits and the leak is
108
+ // invisible at the call site.
109
+ //
110
+ // On a DECIPHER the abandoned copy is plaintext. `update` returns the recovered plaintext while
111
+ // `final` is what decides whether the message was authentic at all -- a GCM tag mismatch, a CBC
112
+ // padding failure -- so on a forged message the whole plaintext exists before anything has judged
113
+ // it, and the `concat` never runs: that buffer becomes unreferenced garbage holding readable
114
+ // plaintext. RFC 5083 sec. 1 requires a receiver whose integrity check fails to destroy the
115
+ // plaintext, and withholding it from the caller is not destroying it. On the success path `concat`
116
+ // COPIES, so the update buffer is a second full copy the caller never receives and nothing else
117
+ // will ever clear.
118
+ //
119
+ // It covers the ENCRYPT direction too, where the intermediates are ciphertext and clearing them
120
+ // buys little. That is deliberate: any rule for which of the two directions must clear is a
121
+ // judgment made at each call site, and the sites where it is wrong are the ones nobody re-reads.
122
+ // One shape, cleared everywhere, needs no such judgment -- and the encrypt intermediates of a
123
+ // key-wrap are not obviously worthless to an attacker either.
124
+ //
125
+ // @enforced-by guard-shape-reinlined
126
+ // @guard-shape \.update\s*\([\s\S]{0,300}?\)\s*,\s*[A-Za-z_$][\w$]*\s*\.\s*final\s*\(\s*\)
127
+ function cipherFinish(transform, input, ErrorClass, code, label) {
128
+ var head = null, tail = null;
129
+ try {
130
+ head = transform.update(input);
131
+ tail = transform.final();
132
+ // Always a fresh copy, never `head` itself: the returned buffer belongs to the caller and the
133
+ // two intermediates have to be clearable independently of it.
134
+ return _concat([head, tail]);
135
+ } finally {
136
+ zeroize(head, ErrorClass, code, label);
137
+ zeroize(tail, ErrorClass, code, label);
138
+ }
139
+ }
140
+
141
+ module.exports = { zeroize: zeroize, zeroizeAll: zeroizeAll, cipherFinish: cipherFinish };
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 (!Number.isInteger(maxBytes) || maxBytes < 0) {
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 (Buffer.isBuffer(input)) {
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
- if (input.length > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
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 TextDecoder(charset, { fatal: true }).decode(input); }
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.toString(charset);
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 : Buffer.byteLength(input, "utf8");
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
- var util = require("util");
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
- var _dateGetTime = Date.prototype.getTime;
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.call(value); }
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) || isNaN(instantOf(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/lib/hpke.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.hpke
6
6
  * @nav Protocols
7
7
  * @title HPKE
8
+ * @fullname HPKE (Hybrid Public Key Encryption, RFC 9180)
8
9
  * @intro Hybrid Public Key Encryption (RFC 9180) -- the standard construction
9
10
  * behind TLS Encrypted Client Hello, MLS, and OHTTP that turns a recipient KEM
10
11
  * public key into an encapsulated key plus an AEAD-encrypting context. This is
@@ -319,7 +320,7 @@ Context.prototype.seal = function (aad, pt) {
319
320
  var nonce = this._nonce();
320
321
  var c = nodeCrypto.createCipheriv(aead.cipher, this._key, nonce, { authTagLength: aead.Nt });
321
322
  if (aad && aad.length) c.setAAD(aad);
322
- var body = concat([c.update(_buf(pt)), c.final()]);
323
+ var body = guard.secret.cipherFinish(c, _buf(pt), HpkeError, "hpke/bad-input", "a seal intermediate");
323
324
  var ct = concat([body, c.getAuthTag()]);
324
325
  this._inc();
325
326
  return ct;
@@ -335,7 +336,10 @@ Context.prototype.open = function (aad, ct) {
335
336
  if (aad && aad.length) d.setAAD(aad);
336
337
  d.setAuthTag(ct.subarray(ct.length - aead.Nt));
337
338
  var pt;
338
- try { pt = concat([d.update(ct.subarray(0, ct.length - aead.Nt)), d.final()]); }
339
+ // Through the shared guard so the recovered plaintext is destroyed when the AEAD tag does not
340
+ // check out (RFC 9180 sec. 5.2 gives open one outcome on failure, and it is not a copy left in
341
+ // the heap), and so the success path's abandoned intermediate is cleared too.
342
+ try { pt = guard.secret.cipherFinish(d, ct.subarray(0, ct.length - aead.Nt), HpkeError, "hpke/bad-input", "the recovered plaintext"); }
339
343
  catch (e) { throw _err("hpke/open-failed", "AEAD authentication failed (RFC 9180 sec. 5.2)", e); }
340
344
  this._inc();
341
345
  return pt;
@@ -5,6 +5,7 @@
5
5
  * @module pki.transport
6
6
  * @nav Protocols
7
7
  * @title Transport
8
+ * @fullname HTTP transport for OCSP, CRL, EST and ACME requests
8
9
  * @order 195
9
10
  * @slug transport
10
11
  *
package/lib/inspect.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.inspect
6
6
  * @nav Tooling
7
7
  * @title Inspect
8
+ * @fullname Inspect: render any PKI structure as readable text
8
9
  * @intro Human-readable inspection of a parsed certificate: the pure-JS
9
10
  * equivalent of `openssl x509 -text`. `certificate(input)` ingests a PEM string,
10
11
  * a DER Buffer, or an already-parsed certificate and returns a familiar
package/lib/jose.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.jose
6
6
  * @nav Protocols
7
7
  * @title JOSE (JWS / JWK)
8
+ * @fullname JOSE: JWS signatures and JWK keys
8
9
  * @order 10
9
10
  * @slug jose
10
11
  *
package/lib/key.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.key
6
6
  * @nav Signing
7
7
  * @title Keys
8
+ * @fullname Key generation, import and export (RSA, EC, EdDSA, ML-DSA)
8
9
  * @intro The key-material lifecycle: export / import a private key as PKCS#8 (`OneAsymmetricKey`,
9
10
  * RFC 5958) or a public key as SPKI (RFC 5280 sec. 4.1.2.7), encrypt / decrypt a private key under
10
11
  * RFC 8018 PBES2 (`EncryptedPrivateKeyInfo`, PBKDF2 + AES-CBC-Pad), and `generate` /
@@ -138,7 +139,7 @@ async function encrypt(privateKey, password, opts) {
138
139
  try { dk = nodeCrypto.pbkdf2Sync(pwK.bytes, salt, iterations, keyBits / 8, prfNode); }
139
140
  finally { if (pwK.owned) guard.secret.zeroize(pwK.bytes, KeyError, "key/bad-input", "the password encoding"); }
140
141
  try {
141
- var ciphertext = pbes2.cbcEncrypt(dk, iv, der, keyBits);
142
+ var ciphertext = pbes2.cbcEncrypt(dk, iv, der, keyBits, KeyError, "key/bad-input");
142
143
  var epki = b.sequence([pbes2.pbes2AlgId(salt, iterations, prf, cipherName, iv), b.octetString(ciphertext)]);
143
144
  pkcs8.parseEncrypted(epki); // self-check: the produced EncryptedPrivateKeyInfo re-parses
144
145
  return opts.pem ? pkcs8.pemEncode(epki, "ENCRYPTED PRIVATE KEY") : epki;
package/lib/lint.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.lint
6
6
  * @nav Tooling
7
7
  * @title Lint
8
+ * @fullname Certificate linting against CA/Browser Forum requirements
8
9
  * @intro The certificate LINTING engine -- the zlint / pkilint of JavaScript. It walks an
9
10
  * ALREADY-PARSED certificate (from `pki.schema.x509.parse`, whose extension values it
10
11
  * decodes with the shared RFC 5280 decoders) and emits graded, advisory FINDINGS: each
package/lib/merkle.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.merkle
6
6
  * @nav Transparency
7
7
  * @title Merkle
8
+ * @fullname Merkle trees: inclusion and consistency proofs (RFC 6962)
8
9
  * @order 220
9
10
  * @slug merkle
10
11
  *
package/lib/ocsp.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.ocsp
6
6
  * @nav Revocation
7
7
  * @title OCSP
8
+ * @fullname OCSP (Online Certificate Status Protocol) revocation checking
8
9
  * @intro The producing + client-facing half of RFC 6960 Online Certificate Status Protocol:
9
10
  * build a status request over a certificate (`pki.ocsp.buildRequest`), build and sign a status
10
11
  * response as an authorized responder (`pki.ocsp.sign`), emit an unsigned error response
package/lib/oid.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.oid
6
6
  * @nav Core
7
7
  * @title Object Identifiers
8
+ * @fullname OID registry: resolve object identifiers by name or dotted form
8
9
  * @order 40
9
10
  * @slug oid
10
11
  *
@@ -6,6 +6,7 @@
6
6
  * @module pki.path
7
7
  * @nav Path validation
8
8
  * @title Certification path validation (RFC 5280 6)
9
+ * @fullname Certificate chain validation (RFC 5280 sec. 6 path validation)
9
10
  * @intro
10
11
  * RFC 5280 6 certification-path validation as a pure, re-entrant algorithm
11
12
  * over already-parsed certificates. `pki.path.validate(path, opts)` runs the