@blamejs/pki 0.1.22 → 0.1.24

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/lib/ct.js CHANGED
@@ -14,26 +14,26 @@
14
14
  * OCSP response) carries in the SCT extension into its individual signed
15
15
  * certificate timestamps.
16
16
  *
17
- * The SCT payload is encoded in the TLS presentation language (RFC 8446 §3 /
18
- * RFC 5246 §4 conventions) positional, tag-less, fixed-width big-endian
19
- * integers and length-prefixed opaque vectors NOT ASN.1/DER. So this module
17
+ * The SCT payload is encoded in the TLS presentation language (RFC 8446 sec. 3 /
18
+ * RFC 5246 sec. 4 conventions) -- positional, tag-less, fixed-width big-endian
19
+ * integers and length-prefixed opaque vectors -- NOT ASN.1/DER. So this module
20
20
  * owns a bounded big-endian TLS-struct reader rather than composing the DER
21
- * schema engine; the only ASN.1 surface is the §3.3 double wrap (the
21
+ * schema engine; the only ASN.1 surface is the sec. 3.3 double wrap (the
22
22
  * extension value is a DER OCTET STRING whose content is another DER OCTET
23
- * STRING whose content is the TLS list the certificate/OCSP layer peels the
23
+ * STRING whose content is the TLS list -- the certificate/OCSP layer peels the
24
24
  * outer, this module peels the inner).
25
25
  *
26
26
  * Structure is decoded, crypto is surfaced RAW: each SCT surfaces its `logId`
27
- * (32 raw bytes SHA-256 of the log's SPKI, never recomputed), the exact
27
+ * (32 raw bytes -- SHA-256 of the log's SPKI, never recomputed), the exact
28
28
  * `timestamp` as a BigInt, the raw `extensions`, the named-but-not-interpreted
29
29
  * `hashAlg`/`sigAlg` code points, and the raw `signature`. The parser NEVER
30
- * verifies a signature, recomputes a LogID, or trusts a log a verifier
30
+ * verifies a signature, recomputes a LogID, or trusts a log -- a verifier
31
31
  * composes `webcrypto` over `reconstructSignedData(...)`, the exact
32
32
  * `digitally-signed` preimage. DER-only carrier, fail-closed.
33
33
  *
34
34
  * @card
35
35
  * Parse RFC 6962 Certificate Transparency SCT lists from a certificate or OCSP
36
- * extension per-SCT logId / timestamp (BigInt) / algorithm / raw signature,
36
+ * extension -- per-SCT logId / timestamp (BigInt) / algorithm / raw signature,
37
37
  * the signed-preimage reconstruction surfaced for external verification,
38
38
  * bounded TLS-struct decode, fail-closed.
39
39
  */
@@ -45,8 +45,8 @@ var frameworkError = require("./framework-error.js");
45
45
  var CtError = frameworkError.CtError;
46
46
  var C = constants;
47
47
 
48
- // RFC 5246 §7.4.1.4.1 code points 1-byte, NOT OIDs. Surfaced named; an
49
- // unknown code surfaces as its numeric byte with a null name (never rejected
48
+ // RFC 5246 sec. 7.4.1.4.1 code points -- 1-byte, NOT OIDs. Surfaced named; an
49
+ // unknown code surfaces as its numeric byte with a null name (never rejected --
50
50
  // off-profile-pair rejection is a verifier-tier log-conformance concern).
51
51
  var HASH_ALGORITHMS = {
52
52
  0: "none", 1: "md5", 2: "sha1", 3: "sha224", 4: "sha256", 5: "sha384", 6: "sha512",
@@ -59,7 +59,7 @@ var SCT_MIN_BODY = 47;
59
59
  var LOGID_BYTES = 32;
60
60
  var MAX_SAFE = 9007199254740991n; // 2^53 - 1; above this a Number loses precision
61
61
 
62
- // ---- TlsReader the one net-new primitive (an ENCODING layer, not a schema
62
+ // ---- TlsReader -- the one net-new primitive (an ENCODING layer, not a schema
63
63
  // combinator): a bounded big-endian TLS-vector cursor with a hard [pos, end).
64
64
  // A lying inner length can overrun only the current sub-reader's `end`, never
65
65
  // the parent buffer, so bounds-before-slice is structural.
@@ -81,7 +81,7 @@ TlsReader.prototype.fixed = function (n, code) {
81
81
  this._need(n, code);
82
82
  var s = this.buf.subarray(this.pos, this.pos + n); this.pos += n; return s;
83
83
  };
84
- // opaque<min..max> a length-prefixed vector (lenWidth-byte big-endian prefix).
84
+ // opaque<min..max> -- a length-prefixed vector (lenWidth-byte big-endian prefix).
85
85
  // The length prefix itself is a plain read (a truncation there is ct/truncated);
86
86
  // only a LYING length whose body overruns the bound carries the field's own code.
87
87
  TlsReader.prototype.vector = function (lenWidth, min, max, code) {
@@ -98,16 +98,16 @@ TlsReader.prototype.subReader = function (len, code) {
98
98
  TlsReader.prototype.remaining = function () { return this.end - this.pos; };
99
99
  TlsReader.prototype.atEnd = function () { return this.pos === this.end; };
100
100
 
101
- // Peel the RFC 6962 §3.3 inner DER OCTET STRING (the certificate/OCSP layer
101
+ // Peel the RFC 6962 sec. 3.3 inner DER OCTET STRING (the certificate/OCSP layer
102
102
  // already peeled the outer extnValue OCTET STRING). Rides the strict codec, so
103
103
  // an indefinite length / constructed OCTET STRING / trailing bytes / single
104
104
  // wrap all fail closed here; the asn1/* fault attaches as `.cause`.
105
105
  function _peelInner(extValue) {
106
106
  var node;
107
107
  try { node = asn1.decode(extValue); }
108
- catch (e) { throw new CtError("ct/bad-der", "the SCT-list extension value is not valid DER (RFC 6962 §3.3)", e); }
108
+ catch (e) { throw new CtError("ct/bad-der", "the SCT-list extension value is not valid DER (RFC 6962 sec. 3.3)", e); }
109
109
  try { return asn1.read.octetString(node); }
110
- catch (e) { throw new CtError("ct/bad-der", "the SCT-list extension value must be a DER OCTET STRING wrapping the TLS list (RFC 6962 §3.3)", e); }
110
+ catch (e) { throw new CtError("ct/bad-der", "the SCT-list extension value must be a DER OCTET STRING wrapping the TLS list (RFC 6962 sec. 3.3)", e); }
111
111
  }
112
112
 
113
113
  function _toBuffer(v, field) {
@@ -119,9 +119,9 @@ function _toBuffer(v, field) {
119
119
  // Parse one SerializedSCT body inside a sub-reader bounded to the element (so a
120
120
  // lying extensions/signature length overruns the element, never the list). A
121
121
  // version this parser does not define is preserved OPAQUE, not rejected: RFC 6962
122
- // §3.3 gives every SerializedSCT its own length prefix precisely so a client "can
122
+ // sec. 3.3 gives every SerializedSCT its own length prefix precisely so a client "can
123
123
  // still parse old SCTs while skipping over new SCTs whose versions they don't
124
- // understand" so an unknown version yields { unknown, version, rawSct } and the
124
+ // understand" -- so an unknown version yields { unknown, version, rawSct } and the
125
125
  // v1-specific field decode (and the 47-byte floor) is skipped.
126
126
  function _parseSct(r, sctLen) {
127
127
  var bodyStart = r.pos;
@@ -130,7 +130,7 @@ function _parseSct(r, sctLen) {
130
130
  return { unknown: true, version: version, rawSct: r.buf.subarray(bodyStart, r.end) };
131
131
  }
132
132
  if (sctLen < SCT_MIN_BODY) {
133
- throw new CtError("ct/sct-too-short", "a v1 SCT body is at least " + SCT_MIN_BODY + " bytes, got " + sctLen + " (RFC 6962 §3.2)");
133
+ throw new CtError("ct/sct-too-short", "a v1 SCT body is at least " + SCT_MIN_BODY + " bytes, got " + sctLen + " (RFC 6962 sec. 3.2)");
134
134
  }
135
135
  var logId = r.fixed(LOGID_BYTES);
136
136
  var timestamp = r.u64();
@@ -139,7 +139,7 @@ function _parseSct(r, sctLen) {
139
139
  var sigAlg = r.u8();
140
140
  var signature = r.vector(2, 0, null, "ct/sig-overrun");
141
141
  if (!r.atEnd()) {
142
- throw new CtError("ct/sct-trailing-bytes", (r.end - r.pos) + " byte(s) left in a SerializedSCT after the signature (RFC 6962 §3.3)");
142
+ throw new CtError("ct/sct-trailing-bytes", (r.end - r.pos) + " byte(s) left in a SerializedSCT after the signature (RFC 6962 sec. 3.3)");
143
143
  }
144
144
  var timestampMs = timestamp <= MAX_SAFE ? Number(timestamp) : null;
145
145
  return {
@@ -176,13 +176,13 @@ function _parseSct(r, sctLen) {
176
176
  * `signatureAlgorithm`, the raw `signature` Buffer, and `rawSct` (the full
177
177
  * SerializedSCT body). A SerializedSCT whose version this parser does not define
178
178
  * is preserved OPAQUE in `unknownScts` as `{ version, rawSct }` rather than
179
- * failing the list RFC 6962 §3.3 frames each SerializedSCT with its own length
179
+ * failing the list -- RFC 6962 sec. 3.3 frames each SerializedSCT with its own length
180
180
  * so unknown versions are skippable (forward compatibility).
181
181
  *
182
182
  * The extension value is a DER `OCTET STRING` wrapping the TLS-encoded list
183
- * (RFC 6962 §3.3 double wrap); everything below that peel is TLS presentation
183
+ * (RFC 6962 sec. 3.3 double wrap); everything below that peel is TLS presentation
184
184
  * language, decoded with a bounded cursor. Structure is decoded, crypto is
185
- * surfaced RAW the signature is never verified and the LogID never recomputed.
185
+ * surfaced RAW -- the signature is never verified and the LogID never recomputed.
186
186
  *
187
187
  * Throws `CtError` with a stable `ct/*` code on any malformed input (a bad inner
188
188
  * DER wrap is `ct/bad-der` with the `asn1/*` fault as `.cause`), never a raw
@@ -209,22 +209,22 @@ function parseSctList(extValue) {
209
209
  var outer = new TlsReader(blob, 0, blob.length);
210
210
  var listLen = outer.u16("ct/bad-list");
211
211
  if (listLen + 2 !== blob.length) {
212
- throw new CtError("ct/bad-list", "the SCT list declared length " + listLen + " does not match the " + (blob.length - 2) + " byte(s) present (RFC 6962 §3.3)");
212
+ throw new CtError("ct/bad-list", "the SCT list declared length " + listLen + " does not match the " + (blob.length - 2) + " byte(s) present (RFC 6962 sec. 3.3)");
213
213
  }
214
214
  if (listLen < 1) {
215
- throw new CtError("ct/empty-list", "an SCT list must contain at least one SCT (RFC 6962 §3.3)");
215
+ throw new CtError("ct/empty-list", "an SCT list must contain at least one SCT (RFC 6962 sec. 3.3)");
216
216
  }
217
217
  var scts = [], unknownScts = [];
218
218
  while (!outer.atEnd()) {
219
219
  if (outer.remaining() < 2) {
220
- throw new CtError("ct/list-trailing-bytes", "a dangling partial element after the last complete SCT (RFC 6962 §3.3)");
220
+ throw new CtError("ct/list-trailing-bytes", "a dangling partial element after the last complete SCT (RFC 6962 sec. 3.3)");
221
221
  }
222
222
  var sctLen = outer.u16("ct/list-trailing-bytes");
223
223
  if (sctLen < 1) {
224
- throw new CtError("ct/sct-empty", "a SerializedSCT must be non-empty (RFC 6962 §3.3)");
224
+ throw new CtError("ct/sct-empty", "a SerializedSCT must be non-empty (RFC 6962 sec. 3.3)");
225
225
  }
226
226
  if (outer.remaining() < sctLen) {
227
- throw new CtError("ct/list-trailing-bytes", "a SerializedSCT length " + sctLen + " overruns the list (RFC 6962 §3.3)");
227
+ throw new CtError("ct/list-trailing-bytes", "a SerializedSCT length " + sctLen + " overruns the list (RFC 6962 sec. 3.3)");
228
228
  }
229
229
  var one = _parseSct(outer.subReader(sctLen, "ct/list-trailing-bytes"), sctLen);
230
230
  if (one.unknown) unknownScts.push({ version: one.version, rawSct: one.rawSct });
@@ -240,7 +240,7 @@ function parseSctList(extValue) {
240
240
 
241
241
  function _u24Bytes(n) {
242
242
  if (n < 1 || n > 0xffffff) {
243
- throw new CtError("ct/bad-tbs-length", "a certificate / TBSCertificate length must be in 1..2^24-1, got " + n + " (RFC 6962 §3.1)");
243
+ throw new CtError("ct/bad-tbs-length", "a certificate / TBSCertificate length must be in 1..2^24-1, got " + n + " (RFC 6962 sec. 3.1)");
244
244
  }
245
245
  return Buffer.from([(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]);
246
246
  }
@@ -254,20 +254,22 @@ function _u24Bytes(n) {
254
254
  * @related pki.ct.parseSctList
255
255
  *
256
256
  * Rebuild the exact `digitally-signed` preimage bytes an external verifier
257
- * hashes to check an SCT's signature (RFC 6962 §3.2), for a parsed `sct`.
257
+ * hashes to check an SCT's signature (RFC 6962 sec. 3.2), for a parsed `sct`.
258
258
  * `entry` selects the log-entry arm:
259
- * - `{ entryType: 0, leafCert: <DER Buffer> }` an SCT delivered over TLS /
259
+ * - `{ entryType: 0, leafCert: <DER Buffer> }` -- an SCT delivered over TLS /
260
260
  * OCSP, signed over `x509_entry(0)` with the leaf certificate.
261
- * - `{ entryType: 1, tbsCertificate: <DER Buffer>, issuerKeyHash: <32B> }`
261
+ * - `{ entryType: 1, tbsCertificate: <DER Buffer>, issuerKeyHash: <32B> }` --
262
262
  * an SCT EMBEDDED in a certificate, signed over `precert_entry(1)` with the
263
263
  * issuer key hash + the precertificate TBS (the TBS with only the SCT
264
264
  * extension removed). `issuerKeyHash` is SHA-256 of the issuer's SPKI DER.
265
265
  *
266
266
  * The preimage reuses the parsed SCT's raw `extensions` byte-for-byte and
267
- * re-emits the fixed-width scalars canonically. This never verifies anything
267
+ * re-emits the fixed-width scalars canonically. This never verifies anything --
268
268
  * a verifier hashes the returned bytes and checks the signature with the log's
269
269
  * public key (compose `webcrypto`). Throws `CtError` (`ct/bad-entry-type`,
270
- * `ct/bad-issuer-key-hash`, `ct/bad-tbs-length`) on a malformed entry.
270
+ * `ct/bad-issuer-key-hash`, `ct/bad-tbs-length`) on a malformed entry, and
271
+ * `ct/bad-input` / `ct/bad-extensions` on an `sct` whose timestamp or
272
+ * extensions exceed their RFC 6962 3.2 wire ranges (uint64 / opaque<0..2^16-1>).
271
273
  *
272
274
  * @example
273
275
  * var sct = pki.ct.parseSctList(sctExtValue).scts[0];
@@ -278,17 +280,23 @@ function reconstructSignedData(entry, sct) {
278
280
  entry = entry || {};
279
281
  var entryType = entry.entryType;
280
282
  if (entryType !== 0 && entryType !== 1) {
281
- throw new CtError("ct/bad-entry-type", "entryType must be x509_entry(0) or precert_entry(1), got " + entryType + " (RFC 6962 §3.1)");
283
+ throw new CtError("ct/bad-entry-type", "entryType must be x509_entry(0) or precert_entry(1), got " + entryType + " (RFC 6962 sec. 3.1)");
282
284
  }
283
- // A fully decoded v1 SCT (from parseSctList().scts[]) not an opaque
285
+ // A fully decoded v1 SCT (from parseSctList().scts[]) -- not an opaque
284
286
  // unknownScts entry, whose body layout is undefined and cannot be signed over.
285
287
  if (!sct || typeof sct.timestamp !== "bigint" || sct.version !== 0) {
286
288
  throw new CtError("ct/bad-input", "reconstructSignedData expects a decoded v1 SCT from parseSctList().scts[]");
287
289
  }
290
+ // RFC 6962 3.2: the timestamp is a uint64. A hand-built value outside
291
+ // 0..2^64-1 cannot fill the fixed 8-byte field -- refused typed here rather
292
+ // than escaping as a raw RangeError from the Buffer write.
293
+ if (sct.timestamp < 0n || sct.timestamp > 0xffffffffffffffffn) {
294
+ throw new CtError("ct/bad-input", "sct.timestamp must be a uint64 (0..2^64-1), got " + sct.timestamp.toString() + " (RFC 6962 3.2)");
295
+ }
288
296
  var parts = [];
289
- parts.push(Buffer.from([sct.version & 0xff])); // Version v1(0)
290
- parts.push(Buffer.from([0])); // SignatureType certificate_timestamp(0)
291
- var ts = Buffer.alloc(8); ts.writeBigUInt64BE(BigInt(sct.timestamp)); parts.push(ts); // uint64 timestamp
297
+ parts.push(Buffer.from([sct.version & 0xff])); // Version -- v1(0)
298
+ parts.push(Buffer.from([0])); // SignatureType -- certificate_timestamp(0)
299
+ var ts = Buffer.alloc(8); ts.writeBigUInt64BE(sct.timestamp); parts.push(ts); // uint64 timestamp
292
300
  parts.push(Buffer.from([(entryType >> 8) & 0xff, entryType & 0xff])); // LogEntryType (2 bytes BE)
293
301
  if (entryType === 0) {
294
302
  var cert = _toBuffer(entry.leafCert, "leafCert");
@@ -296,13 +304,20 @@ function reconstructSignedData(entry, sct) {
296
304
  } else {
297
305
  var ikh = _toBuffer(entry.issuerKeyHash, "issuerKeyHash");
298
306
  if (ikh.length !== 32) {
299
- throw new CtError("ct/bad-issuer-key-hash", "issuer_key_hash must be exactly 32 bytes (SHA-256 of the issuer SPKI), got " + ikh.length + " (RFC 6962 §3.2)");
307
+ throw new CtError("ct/bad-issuer-key-hash", "issuer_key_hash must be exactly 32 bytes (SHA-256 of the issuer SPKI), got " + ikh.length + " (RFC 6962 sec. 3.2)");
300
308
  }
301
309
  var tbs = _toBuffer(entry.tbsCertificate, "tbsCertificate");
302
310
  parts.push(ikh);
303
311
  parts.push(_u24Bytes(tbs.length)); parts.push(tbs); // PreCert.tbs_certificate<1..2^24-1>
304
312
  }
305
313
  var ext = _toBuffer(sct.extensions, "sct.extensions"); // reuse the parsed raw bytes, never re-encode
314
+ // RFC 6962 3.2: CtExtensions is opaque<0..2^16-1>. A longer value is
315
+ // unrepresentable -- its 2-byte prefix would silently truncate mod 65536,
316
+ // an internally inconsistent preimage -- so it is refused, matching the
317
+ // range check the u24 certificate lengths get.
318
+ if (ext.length > 0xffff) {
319
+ throw new CtError("ct/bad-extensions", "CtExtensions must be 0..65535 bytes, got " + ext.length + " (RFC 6962 3.2)");
320
+ }
306
321
  parts.push(Buffer.from([(ext.length >> 8) & 0xff, ext.length & 0xff])); parts.push(ext); // CtExtensions
307
322
  return Buffer.concat(parts);
308
323
  }