lemon-tls 0.2.0 → 0.2.2

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
@@ -11,6 +11,12 @@ const TLS_VERSION = {
11
11
  TLS1_3: 0x0304
12
12
  };
13
13
 
14
+ const DTLS_VERSION = {
15
+ DTLS1_0: 0xFEFF,
16
+ DTLS1_2: 0xFEFD,
17
+ DTLS1_3: 0xFEFC
18
+ };
19
+
14
20
  const TLS_CONTENT_TYPE = {
15
21
  CHANGE_CIPHER_SPEC: 20,
16
22
  ALERT: 21,
@@ -125,6 +131,18 @@ function w_u24(buf, off, v) {
125
131
  return off;
126
132
  }
127
133
 
134
+ function w_u48(buf, off, v) {
135
+ let hi = Math.floor(v / 0x100000000);
136
+ let lo = v >>> 0;
137
+ buf[off++] = (hi >>> 8) & 0xFF;
138
+ buf[off++] = hi & 0xFF;
139
+ buf[off++] = (lo >>> 24) & 0xFF;
140
+ buf[off++] = (lo >>> 16) & 0xFF;
141
+ buf[off++] = (lo >>> 8) & 0xFF;
142
+ buf[off++] = lo & 0xFF;
143
+ return off;
144
+ }
145
+
128
146
  function w_bytes(buf, off, b) {
129
147
  buf.set(b, off);
130
148
  return off + b.length;
@@ -220,6 +238,7 @@ exts.SIGNATURE_ALGORITHMS = { encode: null, decode: null };
220
238
  exts.PSK_KEY_EXCHANGE_MODES = { encode: null, decode: null };
221
239
  exts.KEY_SHARE = { encode: null, decode: null };
222
240
  exts.ALPN = { encode: null, decode: null };
241
+ exts.COOKIE = { encode: null, decode: null };
223
242
  exts.RENEGOTIATION_INFO = { encode: null, decode: null };
224
243
 
225
244
  /* ------------------------------ SERVER_NAME (0) ------------------------------ */
@@ -527,6 +546,13 @@ exts.KEY_SHARE.encode = function (value) {
527
546
  };
528
547
 
529
548
  exts.KEY_SHARE.decode = function (data) {
549
+ // HelloRetryRequest form: just NamedGroup (2 bytes, no key_exchange)
550
+ if (data.length === 2) {
551
+ let g, off = 0;
552
+ [g, off] = r_u16(data, off);
553
+ return [{ group: g, key_exchange: new Uint8Array(0) }];
554
+ }
555
+
530
556
  // Try ServerHello form: group(2) + len(2) + key
531
557
  if (data.length >= 4) {
532
558
  let g, off = 0;
@@ -627,6 +653,19 @@ exts.RENEGOTIATION_INFO.decode = function (data) {
627
653
  return v; // return raw bytes (Uint8Array)
628
654
  };
629
655
 
656
+ /* -------------------------------- COOKIE (44) -------------------------------- */
657
+ exts.COOKIE.encode = function (value) {
658
+ let v = toU8(value || new Uint8Array(0));
659
+ return veclen(2, v);
660
+ };
661
+
662
+ exts.COOKIE.decode = function (data) {
663
+ let off = 0;
664
+ let v;
665
+ [v, off] = readVec(data, off, 2);
666
+ return v; // Uint8Array — opaque cookie
667
+ };
668
+
630
669
  /* ============================= Extensions helpers ============================= */
631
670
  function ext_name_by_code(code) {
632
671
  // best-effort pretty name
@@ -718,10 +757,13 @@ function parse_extensions(buf) {
718
757
 
719
758
 
720
759
  /* ================================ Hello I/O ================================ */
721
- function build_hello(kind, params) {
760
+ function build_hello(params) {
722
761
  params = params || {};
762
+ let kind = params.kind;
723
763
 
724
- let legacy_version = TLS_VERSION.TLS1_2; // even for TLS1.3 legacy fields
764
+ let isDtls = (params.cookie !== undefined) ||
765
+ (params.version !== undefined && (params.version & 0xFF00) === 0xFE00);
766
+ let legacy_version = isDtls ? DTLS_VERSION.DTLS1_2 : TLS_VERSION.TLS1_2;
725
767
 
726
768
  let sid = toU8(params.session_id || "");
727
769
  if (sid.length > 32) sid = sid.subarray(0, 32);
@@ -746,8 +788,19 @@ function build_hello(kind, params) {
746
788
  oc = w_u8(compBlock, oc, comp[j]);
747
789
  }
748
790
 
791
+ // DTLS cookie field (between session_id and cipher_suites)
792
+ let cookieBuf = null;
793
+ if (params.cookie !== undefined) {
794
+ let cookie = toU8(params.cookie);
795
+ cookieBuf = new Uint8Array(1 + cookie.length);
796
+ cookieBuf[0] = cookie.length;
797
+ if (cookie.length > 0) cookieBuf.set(cookie, 1);
798
+ }
799
+
749
800
  const out = new Uint8Array(
750
- 2 + 32 + 1 + sid.length + csBlock.length + compBlock.length + extsBuf.length
801
+ 2 + 32 + 1 + sid.length +
802
+ (cookieBuf ? cookieBuf.length : 0) +
803
+ csBlock.length + compBlock.length + extsBuf.length
751
804
  );
752
805
 
753
806
  let off = 0;
@@ -755,6 +808,7 @@ function build_hello(kind, params) {
755
808
  off = w_bytes(out, off, params.random);
756
809
  off = w_u8(out, off, sid.length);
757
810
  off = w_bytes(out, off, sid);
811
+ if (cookieBuf) off = w_bytes(out, off, cookieBuf);
758
812
  off = w_bytes(out, off, csBlock);
759
813
  off = w_bytes(out, off, compBlock);
760
814
  off = w_bytes(out, off, extsBuf);
@@ -782,11 +836,13 @@ function build_hello(kind, params) {
782
836
  throw new Error('build_hello: kind must be "client" or "server"');
783
837
  }
784
838
 
785
- function parse_hello(hsType, body) {
786
- let isClient = (hsType === TLS_MESSAGE_TYPE.CLIENT_HELLO || hsType === 'client_hello');
839
+ function parse_hello(params) {
840
+ let hsType = params.kind;
841
+ let body = params.body;
842
+ let isClient = (hsType === 'client' || hsType === TLS_MESSAGE_TYPE.CLIENT_HELLO || hsType === 'client_hello');
787
843
  let off = 0;
788
844
 
789
- // --- שדות משותפים ---
845
+ // --- shared fields ---
790
846
  let legacy_version; [legacy_version, off] = r_u16(body, off);
791
847
  let random; [random, off] = r_bytes(body, off, 32);
792
848
  let sidLen; [sidLen, off] = r_u8(body, off);
@@ -796,8 +852,20 @@ function parse_hello(hsType, body) {
796
852
  let legacy_compression = [];
797
853
  let type = isClient ? 'client_hello' : 'server_hello';
798
854
 
855
+ // DTLS cookie — auto-detect by version (DTLS versions have 0xFE in high byte)
856
+ let dtls_cookie = null;
857
+ let isDTLS = (legacy_version & 0xFF00) === 0xFE00;
858
+
799
859
  if (isClient) {
800
860
  // --- ClientHello ---
861
+ // DTLS ClientHello has a cookie field between session_id and cipher_suites
862
+ if (isDTLS) {
863
+ let cookieLen; [cookieLen, off] = r_u8(body, off);
864
+ if (cookieLen > 0) {
865
+ [dtls_cookie, off] = r_bytes(body, off, cookieLen);
866
+ }
867
+ }
868
+
801
869
  let csLen; [csLen, off] = r_u16(body, off);
802
870
  let csEnd = off + csLen;
803
871
  while (off < csEnd) {
@@ -837,9 +905,10 @@ function parse_hello(hsType, body) {
837
905
  version: version, // single (u16)
838
906
  random: random, // single (Uint8Array(32))
839
907
  session_id: session_id, // single (Uint8Array)
908
+ dtls_cookie: dtls_cookie, // Uint8Array or null (DTLS only)
840
909
  cipher_suites: cipher_suites, // array
841
910
  legacy_compression: legacy_compression, // array
842
- extensions: extensions // array (לא נוגעים בפנים)
911
+ extensions: extensions // array
843
912
  };
844
913
  }
845
914
 
@@ -1204,21 +1273,23 @@ function parse_certificate_request(body) {
1204
1273
 
1205
1274
  /* ============================== TLS 1.3 HelloRetryRequest ============================== */
1206
1275
  function build_hello_retry_request(params) {
1207
- // params: { cipher_suite, selected_version, selected_group, cookie?: Uint8Array|string, other_exts?: list }
1276
+ // params: { cipher_suite, selected_version, selected_group, session_id?, cookie?, other_exts? }
1208
1277
  let rnd = TLS13_HRR_RANDOM;
1209
- const sid = new Uint8Array(0);
1278
+ let sid = (params && params.session_id) ? toU8(params.session_id) : new Uint8Array(0);
1210
1279
  let legacy_version = TLS_VERSION.TLS1_2;
1211
1280
 
1212
1281
  let extList = [];
1213
1282
  // supported_versions (selected)
1214
1283
  extList.push({ type: 'SUPPORTED_VERSIONS', value: (params && params.selected_version) || TLS_VERSION.TLS1_3 });
1215
- // key_share: only selected_group (no key)
1284
+ // key_share: HRR format = just NamedGroup (2 bytes), NOT ServerHello format
1216
1285
  if (params && params.selected_group != null) {
1217
- extList.push({ type: 'KEY_SHARE', value: { selected_group: params.selected_group, key_exchange: new Uint8Array(0) } });
1286
+ let ks_data = new Uint8Array(2);
1287
+ ks_data[0] = (params.selected_group >> 8) & 0xff;
1288
+ ks_data[1] = params.selected_group & 0xff;
1289
+ extList.push({ type: 0x0033, data: ks_data });
1218
1290
  }
1219
1291
  // cookie if supplied
1220
1292
  if (params && params.cookie) {
1221
- if (!exts.COOKIE) { exts.COOKIE = { encode: function(v){ return veclen(2, toU8(v||'')); }, decode: function(d){ let off=0,v; [v,off]=readVec(d,0,2); return v; } }; }
1222
1293
  extList.push({ type: 'COOKIE', value: params.cookie });
1223
1294
  }
1224
1295
  // other extensions passthrough
@@ -1229,12 +1300,13 @@ function build_hello_retry_request(params) {
1229
1300
  let extsBuf = build_extensions(extList);
1230
1301
  let cipher_suite = (params && typeof params.cipher_suite==='number') ? params.cipher_suite : 0x1301;
1231
1302
 
1232
- // Wire = legacy_version + random + sid + cipher_suite + compression(0) + extensions
1233
- const out = new Uint8Array(2 + 32 + 1 + 0 + 2 + 1 + extsBuf.length);
1303
+ // Wire = legacy_version + random + sid_len + sid + cipher_suite + compression(0) + extensions
1304
+ const out = new Uint8Array(2 + 32 + 1 + sid.length + 2 + 1 + extsBuf.length);
1234
1305
  let off = 0;
1235
1306
  off = w_u16(out, off, legacy_version);
1236
1307
  off = w_bytes(out, off, rnd);
1237
- off = w_u8(out, off, 0);
1308
+ off = w_u8(out, off, sid.length);
1309
+ if (sid.length > 0) off = w_bytes(out, off, sid);
1238
1310
  off = w_u16(out, off, cipher_suite);
1239
1311
  off = w_u8(out, off, 0);
1240
1312
  off = w_bytes(out, off, extsBuf);
@@ -1432,9 +1504,89 @@ function parse_key_update(body) {
1432
1504
  }
1433
1505
 
1434
1506
 
1507
+ /* ============================== DTLS Handshake Message ============================== */
1508
+
1509
+ /**
1510
+ * Build a DTLS handshake message from a TLS message.
1511
+ * Adds 8-byte reconstruction header: msg_seq(2) + frag_offset(3) + frag_length(3).
1512
+ *
1513
+ * tls_msg: Uint8Array — TLS format: type(1) + length(3) + body
1514
+ * msg_seq: u16 — handshake message sequence number
1515
+ * frag_offset: optional, defaults to 0
1516
+ * frag_length: optional, defaults to full body length
1517
+ */
1518
+ function build_dtls_handshake(tls_msg, msg_seq, frag_offset, frag_length) {
1519
+ let type = tls_msg[0];
1520
+ let total_length = (tls_msg[1] << 16) | (tls_msg[2] << 8) | tls_msg[3];
1521
+ let body = tls_msg.subarray(4);
1522
+
1523
+ if (frag_offset === undefined) frag_offset = 0;
1524
+ if (frag_length === undefined) frag_length = body.length;
1525
+
1526
+ let frag_body = body.subarray(frag_offset, frag_offset + frag_length);
1527
+
1528
+ let out = new Uint8Array(12 + frag_body.length);
1529
+ let off = 0;
1530
+ off = w_u8(out, off, type);
1531
+ off = w_u24(out, off, total_length);
1532
+ off = w_u16(out, off, msg_seq);
1533
+ off = w_u24(out, off, frag_offset);
1534
+ off = w_u24(out, off, frag_length);
1535
+ off = w_bytes(out, off, frag_body);
1536
+ return out;
1537
+ }
1538
+
1539
+ /**
1540
+ * Parse a DTLS handshake message.
1541
+ * Returns { type, length, msg_seq, frag_offset, frag_length, body }.
1542
+ */
1543
+ function parse_dtls_handshake(buf) {
1544
+ let off = 0;
1545
+ let type; [type, off] = r_u8(buf, off);
1546
+ let length; [length, off] = r_u24(buf, off);
1547
+ let msg_seq; [msg_seq, off] = r_u16(buf, off);
1548
+ let frag_offset;[frag_offset, off]= r_u24(buf, off);
1549
+ let frag_length;[frag_length, off]= r_u24(buf, off);
1550
+ let body; [body, off] = r_bytes(buf, off, frag_length);
1551
+ return { type, length, msg_seq, frag_offset, frag_length, body };
1552
+ }
1553
+
1554
+
1555
+ /* ============================== DTLS 1.2 HelloVerifyRequest ============================== */
1556
+
1557
+ /**
1558
+ * Build HelloVerifyRequest body (DTLS 1.2 — message type 3).
1559
+ * params: { server_version?, cookie: Uint8Array }
1560
+ */
1561
+ function build_hello_verify_request(params) {
1562
+ let version = (params && params.server_version) || DTLS_VERSION.DTLS1_2;
1563
+ let cookie = toU8(params && params.cookie || new Uint8Array(0));
1564
+ let out = new Uint8Array(2 + 1 + cookie.length);
1565
+ let off = 0;
1566
+ off = w_u16(out, off, version);
1567
+ off = w_u8(out, off, cookie.length);
1568
+ if (cookie.length > 0) off = w_bytes(out, off, cookie);
1569
+ return out;
1570
+ }
1571
+
1572
+ /**
1573
+ * Parse HelloVerifyRequest body.
1574
+ * Returns { server_version, cookie }.
1575
+ */
1576
+ function parse_hello_verify_request(body) {
1577
+ let off = 0;
1578
+ let server_version; [server_version, off] = r_u16(body, off);
1579
+ let cookieLen; [cookieLen, off] = r_u8(body, off);
1580
+ let cookie = new Uint8Array(0);
1581
+ if (cookieLen > 0) { [cookie, off] = r_bytes(body, off, cookieLen); }
1582
+ return { server_version, cookie };
1583
+ }
1584
+
1585
+
1435
1586
  /* ================================ Exports ================================= */
1436
1587
  export {
1437
1588
  TLS_VERSION,
1589
+ DTLS_VERSION,
1438
1590
  TLS_CONTENT_TYPE,
1439
1591
  TLS_ALERT_LEVEL,
1440
1592
  TLS_ALERT,
@@ -1444,6 +1596,7 @@ export {
1444
1596
  w_u8,
1445
1597
  w_u16,
1446
1598
  w_u24,
1599
+ w_u48,
1447
1600
  w_bytes,
1448
1601
  r_u8,
1449
1602
  r_u16,
@@ -1492,8 +1645,10 @@ export {
1492
1645
 
1493
1646
  build_key_update,
1494
1647
  parse_key_update,
1495
- };
1496
-
1497
-
1498
-
1499
1648
 
1649
+ // DTLS
1650
+ build_dtls_handshake,
1651
+ parse_dtls_handshake,
1652
+ build_hello_verify_request,
1653
+ parse_hello_verify_request,
1654
+ };