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/index.js +21 -6
- package/package.json +3 -10
- package/src/crypto.js +12 -1
- package/src/dtls_session.js +865 -0
- package/src/dtls_socket.js +263 -0
- package/src/record.js +486 -4
- package/src/session/ecdh.js +25 -1
- package/src/session/message.js +6 -3
- package/src/tls_session.js +258 -71
- package/src/wire.js +174 -19
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(
|
|
760
|
+
function build_hello(params) {
|
|
722
761
|
params = params || {};
|
|
762
|
+
let kind = params.kind;
|
|
723
763
|
|
|
724
|
-
let
|
|
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 +
|
|
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(
|
|
786
|
-
let
|
|
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
|
|
1276
|
+
// params: { cipher_suite, selected_version, selected_group, session_id?, cookie?, other_exts? }
|
|
1208
1277
|
let rnd = TLS13_HRR_RANDOM;
|
|
1209
|
-
|
|
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:
|
|
1284
|
+
// key_share: HRR format = just NamedGroup (2 bytes), NOT ServerHello format
|
|
1216
1285
|
if (params && params.selected_group != null) {
|
|
1217
|
-
|
|
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 +
|
|
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,
|
|
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
|
+
};
|