lemon-tls 0.5.5 → 0.6.0

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/src/wire.js CHANGED
@@ -49,6 +49,7 @@ const TLS_ALERT = {
49
49
  MISSING_EXTENSION: 109,
50
50
  UNSUPPORTED_EXTENSION: 110,
51
51
  UNRECOGNIZED_NAME: 112,
52
+ CERTIFICATE_REQUIRED: 116,
52
53
  NO_APPLICATION_PROTOCOL: 120
53
54
  };
54
55
 
@@ -151,21 +152,39 @@ function w_bytes(buf, off, b) {
151
152
  }
152
153
 
153
154
  /* ============================ Binary read helpers ============================ */
155
+
156
+ /**
157
+ * Parse error carrying the TLS alert description to send.
158
+ * The session layer catches these at its single parse entry point and
159
+ * converts them into a fatal alert (default: decode_error, RFC 8446 §6.2).
160
+ * Keeping the alert code ON the error keeps wire.js policy-free — it only
161
+ * states WHAT is malformed; the session decides to abort.
162
+ */
163
+ function parseError(msg, alertDesc) {
164
+ let e = new Error(msg);
165
+ e.alertDesc = (alertDesc === undefined) ? TLS_ALERT.DECODE_ERROR : alertDesc;
166
+ return e;
167
+ }
168
+
154
169
  function r_u8(buf, off) {
170
+ if (off + 1 > buf.length) throw parseError('truncated u8 at ' + off);
155
171
  return [buf[off++] >>> 0, off];
156
172
  }
157
173
 
158
174
  function r_u16(buf, off) {
175
+ if (off + 2 > buf.length) throw parseError('truncated u16 at ' + off);
159
176
  let v = ((buf[off] << 8) | buf[off + 1]) >>> 0;
160
177
  return [v, off + 2];
161
178
  }
162
179
 
163
180
  function r_u24(buf, off) {
181
+ if (off + 3 > buf.length) throw parseError('truncated u24 at ' + off);
164
182
  let v = ((buf[off] << 16) | (buf[off + 1] << 8) | buf[off + 2]) >>> 0;
165
183
  return [v, off + 3];
166
184
  }
167
185
 
168
186
  function r_bytes(buf, off, n) {
187
+ if (off + n > buf.length) throw parseError('truncated field: need ' + n + ' bytes at ' + off + ', have ' + (buf.length - off));
169
188
  let slice;
170
189
  if (buf instanceof Uint8Array) {
171
190
  // real slice from Uint8Array
@@ -244,10 +263,94 @@ exts.COOKIE = { encode: null, decode: null };
244
263
  exts.RENEGOTIATION_INFO = { encode: null, decode: null };
245
264
  exts.SESSION_TICKET = { encode: null, decode: null };
246
265
  exts.EXTENDED_MASTER_SECRET = { encode: null, decode: null };
266
+ exts.CERTIFICATE_AUTHORITIES = { encode: null, decode: null };
267
+
268
+ /* ---------------------- CERTIFICATE_AUTHORITIES (47) ---------------------- */
269
+ // RFC 8446 §4.2.4: DistinguishedName authorities<3..2^16-1> — the list is
270
+ // declared non-empty by the grammar itself, so an empty list is a peer
271
+ // encoding bug and must be rejected as decode_error (BoGo:
272
+ // RejectEmptyCertificateAuthorities).
273
+ exts.CERTIFICATE_AUTHORITIES.decode = function (data) {
274
+ let off = 0;
275
+ let list;
276
+ [list, off] = readVec(data, off, 2);
277
+ if (off !== data.length) throw parseError('trailing data after certificate_authorities');
278
+ if (list.length === 0) throw parseError('empty certificate_authorities list');
279
+
280
+ let out = [];
281
+ let o2 = 0;
282
+ while (o2 < list.length) {
283
+ let dn;
284
+ [dn, o2] = readVec(list, o2, 2);
285
+ if (dn.length === 0) throw parseError('empty DistinguishedName in certificate_authorities');
286
+ out.push(dn);
287
+ }
288
+ return out;
289
+ };
290
+
291
+ /* ------------------------------ USE_SRTP (14) ------------------------------ */
292
+ /*
293
+ * RFC 5764 §4.1.1:
294
+ * struct {
295
+ * SRTPProtectionProfiles SRTPProtectionProfiles; // uint16 list, vector<2>
296
+ * opaque srtp_mki<0..255>;
297
+ * } UseSRTPData;
298
+ *
299
+ * The ClientHello carries every profile it accepts; the ServerHello (TLS 1.2)
300
+ * or EncryptedExtensions (TLS 1.3) carries exactly one. The MKI is opaque to
301
+ * the handshake and is simply carried through.
302
+ */
303
+ exts.USE_SRTP = { encode: null, decode: null };
304
+
305
+ exts.USE_SRTP.encode = function (value) {
306
+ let profiles = (value && value.profiles) ? value.profiles : [];
307
+ let mki = toU8((value && value.mki) ? value.mki : new Uint8Array(0));
308
+ if (mki.length > 255) throw parseError('use_srtp MKI longer than 255 bytes');
309
+
310
+ let out = new Uint8Array(2 + profiles.length * 2 + 1 + mki.length);
311
+ let off = 0;
312
+ off = w_u16(out, off, profiles.length * 2);
313
+ for (let i = 0; i < profiles.length; i++) off = w_u16(out, off, profiles[i] & 0xFFFF);
314
+ off = w_u8(out, off, mki.length);
315
+ w_bytes(out, off, mki);
316
+ return out;
317
+ };
318
+
319
+ exts.USE_SRTP.decode = function (data) {
320
+ let off = 0;
321
+ let listLen;
322
+ [listLen, off] = r_u16(data, off);
323
+ // The list is a vector of uint16, so an odd length is malformed.
324
+ if (listLen % 2 !== 0) throw parseError('use_srtp profile list has odd length');
325
+ if (off + listLen > data.length) throw parseError('use_srtp profile list overruns the extension');
326
+
327
+ let profiles = [];
328
+ let end = off + listLen;
329
+ while (off < end) {
330
+ let p;
331
+ [p, off] = r_u16(data, off);
332
+ profiles.push(p);
333
+ }
334
+
335
+ let mkiLen;
336
+ [mkiLen, off] = r_u8(data, off);
337
+ if (off + mkiLen > data.length) throw parseError('use_srtp MKI overruns the extension');
338
+ let mki;
339
+ [mki, off] = r_bytes(data, off, mkiLen);
340
+
341
+ return { profiles: profiles, mki: mki };
342
+ };
247
343
 
248
344
  /* ------------------------------ SERVER_NAME (0) ------------------------------ */
249
345
  exts.SERVER_NAME.encode = function (value) {
250
- let host = toU8(value || "");
346
+ // Server-side acknowledgement: a zero-length body, no ServerNameList.
347
+ // Mirrors the decoder's asymmetry (RFC 6066 §3 / RFC 8446 §4.4.2) so the
348
+ // extension can round-trip in both directions through one implementation.
349
+ if (value === null || value === undefined || value === "") {
350
+ return new Uint8Array(0);
351
+ }
352
+
353
+ let host = toU8(value);
251
354
 
252
355
  // one name: type(1)=0, len(2), bytes
253
356
  const inner = new Uint8Array(1 + 2 + host.length);
@@ -262,6 +365,20 @@ exts.SERVER_NAME.encode = function (value) {
262
365
  };
263
366
 
264
367
  exts.SERVER_NAME.decode = function (data) {
368
+ // The extension is ASYMMETRIC (RFC 6066 §3, RFC 8446 §4.4.2):
369
+ //
370
+ // client → server : a full ServerNameList (host_name entries)
371
+ // server → client : a COMPLETELY EMPTY extension — zero-length body — sent
372
+ // in EncryptedExtensions purely to acknowledge that the
373
+ // name was understood. There is no ServerNameList in
374
+ // this direction at all.
375
+ //
376
+ // Decoding both directions with one reader meant the acknowledgement was
377
+ // parsed as a truncated list ("truncated u16 at 0") and a perfectly legal
378
+ // handshake was rejected. An empty body is not a malformed list; it is the
379
+ // other half of the grammar.
380
+ if (!data || data.length === 0) return null;
381
+
265
382
  let off = 0;
266
383
  let list;
267
384
  [list, off] = readVec(data, off, 2);
@@ -363,7 +480,15 @@ exts.SUPPORTED_GROUPS.decode = function (data) {
363
480
 
364
481
  /* -------------------------- SIGNATURE_ALGORITHMS (13) -------------------------- */
365
482
  exts.SIGNATURE_ALGORITHMS.encode = function (value) {
366
- let algs = Array.isArray(value) && value.length>0 ? value : [0x0403, 0x0804, 0x0401];
483
+ // A codec encodes what it is handed; the set of signature schemes is policy
484
+ // and belongs to signing.js (default_signature_schemes), not here. The old
485
+ // hardcoded fallback silently replaced a caller's real list whenever the
486
+ // value arrived empty — the same class of bug as the cipher_suites fallback
487
+ // that was removed from build_hello. Fail loudly instead.
488
+ if (!Array.isArray(value) || value.length === 0) {
489
+ throw new Error('SIGNATURE_ALGORITHMS.encode requires a non-empty scheme list');
490
+ }
491
+ let algs = value;
367
492
 
368
493
  const body = new Uint8Array(2 + algs.length * 2);
369
494
  let off = 0;
@@ -628,12 +753,22 @@ exts.ALPN.decode = function (data) {
628
753
  [n, off] = r_u16(data, off);
629
754
 
630
755
  let end = off + n;
756
+ if (end > data.length) throw parseError('ALPN list length exceeds extension body');
757
+ if (end < data.length) throw parseError('trailing data after ALPN protocol list');
758
+
631
759
  let out = [];
632
760
 
633
761
  while (off < end) {
634
762
  let l;
635
763
  [l, off] = r_u8(data, off);
636
764
 
765
+ // RFC 7301 §3.1 declares ProtocolName<1..2^8-1>: the vector's lower bound
766
+ // is 1, so a zero-length name is a grammar violation rather than an empty
767
+ // string. Accepting it produced a phantom "" protocol that could then be
768
+ // negotiated, and left the list length inconsistent with its contents.
769
+ if (l === 0) throw parseError('ALPN protocol name of zero length');
770
+ if (off + l > end) throw parseError('ALPN protocol name overruns the list');
771
+
637
772
  let v;
638
773
  [v, off] = r_bytes(data, off, l);
639
774
 
@@ -752,24 +887,54 @@ function build_extensions(list) {
752
887
  return out;
753
888
  }
754
889
 
755
- function parse_extensions(buf) {
890
+ /**
891
+ * Parse a bare extension LIST — the caller has already consumed the 2-byte
892
+ * vector length and hands us exactly the list bytes.
893
+ *
894
+ * Two conventions exist in this file and mixing them is a real bug source:
895
+ * - Structures where the extensions vector ENDS the enclosing message
896
+ * (hello, EncryptedExtensions, NewSessionTicket) pass the buffer WITH the
897
+ * length prefix → use parse_extensions().
898
+ * - Structures where the vector is an inner field followed by more data, or
899
+ * where the caller already read the prefix (Certificate entries,
900
+ * CertificateRequest) → use parse_extension_list().
901
+ * Both share this core so the duplicate/overflow rules apply identically.
902
+ */
903
+ function parse_extension_list(buf) {
756
904
  let off = 0;
757
- let n;
758
- [n, off] = r_u16(buf, off);
759
-
760
- let end = off + n;
761
905
  let out = [];
906
+ let seen = {}; // RFC 8446 §4.2: "There MUST NOT be more than one extension of the same type"
762
907
 
763
- while (off < end) {
908
+ while (off < buf.length) {
764
909
  let t;
765
910
  [t, off] = r_u16(buf, off);
766
911
 
767
912
  let l;
768
913
  [l, off] = r_u16(buf, off);
914
+ if (off + l > buf.length) throw parseError('extension body overflows extension list');
769
915
 
770
916
  let d;
771
917
  [d, off] = r_bytes(buf, off, l);
772
918
 
919
+ if (seen[t] === true) {
920
+ // decode_error (50), NOT illegal_parameter (47).
921
+ //
922
+ // RFC 8446 §4.2 says "There MUST NOT be more than one extension of the
923
+ // same type ... Receivers MUST check for this and abort with an
924
+ // 'illegal_parameter' alert", so 47 is a defensible reading and this
925
+ // code used to send it. BoringSSL — and therefore BoGo, and therefore
926
+ // most of the ecosystem that tests against it — treats a repeated
927
+ // extension as a malformed extensions VECTOR rather than a bad parameter
928
+ // value, and sends decode_error. Interoperating with deployed peers wins
929
+ // over the letter of the sentence here.
930
+ //
931
+ // Left as an explicit note because the RFC text genuinely reads the other
932
+ // way: without this comment the next person to check §4.2 will "fix" it
933
+ // back and silently break the same seven tests again.
934
+ throw parseError('duplicate extension type ' + t, TLS_ALERT.DECODE_ERROR);
935
+ }
936
+ seen[t] = true;
937
+
773
938
  let name = ext_name_by_code(t);
774
939
  let dec = exts[name] && exts[name].decode;
775
940
  let val = dec ? dec(d) : null;
@@ -780,6 +945,25 @@ function parse_extensions(buf) {
780
945
  return out;
781
946
  }
782
947
 
948
+ /**
949
+ * Parse a length-prefixed extensions vector that ENDS the enclosing structure.
950
+ * `buf` must START with the 2-byte vector length and contain nothing after it.
951
+ */
952
+ function parse_extensions(buf) {
953
+ let off = 0;
954
+ let n;
955
+ [n, off] = r_u16(buf, off);
956
+
957
+ let end = off + n;
958
+ // The declared vector length must fit the buffer, and the buffer must not
959
+ // carry trailing bytes past the vector — either way it's a framing error
960
+ // in the peer's encoder (decode_error, RFC 8446 §6.2).
961
+ if (end > buf.length) throw parseError('extensions vector length exceeds body');
962
+ if (end < buf.length) throw parseError('trailing data after extensions vector');
963
+
964
+ return parse_extension_list(buf.subarray(off, end));
965
+ }
966
+
783
967
 
784
968
  /* ================================ Hello I/O ================================ */
785
969
  function build_hello(params) {
@@ -796,7 +980,15 @@ function build_hello(params) {
796
980
  let extsBuf = build_extensions(params.extensions || []);
797
981
 
798
982
  if (kind === 'client') {
799
- let cs = params.cipher_suites || [0x1301, 0x1302, 0x1303, 0xC02F, 0xC02B];
983
+ // A codec encodes what it is handed; it must not invent policy. This
984
+ // used to fall back to a hardcoded suite list, which silently replaced the
985
+ // caller's configuration whenever the field name didn't line up — the
986
+ // exact failure the `cipher_suites` plural fix elsewhere was chasing. Fail
987
+ // loudly instead, so a wiring mistake surfaces at the call site.
988
+ let cs = params.cipher_suites;
989
+ if (!Array.isArray(cs) || cs.length === 0) {
990
+ throw new Error('build_hello: ClientHello requires a non-empty cipher_suites array');
991
+ }
800
992
 
801
993
  const csBlock = new Uint8Array(2 + cs.length * 2);
802
994
  let o = 0;
@@ -1037,7 +1229,10 @@ function parse_certificate(body) {
1037
1229
  let extLen; [extLen, off] = r_u16(body, off); // extensions vec<2>
1038
1230
  let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
1039
1231
 
1040
- const extensions = extLen ? parse_extensions(extRaw) : [];
1232
+ // extRaw is the list body (prefix already consumed above), so use
1233
+ // the list parser — parse_extensions() would misread the first two
1234
+ // bytes of the first extension as a vector length.
1235
+ const extensions = parse_extension_list(extRaw);
1041
1236
  entries.push({ cert, extensions });
1042
1237
  }
1043
1238
 
@@ -1105,6 +1300,10 @@ function parse_certificate_verify(body) {
1105
1300
  let sig;
1106
1301
  [sig, off] = r_bytes(body, off, slen);
1107
1302
 
1303
+ // The signature vector ends the message; anything after it is framing junk.
1304
+ if (off !== body.length) throw parseError('trailing data after CertificateVerify signature');
1305
+ if (slen === 0) throw parseError('empty CertificateVerify signature');
1306
+
1108
1307
  return { scheme: alg, signature: sig };
1109
1308
  }
1110
1309
 
@@ -1165,6 +1364,12 @@ function parse_new_session_ticket(body) {
1165
1364
  let ticket;
1166
1365
  [ticket, off] = r_bytes(body, off, tlen);
1167
1366
 
1367
+ // RFC 8446 §4.6.1 declares ticket<1..2^16-1>: the vector's lower bound is 1,
1368
+ // so a zero-length ticket is a grammar violation, not an empty-but-valid
1369
+ // value. Enforced here in the codec because it is a property of the wire
1370
+ // format; what a session then DOES with a ticket is policy and lives above.
1371
+ if (tlen === 0) throw parseError('NewSessionTicket carries an empty ticket');
1372
+
1168
1373
  let extBuf = body.subarray(off);
1169
1374
  let extensions = extBuf.length ? parse_extensions(extBuf) : [];
1170
1375
 
@@ -1197,9 +1402,44 @@ function build_certificate_request(params) {
1197
1402
 
1198
1403
  if (v === TLS_VERSION.TLS1_3) {
1199
1404
  let ctx = toU8((params && params.request_context) || new Uint8Array(0));
1200
- let extsBuf = Array.isArray(params && params.extensions)
1201
- ? build_extensions(params.extensions)
1202
- : (params && params.extensions) || veclen(2, new Uint8Array(0));
1405
+
1406
+ // RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be specified"
1407
+ // in a TLS 1.3 CertificateRequest.
1408
+ //
1409
+ // Extension assembly belongs here, next to build_hello's, so callers pass
1410
+ // SEMANTICS (which algorithms) rather than pre-encoded extension records.
1411
+ // Previously only a pre-built `extensions` array was honoured while the
1412
+ // session passed `signature_algorithms` as a flat field — silently
1413
+ // dropped, producing a CertificateRequest with an empty extensions vector
1414
+ // that strict peers reject outright ("missing sigalgs extension").
1415
+ let extsBuf;
1416
+ if (params && params.extensions && !Array.isArray(params.extensions)) {
1417
+ // Pre-encoded extensions vector supplied verbatim.
1418
+ extsBuf = params.extensions;
1419
+ } else {
1420
+ let extList = Array.isArray(params && params.extensions)
1421
+ ? params.extensions.slice()
1422
+ : [];
1423
+
1424
+ let hasSigAlgs = false;
1425
+ for (let i = 0; i < extList.length; i++) {
1426
+ let t = extList[i] && extList[i].type;
1427
+ if (t === 'SIGNATURE_ALGORITHMS' || t === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
1428
+ }
1429
+
1430
+ if (!hasSigAlgs) {
1431
+ let sa = (params && params.signature_algorithms) || [];
1432
+ if (sa.length === 0) {
1433
+ throw new Error('build_certificate_request: TLS 1.3 requires a non-empty signature_algorithms list');
1434
+ }
1435
+ extList.unshift({ type: 'SIGNATURE_ALGORITHMS', value: sa });
1436
+ }
1437
+
1438
+ // certificate_authorities is OPTIONAL and its DistinguishedName list is
1439
+ // non-empty by grammar — an empty list means "no constraint", which is
1440
+ // expressed by omitting the extension, not by sending an empty one.
1441
+ extsBuf = build_extensions(extList);
1442
+ }
1203
1443
 
1204
1444
  let ctxVec = veclen(1, ctx);
1205
1445
  return concatUint8Arrays([ctxVec, extsBuf]);
@@ -1240,10 +1480,27 @@ function parse_certificate_request(body) {
1240
1480
  if (1 + ctxLen + 2 + extLen === body.length) {
1241
1481
  let ctx = body.subarray(1, 1 + ctxLen);
1242
1482
  let extBuf = body.subarray(1 + ctxLen + 2);
1483
+ let crExts = parse_extension_list(extBuf);
1484
+
1485
+ // RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be
1486
+ // specified". Mirrors the same rule already enforced when BUILDING a
1487
+ // CertificateRequest — encoder and decoder agree on what the message
1488
+ // must contain, so we can neither emit nor accept one without it.
1489
+ let hasSigAlgs = false;
1490
+ for (let ci = 0; ci < crExts.length; ci++) {
1491
+ if (crExts[ci] && crExts[ci].type === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
1492
+ }
1493
+ if (!hasSigAlgs) {
1494
+ throw parseError('TLS 1.3 CertificateRequest without the mandatory signature_algorithms extension');
1495
+ }
1496
+
1243
1497
  return {
1244
1498
  version: TLS_VERSION.TLS1_3,
1245
1499
  request_context: ctx,
1246
- extensions: parse_extensions(extBuf)
1500
+ // extBuf skips the 2-byte prefix (validated as extLen above), so it
1501
+ // is a bare list — this was reading the prefix twice and breaking
1502
+ // every CertificateRequest.
1503
+ extensions: crExts
1247
1504
  };
1248
1505
  }
1249
1506
  }
@@ -1633,6 +1890,7 @@ export {
1633
1890
  exts,
1634
1891
  build_extensions,
1635
1892
  parse_extensions,
1893
+ parse_extension_list,
1636
1894
 
1637
1895
  build_message,
1638
1896
  parse_message,