lemon-tls 0.2.1 → 0.3.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/README.md +258 -203
- package/index.d.ts +145 -14
- package/index.js +33 -6
- package/package.json +3 -10
- package/src/compat.js +290 -31
- package/src/crypto.js +139 -8
- package/src/dtls_session.js +865 -0
- package/src/dtls_socket.js +263 -0
- package/src/record.js +894 -65
- package/src/session/message.js +33 -5
- package/src/session/ticket.js +185 -0
- package/src/tls_session.js +945 -150
- package/src/tls_socket.js +815 -249
- package/src/wire.js +167 -11
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,
|
|
@@ -84,6 +90,8 @@ const TLS_EXT = {
|
|
|
84
90
|
CLIENT_CERT_TYPE: 19,
|
|
85
91
|
SERVER_CERT_TYPE: 20,
|
|
86
92
|
PADDING: 21,
|
|
93
|
+
EXTENDED_MASTER_SECRET: 23,
|
|
94
|
+
SESSION_TICKET: 35,
|
|
87
95
|
PRE_SHARED_KEY: 41,
|
|
88
96
|
EARLY_DATA: 42,
|
|
89
97
|
SUPPORTED_VERSIONS: 43,
|
|
@@ -125,6 +133,18 @@ function w_u24(buf, off, v) {
|
|
|
125
133
|
return off;
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
function w_u48(buf, off, v) {
|
|
137
|
+
let hi = Math.floor(v / 0x100000000);
|
|
138
|
+
let lo = v >>> 0;
|
|
139
|
+
buf[off++] = (hi >>> 8) & 0xFF;
|
|
140
|
+
buf[off++] = hi & 0xFF;
|
|
141
|
+
buf[off++] = (lo >>> 24) & 0xFF;
|
|
142
|
+
buf[off++] = (lo >>> 16) & 0xFF;
|
|
143
|
+
buf[off++] = (lo >>> 8) & 0xFF;
|
|
144
|
+
buf[off++] = lo & 0xFF;
|
|
145
|
+
return off;
|
|
146
|
+
}
|
|
147
|
+
|
|
128
148
|
function w_bytes(buf, off, b) {
|
|
129
149
|
buf.set(b, off);
|
|
130
150
|
return off + b.length;
|
|
@@ -222,6 +242,8 @@ exts.KEY_SHARE = { encode: null, decode: null };
|
|
|
222
242
|
exts.ALPN = { encode: null, decode: null };
|
|
223
243
|
exts.COOKIE = { encode: null, decode: null };
|
|
224
244
|
exts.RENEGOTIATION_INFO = { encode: null, decode: null };
|
|
245
|
+
exts.SESSION_TICKET = { encode: null, decode: null };
|
|
246
|
+
exts.EXTENDED_MASTER_SECRET = { encode: null, decode: null };
|
|
225
247
|
|
|
226
248
|
/* ------------------------------ SERVER_NAME (0) ------------------------------ */
|
|
227
249
|
exts.SERVER_NAME.encode = function (value) {
|
|
@@ -648,6 +670,27 @@ exts.COOKIE.decode = function (data) {
|
|
|
648
670
|
return v; // Uint8Array — opaque cookie
|
|
649
671
|
};
|
|
650
672
|
|
|
673
|
+
/* ---------------------------- SESSION_TICKET (35) ---------------------------- */
|
|
674
|
+
// RFC 5077. Both directions carry opaque bytes (not a length-prefixed vector).
|
|
675
|
+
// ClientHello: empty = "I support tickets" / non-empty = "resume using this ticket"
|
|
676
|
+
// ServerHello: empty = "I will send a NewSessionTicket" (never non-empty in ServerHello)
|
|
677
|
+
exts.SESSION_TICKET.encode = function (value) {
|
|
678
|
+
return toU8(value || new Uint8Array(0));
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
exts.SESSION_TICKET.decode = function (data) {
|
|
682
|
+
return data; // opaque bytes — caller interprets
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
/* -------------------------- EXTENDED_MASTER_SECRET (23) -------------------------- */
|
|
686
|
+
// RFC 7627. Both directions: empty body. Signals support for Extended Master Secret.
|
|
687
|
+
exts.EXTENDED_MASTER_SECRET.encode = function (value) {
|
|
688
|
+
return new Uint8Array(0);
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
exts.EXTENDED_MASTER_SECRET.decode = function (data) {
|
|
692
|
+
return true; // presence is the signal
|
|
693
|
+
};
|
|
651
694
|
/* ============================= Extensions helpers ============================= */
|
|
652
695
|
function ext_name_by_code(code) {
|
|
653
696
|
// best-effort pretty name
|
|
@@ -739,10 +782,13 @@ function parse_extensions(buf) {
|
|
|
739
782
|
|
|
740
783
|
|
|
741
784
|
/* ================================ Hello I/O ================================ */
|
|
742
|
-
function build_hello(
|
|
785
|
+
function build_hello(params) {
|
|
743
786
|
params = params || {};
|
|
787
|
+
let kind = params.kind;
|
|
744
788
|
|
|
745
|
-
let
|
|
789
|
+
let isDtls = (params.cookie !== undefined) ||
|
|
790
|
+
(params.version !== undefined && (params.version & 0xFF00) === 0xFE00);
|
|
791
|
+
let legacy_version = isDtls ? DTLS_VERSION.DTLS1_2 : TLS_VERSION.TLS1_2;
|
|
746
792
|
|
|
747
793
|
let sid = toU8(params.session_id || "");
|
|
748
794
|
if (sid.length > 32) sid = sid.subarray(0, 32);
|
|
@@ -767,8 +813,19 @@ function build_hello(kind, params) {
|
|
|
767
813
|
oc = w_u8(compBlock, oc, comp[j]);
|
|
768
814
|
}
|
|
769
815
|
|
|
816
|
+
// DTLS cookie field (between session_id and cipher_suites)
|
|
817
|
+
let cookieBuf = null;
|
|
818
|
+
if (params.cookie !== undefined) {
|
|
819
|
+
let cookie = toU8(params.cookie);
|
|
820
|
+
cookieBuf = new Uint8Array(1 + cookie.length);
|
|
821
|
+
cookieBuf[0] = cookie.length;
|
|
822
|
+
if (cookie.length > 0) cookieBuf.set(cookie, 1);
|
|
823
|
+
}
|
|
824
|
+
|
|
770
825
|
const out = new Uint8Array(
|
|
771
|
-
2 + 32 + 1 + sid.length +
|
|
826
|
+
2 + 32 + 1 + sid.length +
|
|
827
|
+
(cookieBuf ? cookieBuf.length : 0) +
|
|
828
|
+
csBlock.length + compBlock.length + extsBuf.length
|
|
772
829
|
);
|
|
773
830
|
|
|
774
831
|
let off = 0;
|
|
@@ -776,6 +833,7 @@ function build_hello(kind, params) {
|
|
|
776
833
|
off = w_bytes(out, off, params.random);
|
|
777
834
|
off = w_u8(out, off, sid.length);
|
|
778
835
|
off = w_bytes(out, off, sid);
|
|
836
|
+
if (cookieBuf) off = w_bytes(out, off, cookieBuf);
|
|
779
837
|
off = w_bytes(out, off, csBlock);
|
|
780
838
|
off = w_bytes(out, off, compBlock);
|
|
781
839
|
off = w_bytes(out, off, extsBuf);
|
|
@@ -803,11 +861,13 @@ function build_hello(kind, params) {
|
|
|
803
861
|
throw new Error('build_hello: kind must be "client" or "server"');
|
|
804
862
|
}
|
|
805
863
|
|
|
806
|
-
function parse_hello(
|
|
807
|
-
let
|
|
864
|
+
function parse_hello(params) {
|
|
865
|
+
let hsType = params.kind;
|
|
866
|
+
let body = params.body;
|
|
867
|
+
let isClient = (hsType === 'client' || hsType === TLS_MESSAGE_TYPE.CLIENT_HELLO || hsType === 'client_hello');
|
|
808
868
|
let off = 0;
|
|
809
869
|
|
|
810
|
-
// ---
|
|
870
|
+
// --- shared fields ---
|
|
811
871
|
let legacy_version; [legacy_version, off] = r_u16(body, off);
|
|
812
872
|
let random; [random, off] = r_bytes(body, off, 32);
|
|
813
873
|
let sidLen; [sidLen, off] = r_u8(body, off);
|
|
@@ -817,8 +877,20 @@ function parse_hello(hsType, body) {
|
|
|
817
877
|
let legacy_compression = [];
|
|
818
878
|
let type = isClient ? 'client_hello' : 'server_hello';
|
|
819
879
|
|
|
880
|
+
// DTLS cookie — auto-detect by version (DTLS versions have 0xFE in high byte)
|
|
881
|
+
let dtls_cookie = null;
|
|
882
|
+
let isDTLS = (legacy_version & 0xFF00) === 0xFE00;
|
|
883
|
+
|
|
820
884
|
if (isClient) {
|
|
821
885
|
// --- ClientHello ---
|
|
886
|
+
// DTLS ClientHello has a cookie field between session_id and cipher_suites
|
|
887
|
+
if (isDTLS) {
|
|
888
|
+
let cookieLen; [cookieLen, off] = r_u8(body, off);
|
|
889
|
+
if (cookieLen > 0) {
|
|
890
|
+
[dtls_cookie, off] = r_bytes(body, off, cookieLen);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
822
894
|
let csLen; [csLen, off] = r_u16(body, off);
|
|
823
895
|
let csEnd = off + csLen;
|
|
824
896
|
while (off < csEnd) {
|
|
@@ -858,9 +930,10 @@ function parse_hello(hsType, body) {
|
|
|
858
930
|
version: version, // single (u16)
|
|
859
931
|
random: random, // single (Uint8Array(32))
|
|
860
932
|
session_id: session_id, // single (Uint8Array)
|
|
933
|
+
dtls_cookie: dtls_cookie, // Uint8Array or null (DTLS only)
|
|
861
934
|
cipher_suites: cipher_suites, // array
|
|
862
935
|
legacy_compression: legacy_compression, // array
|
|
863
|
-
extensions: extensions // array
|
|
936
|
+
extensions: extensions // array
|
|
864
937
|
};
|
|
865
938
|
}
|
|
866
939
|
|
|
@@ -1456,9 +1529,89 @@ function parse_key_update(body) {
|
|
|
1456
1529
|
}
|
|
1457
1530
|
|
|
1458
1531
|
|
|
1532
|
+
/* ============================== DTLS Handshake Message ============================== */
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Build a DTLS handshake message from a TLS message.
|
|
1536
|
+
* Adds 8-byte reconstruction header: msg_seq(2) + frag_offset(3) + frag_length(3).
|
|
1537
|
+
*
|
|
1538
|
+
* tls_msg: Uint8Array — TLS format: type(1) + length(3) + body
|
|
1539
|
+
* msg_seq: u16 — handshake message sequence number
|
|
1540
|
+
* frag_offset: optional, defaults to 0
|
|
1541
|
+
* frag_length: optional, defaults to full body length
|
|
1542
|
+
*/
|
|
1543
|
+
function build_dtls_handshake(tls_msg, msg_seq, frag_offset, frag_length) {
|
|
1544
|
+
let type = tls_msg[0];
|
|
1545
|
+
let total_length = (tls_msg[1] << 16) | (tls_msg[2] << 8) | tls_msg[3];
|
|
1546
|
+
let body = tls_msg.subarray(4);
|
|
1547
|
+
|
|
1548
|
+
if (frag_offset === undefined) frag_offset = 0;
|
|
1549
|
+
if (frag_length === undefined) frag_length = body.length;
|
|
1550
|
+
|
|
1551
|
+
let frag_body = body.subarray(frag_offset, frag_offset + frag_length);
|
|
1552
|
+
|
|
1553
|
+
let out = new Uint8Array(12 + frag_body.length);
|
|
1554
|
+
let off = 0;
|
|
1555
|
+
off = w_u8(out, off, type);
|
|
1556
|
+
off = w_u24(out, off, total_length);
|
|
1557
|
+
off = w_u16(out, off, msg_seq);
|
|
1558
|
+
off = w_u24(out, off, frag_offset);
|
|
1559
|
+
off = w_u24(out, off, frag_length);
|
|
1560
|
+
off = w_bytes(out, off, frag_body);
|
|
1561
|
+
return out;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Parse a DTLS handshake message.
|
|
1566
|
+
* Returns { type, length, msg_seq, frag_offset, frag_length, body }.
|
|
1567
|
+
*/
|
|
1568
|
+
function parse_dtls_handshake(buf) {
|
|
1569
|
+
let off = 0;
|
|
1570
|
+
let type; [type, off] = r_u8(buf, off);
|
|
1571
|
+
let length; [length, off] = r_u24(buf, off);
|
|
1572
|
+
let msg_seq; [msg_seq, off] = r_u16(buf, off);
|
|
1573
|
+
let frag_offset;[frag_offset, off]= r_u24(buf, off);
|
|
1574
|
+
let frag_length;[frag_length, off]= r_u24(buf, off);
|
|
1575
|
+
let body; [body, off] = r_bytes(buf, off, frag_length);
|
|
1576
|
+
return { type, length, msg_seq, frag_offset, frag_length, body };
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
/* ============================== DTLS 1.2 HelloVerifyRequest ============================== */
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Build HelloVerifyRequest body (DTLS 1.2 — message type 3).
|
|
1584
|
+
* params: { server_version?, cookie: Uint8Array }
|
|
1585
|
+
*/
|
|
1586
|
+
function build_hello_verify_request(params) {
|
|
1587
|
+
let version = (params && params.server_version) || DTLS_VERSION.DTLS1_2;
|
|
1588
|
+
let cookie = toU8(params && params.cookie || new Uint8Array(0));
|
|
1589
|
+
let out = new Uint8Array(2 + 1 + cookie.length);
|
|
1590
|
+
let off = 0;
|
|
1591
|
+
off = w_u16(out, off, version);
|
|
1592
|
+
off = w_u8(out, off, cookie.length);
|
|
1593
|
+
if (cookie.length > 0) off = w_bytes(out, off, cookie);
|
|
1594
|
+
return out;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Parse HelloVerifyRequest body.
|
|
1599
|
+
* Returns { server_version, cookie }.
|
|
1600
|
+
*/
|
|
1601
|
+
function parse_hello_verify_request(body) {
|
|
1602
|
+
let off = 0;
|
|
1603
|
+
let server_version; [server_version, off] = r_u16(body, off);
|
|
1604
|
+
let cookieLen; [cookieLen, off] = r_u8(body, off);
|
|
1605
|
+
let cookie = new Uint8Array(0);
|
|
1606
|
+
if (cookieLen > 0) { [cookie, off] = r_bytes(body, off, cookieLen); }
|
|
1607
|
+
return { server_version, cookie };
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
|
|
1459
1611
|
/* ================================ Exports ================================= */
|
|
1460
1612
|
export {
|
|
1461
1613
|
TLS_VERSION,
|
|
1614
|
+
DTLS_VERSION,
|
|
1462
1615
|
TLS_CONTENT_TYPE,
|
|
1463
1616
|
TLS_ALERT_LEVEL,
|
|
1464
1617
|
TLS_ALERT,
|
|
@@ -1468,6 +1621,7 @@ export {
|
|
|
1468
1621
|
w_u8,
|
|
1469
1622
|
w_u16,
|
|
1470
1623
|
w_u24,
|
|
1624
|
+
w_u48,
|
|
1471
1625
|
w_bytes,
|
|
1472
1626
|
r_u8,
|
|
1473
1627
|
r_u16,
|
|
@@ -1516,8 +1670,10 @@ export {
|
|
|
1516
1670
|
|
|
1517
1671
|
build_key_update,
|
|
1518
1672
|
parse_key_update,
|
|
1519
|
-
};
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
1673
|
|
|
1674
|
+
// DTLS
|
|
1675
|
+
build_dtls_handshake,
|
|
1676
|
+
parse_dtls_handshake,
|
|
1677
|
+
build_hello_verify_request,
|
|
1678
|
+
parse_hello_verify_request,
|
|
1679
|
+
};
|