lemon-tls 0.9.0 → 0.11.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 +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
package/src/wire.js
CHANGED
|
@@ -42,6 +42,17 @@ function is_tls13_wire(v) {
|
|
|
42
42
|
return v === TLS_VERSION.TLS1_3 || v === DTLS_VERSION.DTLS1_3;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** A version's node:tls name ('TLSv1.3', 'DTLSv1.2' …), or null. */
|
|
46
|
+
function version_name(v) {
|
|
47
|
+
switch (v) {
|
|
48
|
+
case TLS_VERSION.TLS1_3: return 'TLSv1.3';
|
|
49
|
+
case TLS_VERSION.TLS1_2: return 'TLSv1.2';
|
|
50
|
+
case DTLS_VERSION.DTLS1_3: return 'DTLSv1.3';
|
|
51
|
+
case DTLS_VERSION.DTLS1_2: return 'DTLSv1.2';
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
/** Companion to is_tls13_wire for the 1.2 message format. */
|
|
46
57
|
function is_tls12_wire(v) {
|
|
47
58
|
return v === TLS_VERSION.TLS1_2 || v === DTLS_VERSION.DTLS1_2;
|
|
@@ -56,8 +67,8 @@ const TLS_CONTENT_TYPE = {
|
|
|
56
67
|
|
|
57
68
|
// The largest one handshake message may be, as its header declares it, or a
|
|
58
69
|
// CompressedCertificate decompressed to. OpenSSL allows a ClientHello of
|
|
59
|
-
// 128 KB and certificate chains up to 100 KB (max_cert_list)
|
|
60
|
-
//
|
|
70
|
+
// 128 KB and certificate chains up to 100 KB (max_cert_list). The 24-bit
|
|
71
|
+
// length would allow 16 MB — memory a
|
|
61
72
|
// peer could make us hold, or inflate a few hundred bytes into, before it
|
|
62
73
|
// has proven anything. A maxHandshakeSize below it lowers it.
|
|
63
74
|
const HANDSHAKE_MESSAGE_MAX = 128 * 1024;
|
|
@@ -74,6 +85,7 @@ const TLS_ALERT = {
|
|
|
74
85
|
RECORD_OVERFLOW: 22,
|
|
75
86
|
HANDSHAKE_FAILURE: 40,
|
|
76
87
|
BAD_CERTIFICATE: 42,
|
|
88
|
+
UNSUPPORTED_CERTIFICATE: 43,
|
|
77
89
|
CERTIFICATE_EXPIRED: 45,
|
|
78
90
|
CERTIFICATE_UNKNOWN: 46,
|
|
79
91
|
ILLEGAL_PARAMETER: 47,
|
|
@@ -87,6 +99,7 @@ const TLS_ALERT = {
|
|
|
87
99
|
USER_CANCELED: 90,
|
|
88
100
|
MISSING_EXTENSION: 109,
|
|
89
101
|
UNSUPPORTED_EXTENSION: 110,
|
|
102
|
+
NO_RENEGOTIATION: 100,
|
|
90
103
|
UNRECOGNIZED_NAME: 112,
|
|
91
104
|
CERTIFICATE_REQUIRED: 116,
|
|
92
105
|
NO_APPLICATION_PROTOCOL: 120,
|
|
@@ -99,6 +112,8 @@ const TLS_MESSAGE_TYPE = {
|
|
|
99
112
|
NEW_SESSION_TICKET: 4,
|
|
100
113
|
END_OF_EARLY_DATA: 5,
|
|
101
114
|
ENCRYPTED_EXTENSIONS: 8,
|
|
115
|
+
REQUEST_CONNECTION_ID: 9, // DTLS 1.3 (RFC 9147 §5.8.4)
|
|
116
|
+
NEW_CONNECTION_ID: 10, // DTLS 1.3 (RFC 9147 §5.8.3)
|
|
102
117
|
CERTIFICATE: 11,
|
|
103
118
|
SERVER_KEY_EXCHANGE: 12,
|
|
104
119
|
CERTIFICATE_REQUEST: 13,
|
|
@@ -136,6 +151,7 @@ const TLS_EXT = {
|
|
|
136
151
|
PADDING: 21,
|
|
137
152
|
EXTENDED_MASTER_SECRET: 23,
|
|
138
153
|
COMPRESS_CERTIFICATE: 27, // RFC 8879
|
|
154
|
+
RECORD_SIZE_LIMIT: 28, // RFC 8449
|
|
139
155
|
SESSION_TICKET: 35,
|
|
140
156
|
PRE_SHARED_KEY: 41,
|
|
141
157
|
EARLY_DATA: 42,
|
|
@@ -147,6 +163,7 @@ const TLS_EXT = {
|
|
|
147
163
|
POST_HANDSHAKE_AUTH: 49,
|
|
148
164
|
SIGNATURE_ALGORITHMS_CERT: 50,
|
|
149
165
|
KEY_SHARE: 51,
|
|
166
|
+
CONNECTION_ID: 54, // RFC 9146 (DTLS)
|
|
150
167
|
RENEGOTIATION_INFO: 0xFF01
|
|
151
168
|
};
|
|
152
169
|
|
|
@@ -295,6 +312,27 @@ function veclen(lenBytes, inner) {
|
|
|
295
312
|
throw new Error('veclen only supports 1/2/3');
|
|
296
313
|
}
|
|
297
314
|
|
|
315
|
+
/**
|
|
316
|
+
* A list of fixed-size items — u8 or u16 each — in a vector that fills the
|
|
317
|
+
* extension exactly (RFC 8446 §3.4): declared length = what follows it, a
|
|
318
|
+
* whole number of items, at least `min` of them (the vector's floor:
|
|
319
|
+
* `<2..2^16-2>` is one u16 item). One reader for every such list, since each
|
|
320
|
+
* decoder had its own loop that read past the declared length, accepted an
|
|
321
|
+
* odd length, an empty list, or bytes after it.
|
|
322
|
+
*/
|
|
323
|
+
function readItemList(data, lenBytes, itemBytes, min, name) {
|
|
324
|
+
let n, off;
|
|
325
|
+
[n, off] = lenBytes === 1 ? r_u8(data, 0) : r_u16(data, 0);
|
|
326
|
+
if (off + n !== data.length) throw parseError(name + ': list of ' + n + ' bytes in an extension of ' + (data.length - off));
|
|
327
|
+
if (n % itemBytes !== 0) throw parseError(name + ': list of ' + n + ' bytes, not a whole number of items');
|
|
328
|
+
if (n / itemBytes < min) throw parseError(name + ': empty list');
|
|
329
|
+
let out = [];
|
|
330
|
+
for (let i = 0; i < n; i += itemBytes) {
|
|
331
|
+
out.push(itemBytes === 2 ? ((data[off + i] << 8) | data[off + i + 1]) >>> 0 : data[off + i]);
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
|
|
298
336
|
function readVec(buf, off, lenBytes) {
|
|
299
337
|
let n, off2 = off;
|
|
300
338
|
|
|
@@ -346,8 +384,29 @@ exts.STATUS_REQUEST.encode = function (value) {
|
|
|
346
384
|
if (value && value.ocsp) return new Uint8Array([1, 0, 0, 0, 0]);
|
|
347
385
|
return new Uint8Array(0);
|
|
348
386
|
};
|
|
349
|
-
|
|
350
|
-
|
|
387
|
+
// RFC 6066 §8. In a ServerHello (TLS 1.2) the extension_data is empty: a
|
|
388
|
+
// bare "a CertificateStatus follows". In a ClientHello, a
|
|
389
|
+
// CertificateStatusRequest: status_type, then for ocsp(1) responder_id_list
|
|
390
|
+
// <0..2^16-1> and request_extensions<0..2^16-1>, and nothing after them. Bytes
|
|
391
|
+
// left over were taken as an offer all the same.
|
|
392
|
+
exts.STATUS_REQUEST.decode = function (data, kind) {
|
|
393
|
+
if (kind === 'server_hello') {
|
|
394
|
+
if (data.length !== 0) throw parseError('status_request: ' + data.length + ' bytes in a ServerHello, which carries none');
|
|
395
|
+
return {};
|
|
396
|
+
}
|
|
397
|
+
// Elsewhere (a TLS 1.3 Certificate entry holds a CertificateStatus, read
|
|
398
|
+
// by parse_certificate_status; a CertificateRequest's is empty).
|
|
399
|
+
if (kind !== 'client_hello') return data.length === 0 ? {} : { status_type: data[0] };
|
|
400
|
+
if (data.length === 0) throw parseError('status_request: empty');
|
|
401
|
+
if (data[0] === 1) {
|
|
402
|
+
let off = 1;
|
|
403
|
+
for (let v = 0; v < 2; v++) {
|
|
404
|
+
if (off + 2 > data.length) throw parseError('status_request: truncated');
|
|
405
|
+
off += 2 + ((data[off] << 8) | data[off + 1]);
|
|
406
|
+
if (off > data.length) throw parseError('status_request: truncated');
|
|
407
|
+
}
|
|
408
|
+
if (off !== data.length) throw parseError('status_request: ' + (data.length - off) + ' trailing bytes');
|
|
409
|
+
}
|
|
351
410
|
return { status_type: data[0] };
|
|
352
411
|
};
|
|
353
412
|
|
|
@@ -484,12 +543,19 @@ exts.SERVER_NAME.decode = function (data) {
|
|
|
484
543
|
// other half of the grammar.
|
|
485
544
|
if (!data || data.length === 0) return null;
|
|
486
545
|
|
|
546
|
+
// From a client: server_name_list<1..2^16-1> filling the extension, each
|
|
547
|
+
// name non-empty (HostName<1..2^16-1>), at most one of each name_type
|
|
548
|
+
// (RFC 6066 §3). A list cut short, overrunning, with bytes after it, an
|
|
549
|
+
// empty host_name or two of them were all taken.
|
|
487
550
|
let off = 0;
|
|
488
551
|
let list;
|
|
489
552
|
[list, off] = readVec(data, off, 2);
|
|
553
|
+
if (off !== data.length) throw parseError('server_name: bytes after the server_name_list');
|
|
554
|
+
if (list.length === 0) throw parseError('server_name: empty server_name_list');
|
|
490
555
|
|
|
491
556
|
let off2 = 0;
|
|
492
557
|
let host = "";
|
|
558
|
+
let seenTypes = {};
|
|
493
559
|
|
|
494
560
|
while (off2 < list.length) {
|
|
495
561
|
let typ;
|
|
@@ -501,7 +567,10 @@ exts.SERVER_NAME.decode = function (data) {
|
|
|
501
567
|
let v;
|
|
502
568
|
[v, off2] = r_bytes(list, off2, l);
|
|
503
569
|
|
|
570
|
+
if (seenTypes[typ]) throw parseError('server_name: two names of type ' + typ);
|
|
571
|
+
seenTypes[typ] = true;
|
|
504
572
|
if (typ === 0) {
|
|
573
|
+
if (l === 0) throw parseError('server_name: empty host_name');
|
|
505
574
|
host = (new TextDecoder()).decode(v);
|
|
506
575
|
}
|
|
507
576
|
}
|
|
@@ -533,26 +602,12 @@ exts.SUPPORTED_VERSIONS.encode = function (value) {
|
|
|
533
602
|
return body;
|
|
534
603
|
};
|
|
535
604
|
|
|
536
|
-
exts.SUPPORTED_VERSIONS.decode = function (data) {
|
|
537
|
-
//
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// ClientHello form: vector<1> of versions (u16 each)
|
|
545
|
-
let off2 = 0;
|
|
546
|
-
let n;
|
|
547
|
-
[n, off2] = r_u8(data, off2);
|
|
548
|
-
|
|
549
|
-
let out = [];
|
|
550
|
-
for (let i = 0; i < n; i += 2) {
|
|
551
|
-
let vv;
|
|
552
|
-
[vv, off2] = r_u16(data, off2);
|
|
553
|
-
out.push(vv);
|
|
554
|
-
}
|
|
555
|
-
return out; // return the array directly
|
|
605
|
+
exts.SUPPORTED_VERSIONS.decode = function (data, kind) {
|
|
606
|
+
// ClientHello form: versions<2..254>, one u16 each.
|
|
607
|
+
if (kind === 'client_hello') return readItemList(data, 1, 2, 1, 'supported_versions');
|
|
608
|
+
// ServerHello / HelloRetryRequest form: selected_version, 2 bytes.
|
|
609
|
+
if (data.length !== 2) throw parseError('supported_versions: selected_version of ' + data.length + ' bytes');
|
|
610
|
+
return [((data[0] << 8) | data[1]) >>> 0];
|
|
556
611
|
};
|
|
557
612
|
|
|
558
613
|
/* ---------------------------- SUPPORTED_GROUPS (10) ---------------------------- */
|
|
@@ -570,17 +625,7 @@ exts.SUPPORTED_GROUPS.encode = function (value) {
|
|
|
570
625
|
};
|
|
571
626
|
|
|
572
627
|
exts.SUPPORTED_GROUPS.decode = function (data) {
|
|
573
|
-
|
|
574
|
-
let n;
|
|
575
|
-
[n, off] = r_u16(data, off);
|
|
576
|
-
|
|
577
|
-
let out = [];
|
|
578
|
-
for (let i = 0; i < n; i += 2) {
|
|
579
|
-
let g;
|
|
580
|
-
[g, off] = r_u16(data, off);
|
|
581
|
-
out.push(g);
|
|
582
|
-
}
|
|
583
|
-
return out; // array of named groups
|
|
628
|
+
return readItemList(data, 2, 2, 1, 'supported_groups'); // named_group_list<2..2^16-1>
|
|
584
629
|
};
|
|
585
630
|
|
|
586
631
|
/* ---------------------------- EC_POINT_FORMATS (11) ---------------------------- */
|
|
@@ -624,17 +669,8 @@ exts.SIGNATURE_ALGORITHMS.encode = function (value) {
|
|
|
624
669
|
};
|
|
625
670
|
|
|
626
671
|
exts.SIGNATURE_ALGORITHMS.decode = function (data) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
[n, off] = r_u16(data, off);
|
|
630
|
-
|
|
631
|
-
let out = [];
|
|
632
|
-
for (let i = 0; i < n; i += 2) {
|
|
633
|
-
let a;
|
|
634
|
-
[a, off] = r_u16(data, off);
|
|
635
|
-
out.push(a);
|
|
636
|
-
}
|
|
637
|
-
return out; // array of sigalgs (u16)
|
|
672
|
+
// supported_signature_algorithms<2..2^16-2>: an empty or odd list was taken.
|
|
673
|
+
return readItemList(data, 2, 2, 1, 'signature_algorithms');
|
|
638
674
|
};
|
|
639
675
|
|
|
640
676
|
/* ------------------------ PSK_KEY_EXCHANGE_MODES (45) ------------------------ */
|
|
@@ -652,17 +688,7 @@ exts.PSK_KEY_EXCHANGE_MODES.encode = function (value) {
|
|
|
652
688
|
};
|
|
653
689
|
|
|
654
690
|
exts.PSK_KEY_EXCHANGE_MODES.decode = function (data) {
|
|
655
|
-
|
|
656
|
-
let n;
|
|
657
|
-
[n, off] = r_u8(data, off);
|
|
658
|
-
|
|
659
|
-
let out = [];
|
|
660
|
-
for (let i = 0; i < n; i++) {
|
|
661
|
-
let m;
|
|
662
|
-
[m, off] = r_u8(data, off);
|
|
663
|
-
out.push(m);
|
|
664
|
-
}
|
|
665
|
-
return out; // array of modes (u8)
|
|
691
|
+
return readItemList(data, 1, 1, 1, 'psk_key_exchange_modes'); // ke_modes<1..255>
|
|
666
692
|
};
|
|
667
693
|
|
|
668
694
|
/* ------------------------------- PRE_SHARED_KEY (41) ------------------------------- */
|
|
@@ -730,28 +756,37 @@ exts.PRE_SHARED_KEY.decode = function (data) {
|
|
|
730
756
|
return { selected: sel };
|
|
731
757
|
}
|
|
732
758
|
|
|
733
|
-
// ClientHello: identities
|
|
759
|
+
// ClientHello: identities<7..2^16-1>, then binders<33..2^16-1>, which
|
|
760
|
+
// fill the rest exactly (RFC 8446 §4.2.11): PskIdentity is identity<1..>
|
|
761
|
+
// and a 4-byte age; PskBinderEntry<32..255>.
|
|
734
762
|
let idLen;
|
|
735
763
|
[idLen, off] = r_u16(data, off);
|
|
736
764
|
let idEnd = off + idLen;
|
|
765
|
+
if (idEnd > data.length) throw parseError('pre_shared_key: identities overrun the extension');
|
|
737
766
|
let identities = [];
|
|
738
767
|
while (off < idEnd) {
|
|
739
768
|
let idL;
|
|
740
769
|
[idL, off] = r_u16(data, off);
|
|
770
|
+
if (idL === 0) throw parseError('pre_shared_key: empty identity');
|
|
771
|
+
if (off + idL + 4 > idEnd) throw parseError('pre_shared_key: an identity overruns the list');
|
|
741
772
|
let identity;
|
|
742
773
|
[identity, off] = r_bytes(data, off, idL);
|
|
743
774
|
let age = (data[off] << 24) | (data[off+1] << 16) | (data[off+2] << 8) | data[off+3];
|
|
744
775
|
off += 4;
|
|
745
776
|
identities.push({ identity: identity, age: age >>> 0 });
|
|
746
777
|
}
|
|
778
|
+
if (identities.length === 0) throw parseError('pre_shared_key: no identities');
|
|
747
779
|
|
|
748
780
|
let binderLen;
|
|
749
781
|
[binderLen, off] = r_u16(data, off);
|
|
750
782
|
let binderEnd = off + binderLen;
|
|
783
|
+
if (binderEnd !== data.length) throw parseError('pre_shared_key: binders do not fill the extension');
|
|
751
784
|
let binders = [];
|
|
752
785
|
while (off < binderEnd) {
|
|
753
786
|
let bL;
|
|
754
787
|
[bL, off] = r_u8(data, off);
|
|
788
|
+
if (bL < 32) throw parseError('pre_shared_key: binder of ' + bL + ' bytes, fewer than 32');
|
|
789
|
+
if (off + bL > binderEnd) throw parseError('pre_shared_key: a binder overruns the list');
|
|
755
790
|
let binder;
|
|
756
791
|
[binder, off] = r_bytes(data, off, bL);
|
|
757
792
|
binders.push(binder);
|
|
@@ -893,6 +928,7 @@ exts.ALPN.decode = function (data) {
|
|
|
893
928
|
let end = off + n;
|
|
894
929
|
if (end > data.length) throw parseError('ALPN list length exceeds extension body');
|
|
895
930
|
if (end < data.length) throw parseError('trailing data after ALPN protocol list');
|
|
931
|
+
if (n === 0) throw parseError('ALPN: empty protocol_name_list'); // <2..2^16-1>
|
|
896
932
|
|
|
897
933
|
let out = [];
|
|
898
934
|
|
|
@@ -927,6 +963,7 @@ exts.RENEGOTIATION_INFO.decode = function (data) {
|
|
|
927
963
|
let off = 0;
|
|
928
964
|
let v;
|
|
929
965
|
[v, off] = readVec(data, off, 1);
|
|
966
|
+
if (off !== data.length) throw parseError('renegotiation_info: bytes after renegotiated_connection');
|
|
930
967
|
return v; // return raw bytes (Uint8Array)
|
|
931
968
|
};
|
|
932
969
|
|
|
@@ -940,6 +977,7 @@ exts.COOKIE.decode = function (data) {
|
|
|
940
977
|
let off = 0;
|
|
941
978
|
let v;
|
|
942
979
|
[v, off] = readVec(data, off, 2);
|
|
980
|
+
if (off !== data.length || v.length === 0) throw parseError('cookie: not one non-empty cookie<1..2^16-1>');
|
|
943
981
|
return v; // Uint8Array — opaque cookie
|
|
944
982
|
};
|
|
945
983
|
|
|
@@ -962,6 +1000,8 @@ exts.EXTENDED_MASTER_SECRET.encode = function (value) {
|
|
|
962
1000
|
};
|
|
963
1001
|
|
|
964
1002
|
exts.EXTENDED_MASTER_SECRET.decode = function (data) {
|
|
1003
|
+
// "extension_data field ... is empty" (RFC 7627 §5.1).
|
|
1004
|
+
if (data && data.length !== 0) throw parseError('extended_master_secret with ' + data.length + ' bytes of data');
|
|
965
1005
|
return true; // presence is the signal
|
|
966
1006
|
};
|
|
967
1007
|
/* ============================= Extensions helpers ============================= */
|
|
@@ -1201,15 +1241,21 @@ function parse_hello(params) {
|
|
|
1201
1241
|
let legacy_version; [legacy_version, off] = r_u16(body, off);
|
|
1202
1242
|
let random; [random, off] = r_bytes(body, off, 32);
|
|
1203
1243
|
let sidLen; [sidLen, off] = r_u8(body, off);
|
|
1244
|
+
// legacy_session_id<0..32> (RFC 8446 §4.1.2, RFC 5246 §7.4.1.2).
|
|
1245
|
+
if (sidLen > 32) throw parseError('session_id of ' + sidLen + ' bytes, more than 32');
|
|
1204
1246
|
let session_id; [session_id, off] = r_bytes(body, off, sidLen);
|
|
1205
1247
|
|
|
1206
1248
|
let cipher_suites = [];
|
|
1207
1249
|
let legacy_compression = [];
|
|
1208
1250
|
let type = isClient ? 'client_hello' : 'server_hello';
|
|
1209
1251
|
|
|
1210
|
-
// DTLS
|
|
1252
|
+
// DTLS or TLS: the transport's to say (params.dtls), not the hello's — a
|
|
1253
|
+
// TLS ClientHello with legacy_version (254,254) was read as a DTLS one, a
|
|
1254
|
+
// cookie field looked for where the cipher suites are: decode_error, for a
|
|
1255
|
+
// hello that offers TLS 1.2. Guessed from the version only when the caller
|
|
1256
|
+
// does not say.
|
|
1211
1257
|
let dtls_cookie = null;
|
|
1212
|
-
let isDTLS = (legacy_version & 0xFF00) === 0xFE00;
|
|
1258
|
+
let isDTLS = (typeof params.dtls === 'boolean') ? params.dtls : (legacy_version & 0xFF00) === 0xFE00;
|
|
1213
1259
|
|
|
1214
1260
|
if (isClient) {
|
|
1215
1261
|
// --- ClientHello ---
|
|
@@ -1221,7 +1267,17 @@ function parse_hello(params) {
|
|
|
1221
1267
|
}
|
|
1222
1268
|
}
|
|
1223
1269
|
|
|
1270
|
+
// cipher_suites<2..2^16-2> (RFC 8446 §4.1.2, RFC 5246 §7.4.1.2): never
|
|
1271
|
+
// empty, whole suites only, inside the message. An empty list was taken
|
|
1272
|
+
// (and the server, with nothing to choose, sent nothing); an odd length
|
|
1273
|
+
// read a byte past it.
|
|
1274
|
+
// (params.lenient: a hello read to be fingerprinted — JA4 has a form
|
|
1275
|
+
// for an empty list — not to be answered.)
|
|
1224
1276
|
let csLen; [csLen, off] = r_u16(body, off);
|
|
1277
|
+
if (off + csLen > body.length || (!params.lenient && (csLen < 2 || (csLen & 1) !== 0))) {
|
|
1278
|
+
throw parseError('ClientHello cipher_suites: length ' + csLen);
|
|
1279
|
+
}
|
|
1280
|
+
if (params.lenient) csLen -= csLen & 1;
|
|
1225
1281
|
let csEnd = off + csLen;
|
|
1226
1282
|
while (off < csEnd) {
|
|
1227
1283
|
let cs; [cs, off] = r_u16(body, off);
|
|
@@ -1261,6 +1317,7 @@ function parse_hello(params) {
|
|
|
1261
1317
|
random: random, // single (Uint8Array(32))
|
|
1262
1318
|
session_id: session_id, // single (Uint8Array)
|
|
1263
1319
|
dtls_cookie: dtls_cookie, // Uint8Array or null (DTLS only)
|
|
1320
|
+
dtls: isDTLS,
|
|
1264
1321
|
cipher_suites: cipher_suites, // array
|
|
1265
1322
|
legacy_compression: legacy_compression, // array
|
|
1266
1323
|
extensions: extensions // array
|
|
@@ -1396,73 +1453,49 @@ function parse_compressed_certificate(body) {
|
|
|
1396
1453
|
return { algorithm: algorithm, uncompressed_length: uncompressed_length, compressed: body.subarray(8) };
|
|
1397
1454
|
}
|
|
1398
1455
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
while (off < end) {
|
|
1423
|
-
let certLen; [certLen, off] = r_u24(body, off); // cert_data vec<3>
|
|
1424
|
-
let cert; [cert, off] = r_bytes(body, off, certLen);
|
|
1425
|
-
|
|
1426
|
-
let extLen; [extLen, off] = r_u16(body, off); // extensions vec<2>
|
|
1427
|
-
let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
|
|
1428
|
-
|
|
1429
|
-
// extRaw is the list body (prefix already consumed above), so use
|
|
1430
|
-
// the list parser — parse_extensions() would misread the first two
|
|
1431
|
-
// bytes of the first extension as a vector length.
|
|
1432
|
-
const extensions = parse_extension_list(extRaw);
|
|
1433
|
-
entries.push({ cert, extensions });
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
return {
|
|
1437
|
-
version: TLS_VERSION.TLS1_3,
|
|
1438
|
-
request_context,
|
|
1439
|
-
entries
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1456
|
+
/**
|
|
1457
|
+
* A Certificate message's body, as the version negotiated has it:
|
|
1458
|
+
* TLS 1.3 (RFC 8446 §4.4.2): certificate_request_context<0..2^8-1>, then
|
|
1459
|
+
* certificate_list<0..2^24-1> of { cert_data<1..2^24-1>,
|
|
1460
|
+
* extensions<0..2^16-1> };
|
|
1461
|
+
* TLS 1.2 (RFC 5246 §7.4.2): certificate_list<0..2^24-1> of
|
|
1462
|
+
* ASN.1Cert<1..2^24-1>.
|
|
1463
|
+
* Every length exact — the list ends the message, each entry inside the
|
|
1464
|
+
* list — or decode_error.
|
|
1465
|
+
*
|
|
1466
|
+
* The version was guessed from the bytes: a body that read as 1.3 was
|
|
1467
|
+
* taken as 1.3, and anything else as 1.2 with its list length unchecked
|
|
1468
|
+
* ("graceful recovery") — a TLS 1.2 Certificate whose length was wrong was
|
|
1469
|
+
* taken (tlsfuzzer). The session always knows which it negotiated.
|
|
1470
|
+
*
|
|
1471
|
+
* Returns { version, request_context? (1.3), entries: [{ cert,
|
|
1472
|
+
* extensions? (1.3) }] }.
|
|
1473
|
+
*/
|
|
1474
|
+
function parse_certificate(body, tls13) {
|
|
1475
|
+
let off = 0, request_context;
|
|
1476
|
+
if (tls13) {
|
|
1477
|
+
let rcLen; [rcLen, off] = r_u8(body, off);
|
|
1478
|
+
[request_context, off] = r_bytes(body, off, rcLen);
|
|
1443
1479
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
let listLen2; [listLen2, off3] = r_u24(body, off3);
|
|
1448
|
-
|
|
1449
|
-
const end2 = off3 + listLen2;
|
|
1450
|
-
if (end2 !== body.length) {
|
|
1451
|
-
// graceful recovery: אם האורך לא תואם, ננסה לפחות לא לקרוס (אפשר גם לזרוק שגיאה)
|
|
1452
|
-
// throw new Error('Bad TLS1.2 Certificate length');
|
|
1480
|
+
let listLen; [listLen, off] = r_u24(body, off);
|
|
1481
|
+
if (off + listLen !== body.length) {
|
|
1482
|
+
throw parseError('Certificate: certificate_list of ' + listLen + ' bytes in ' + (body.length - off));
|
|
1453
1483
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
let cert; [cert,
|
|
1459
|
-
|
|
1484
|
+
const entries = [];
|
|
1485
|
+
while (off < body.length) {
|
|
1486
|
+
let certLen; [certLen, off] = r_u24(body, off);
|
|
1487
|
+
if (certLen === 0) throw parseError('Certificate: an empty certificate in the list');
|
|
1488
|
+
let cert; [cert, off] = r_bytes(body, off, certLen);
|
|
1489
|
+
if (!tls13) { entries.push({ cert }); continue; }
|
|
1490
|
+
let extLen; [extLen, off] = r_u16(body, off);
|
|
1491
|
+
let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
|
|
1492
|
+
// extRaw is the list body (prefix already consumed above), so use
|
|
1493
|
+
// the list parser — parse_extensions() would misread the first two
|
|
1494
|
+
// bytes of the first extension as a vector length.
|
|
1495
|
+
entries.push({ cert, extensions: parse_extension_list(extRaw) });
|
|
1460
1496
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
version: TLS_VERSION.TLS1_2,
|
|
1464
|
-
entries: entries12
|
|
1465
|
-
};
|
|
1497
|
+
return tls13 ? { version: TLS_VERSION.TLS1_3, request_context, entries }
|
|
1498
|
+
: { version: TLS_VERSION.TLS1_2, entries };
|
|
1466
1499
|
}
|
|
1467
1500
|
|
|
1468
1501
|
|
|
@@ -1499,7 +1532,9 @@ function parse_certificate_verify(body) {
|
|
|
1499
1532
|
|
|
1500
1533
|
// The signature vector ends the message; anything after it is framing junk.
|
|
1501
1534
|
if (off !== body.length) throw parseError('trailing data after CertificateVerify signature');
|
|
1502
|
-
|
|
1535
|
+
// An empty signature is well formed (signature<0..2^16-1>, RFC 8446
|
|
1536
|
+
// §4.4.3; RFC 5246 §4.7) and does not verify: decrypt_error, from the
|
|
1537
|
+
// check — not decode_error here (tlsfuzzer).
|
|
1503
1538
|
|
|
1504
1539
|
return { scheme: alg, signature: sig };
|
|
1505
1540
|
}
|
|
@@ -1548,6 +1583,8 @@ function build_new_session_ticket(p) {
|
|
|
1548
1583
|
function parse_new_session_ticket(body) {
|
|
1549
1584
|
let off = 0;
|
|
1550
1585
|
|
|
1586
|
+
// The two u32s were read without a bounds check: a short body read as 0s.
|
|
1587
|
+
if (body.length < 8) throw parseError('NewSessionTicket: truncated');
|
|
1551
1588
|
let lifetime = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3]) >>> 0; off+=4;
|
|
1552
1589
|
let age_add = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3]) >>> 0; off+=4;
|
|
1553
1590
|
|
|
@@ -1567,8 +1604,10 @@ function parse_new_session_ticket(body) {
|
|
|
1567
1604
|
// format; what a session then DOES with a ticket is policy and lives above.
|
|
1568
1605
|
if (tlen === 0) throw parseError('NewSessionTicket carries an empty ticket');
|
|
1569
1606
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1607
|
+
// extensions<0..2^16-2> is part of the structure (RFC 8446 §4.6.1): it
|
|
1608
|
+
// must be there, and it ends the message (parse_extensions refuses bytes
|
|
1609
|
+
// after it). A body that stopped after the ticket passed as "no extensions".
|
|
1610
|
+
let extensions = parse_extensions(body.subarray(off));
|
|
1572
1611
|
|
|
1573
1612
|
return {
|
|
1574
1613
|
ticket_lifetime: lifetime,
|
|
@@ -1757,7 +1796,10 @@ function build_hello_retry_request(params) {
|
|
|
1757
1796
|
// params: { cipher_suite, selected_version, selected_group, session_id?, cookie?, other_exts? }
|
|
1758
1797
|
let rnd = TLS13_HRR_RANDOM;
|
|
1759
1798
|
let sid = (params && params.session_id) ? toU8(params.session_id) : new Uint8Array(0);
|
|
1760
|
-
|
|
1799
|
+
// The HelloRetryRequest is a ServerHello (RFC 8446 §4.1.4), with the same
|
|
1800
|
+
// legacy_version: 0x0303 over TLS, {254,253} over DTLS 1.3 (RFC 9147 §5.3).
|
|
1801
|
+
let legacy_version = (params && params.selected_version === DTLS_VERSION.DTLS1_3)
|
|
1802
|
+
? DTLS_VERSION.DTLS1_2 : TLS_VERSION.TLS1_2;
|
|
1761
1803
|
|
|
1762
1804
|
let extList = [];
|
|
1763
1805
|
// supported_versions (selected)
|
|
@@ -1850,31 +1892,10 @@ function parse_server_key_exchange(body) {
|
|
|
1850
1892
|
return { kex: 'ECDHE', group: group, public_key: pub, sig_alg: sig_alg, signature: sig };
|
|
1851
1893
|
}
|
|
1852
1894
|
|
|
1853
|
-
//
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
[dh_p, off] = r_bytes(body, off, pLen);
|
|
1858
|
-
|
|
1859
|
-
let gLen;
|
|
1860
|
-
[gLen, off] = r_u16(body, off);
|
|
1861
|
-
let dh_g;
|
|
1862
|
-
[dh_g, off] = r_bytes(body, off, gLen);
|
|
1863
|
-
|
|
1864
|
-
let yLen;
|
|
1865
|
-
[yLen, off] = r_u16(body, off);
|
|
1866
|
-
let dh_Ys;
|
|
1867
|
-
[dh_Ys, off] = r_bytes(body, off, yLen);
|
|
1868
|
-
|
|
1869
|
-
let sig_alg2;
|
|
1870
|
-
[sig_alg2, off] = r_u16(body, off);
|
|
1871
|
-
|
|
1872
|
-
let s2len;
|
|
1873
|
-
[s2len, off] = r_u16(body, off);
|
|
1874
|
-
let sig2;
|
|
1875
|
-
[sig2, off] = r_bytes(body, off, s2len);
|
|
1876
|
-
|
|
1877
|
-
return { kex: 'DHE', dh_p: dh_p, dh_g: dh_g, dh_Ys: dh_Ys, sig_alg: sig_alg2, signature: sig2 };
|
|
1895
|
+
// Not a named curve (RFC 8422 §5.4: the only ECCurveType a TLS 1.2 ECDHE
|
|
1896
|
+
// ServerKeyExchange may carry). DHE parameters were read here once — from
|
|
1897
|
+
// the wrong offset — for suites this library no longer has.
|
|
1898
|
+
throw parseError('ServerKeyExchange: ECCurveType ' + curve_type + ', not named_curve', TLS_ALERT.ILLEGAL_PARAMETER);
|
|
1878
1899
|
}
|
|
1879
1900
|
|
|
1880
1901
|
/* ============================== TLS 1.2 ClientKeyExchange ============================== */
|
|
@@ -1915,10 +1936,13 @@ function build_new_session_ticket_tls12(p) {
|
|
|
1915
1936
|
off = w_bytes(out, off, ticket);
|
|
1916
1937
|
return out;
|
|
1917
1938
|
}
|
|
1939
|
+
// RFC 5077 §3.3: ticket_lifetime_hint, ticket<0..2^16-1>, nothing after.
|
|
1918
1940
|
function parse_new_session_ticket_tls12(body) {
|
|
1919
1941
|
let off=0;
|
|
1942
|
+
if (body.length < 4) throw parseError('NewSessionTicket: truncated');
|
|
1920
1943
|
let hint = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3])>>>0; off+=4;
|
|
1921
1944
|
let tlen; [tlen,off]=r_u16(body,off); let t; [t,off]=r_bytes(body,off,tlen);
|
|
1945
|
+
if (off !== body.length) throw parseError('NewSessionTicket: ' + (body.length - off) + ' trailing bytes');
|
|
1922
1946
|
return { ticket_lifetime_hint: hint, ticket: t };
|
|
1923
1947
|
}
|
|
1924
1948
|
|
|
@@ -1977,11 +2001,14 @@ function build_key_update(request_update) {
|
|
|
1977
2001
|
}
|
|
1978
2002
|
|
|
1979
2003
|
/**
|
|
1980
|
-
* Parse KeyUpdate message body.
|
|
1981
|
-
* Returns { request_update: 0|1 }
|
|
2004
|
+
* Parse KeyUpdate message body (RFC 8446 §4.6.3): one byte, 0 or 1.
|
|
2005
|
+
* Returns { request_update: 0|1 }. Another length: decode_error; another
|
|
2006
|
+
* value: illegal_parameter ("MUST" — it was read as 0, and a 2 passed).
|
|
1982
2007
|
*/
|
|
1983
2008
|
function parse_key_update(body) {
|
|
1984
|
-
|
|
2009
|
+
if (!body || body.length !== 1) throw parseError('KeyUpdate of ' + (body ? body.length : 0) + ' bytes, not 1');
|
|
2010
|
+
if (body[0] > 1) throw parseError('KeyUpdate request_update ' + body[0] + ', not 0 or 1', TLS_ALERT.ILLEGAL_PARAMETER);
|
|
2011
|
+
return { request_update: body[0] };
|
|
1985
2012
|
}
|
|
1986
2013
|
|
|
1987
2014
|
|
|
@@ -2004,15 +2031,22 @@ function build_dtls_handshake(tls_msg, msg_seq, frag_offset, frag_length) {
|
|
|
2004
2031
|
if (frag_offset === undefined) frag_offset = 0;
|
|
2005
2032
|
if (frag_length === undefined) frag_length = body.length;
|
|
2006
2033
|
|
|
2007
|
-
|
|
2034
|
+
return build_dtls_fragment(type, total_length, msg_seq, frag_offset, body.subarray(frag_offset, frag_offset + frag_length));
|
|
2035
|
+
}
|
|
2008
2036
|
|
|
2037
|
+
/**
|
|
2038
|
+
* One fragment of a DTLS handshake message (RFC 6347 §4.2.2, RFC 9147
|
|
2039
|
+
* §5.2): its 12-byte header — type, the whole message's length, message_seq,
|
|
2040
|
+
* the fragment's offset and length — and its bytes.
|
|
2041
|
+
*/
|
|
2042
|
+
function build_dtls_fragment(type, total_length, msg_seq, frag_offset, frag_body) {
|
|
2009
2043
|
let out = new Uint8Array(12 + frag_body.length);
|
|
2010
2044
|
let off = 0;
|
|
2011
2045
|
off = w_u8(out, off, type);
|
|
2012
2046
|
off = w_u24(out, off, total_length);
|
|
2013
2047
|
off = w_u16(out, off, msg_seq);
|
|
2014
2048
|
off = w_u24(out, off, frag_offset);
|
|
2015
|
-
off = w_u24(out, off,
|
|
2049
|
+
off = w_u24(out, off, frag_body.length);
|
|
2016
2050
|
off = w_bytes(out, off, frag_body);
|
|
2017
2051
|
return out;
|
|
2018
2052
|
}
|
|
@@ -2054,23 +2088,102 @@ function build_hello_verify_request(params) {
|
|
|
2054
2088
|
* Parse HelloVerifyRequest body.
|
|
2055
2089
|
* Returns { server_version, cookie }.
|
|
2056
2090
|
*/
|
|
2091
|
+
// RFC 6347 §4.2.1: server_version, then cookie<0..2^8-1> — and nothing
|
|
2092
|
+
// after it (decode_error). Trailing bytes were ignored.
|
|
2057
2093
|
function parse_hello_verify_request(body) {
|
|
2058
2094
|
let off = 0;
|
|
2059
2095
|
let server_version; [server_version, off] = r_u16(body, off);
|
|
2060
2096
|
let cookieLen; [cookieLen, off] = r_u8(body, off);
|
|
2061
2097
|
let cookie = new Uint8Array(0);
|
|
2062
2098
|
if (cookieLen > 0) { [cookie, off] = r_bytes(body, off, cookieLen); }
|
|
2099
|
+
if (off !== body.length) throw parseError('HelloVerifyRequest: ' + (body.length - off) + ' trailing bytes');
|
|
2063
2100
|
return { server_version, cookie };
|
|
2064
2101
|
}
|
|
2065
2102
|
|
|
2066
2103
|
|
|
2067
2104
|
/* ================================ Exports ================================= */
|
|
2105
|
+
|
|
2106
|
+
// Warning alerts taken in a row, nothing else between: BoringSSL's limit
|
|
2107
|
+
// (BoGo TooManyWarningAlerts).
|
|
2108
|
+
const MAX_WARNING_ALERTS = 4;
|
|
2109
|
+
|
|
2110
|
+
// The most plaintext a record carries (RFC 8446 §5.1, RFC 5246 §6.2.1):
|
|
2111
|
+
// 2^14 — TLS and DTLS, both directions. One constant for every layer that
|
|
2112
|
+
// cuts or checks records (TLSSession, TLSSocket, DTLSSession).
|
|
2113
|
+
const MAX_RECORD_PLAINTEXT = 16384;
|
|
2114
|
+
|
|
2115
|
+
/**
|
|
2116
|
+
* What an alert record from the peer says — one reading for TLS over TCP and
|
|
2117
|
+
* DTLS, which each had their own and disagreed (an empty alert, a level that
|
|
2118
|
+
* is neither, user_canceled at the fatal level). Two bytes, a level of 1 or
|
|
2119
|
+
* 2 (RFC 8446 §6, RFC 5246 §7.2); an empty one is a zero-length fragment
|
|
2120
|
+
* (§5.1 forbids them for alerts): unexpected_message; another length,
|
|
2121
|
+
* decode_error; another level, illegal_parameter — each as
|
|
2122
|
+
* { error: { alert, message } }. Otherwise { level, desc, fatal }:
|
|
2123
|
+
* in TLS 1.3 every alert but close_notify and user_canceled is fatal,
|
|
2124
|
+
* "regardless of the AlertLevel" (§6.2, §6); before it, the level says.
|
|
2125
|
+
*/
|
|
2126
|
+
function read_peer_alert(body, is13) {
|
|
2127
|
+
if (!body || body.length === 0) return { error: { alert: TLS_ALERT.UNEXPECTED_MESSAGE, message: 'empty alert record' } };
|
|
2128
|
+
if (body.length !== 2) return { error: { alert: TLS_ALERT.DECODE_ERROR, message: 'alert of ' + body.length + ' bytes, not 2' } };
|
|
2129
|
+
let level = body[0], desc = body[1];
|
|
2130
|
+
if (level !== TLS_ALERT_LEVEL.WARNING && level !== TLS_ALERT_LEVEL.FATAL) {
|
|
2131
|
+
return { error: { alert: TLS_ALERT.ILLEGAL_PARAMETER, message: 'alert of level ' + level } };
|
|
2132
|
+
}
|
|
2133
|
+
let fatal = is13 ? (desc !== TLS_ALERT.CLOSE_NOTIFY && desc !== TLS_ALERT.USER_CANCELED) : level === TLS_ALERT_LEVEL.FATAL;
|
|
2134
|
+
return { level: level, desc: desc, fatal: fatal };
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// An alert's name, lowercase as the RFC writes it ('record_overflow'), or null.
|
|
2138
|
+
function alert_name(desc) {
|
|
2139
|
+
for (const k in TLS_ALERT) if (TLS_ALERT[k] === desc) return k.toLowerCase();
|
|
2140
|
+
return null;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* A fatal alert of OURS, as every layer reports it — TLSSession, TLSSocket
|
|
2145
|
+
* and DTLSSession alike, which gave a bare Error, one with a code, and one
|
|
2146
|
+
* with neither: the message, err.alert (what we sent), err.code —
|
|
2147
|
+
* ERR_SSL_<ALERT> (OpenSSL's reason codes as node:tls has them:
|
|
2148
|
+
* ERR_SSL_RECORD_OVERFLOW, ERR_SSL_UNEXPECTED_MESSAGE …) unless a more
|
|
2149
|
+
* precise one is given (ERR_TLS_CERT_PIN_MISMATCH). An error that already
|
|
2150
|
+
* has them keeps its own.
|
|
2151
|
+
*/
|
|
2152
|
+
function local_alert_error(desc, message, code) {
|
|
2153
|
+
const err = message instanceof Error ? message : new Error(message);
|
|
2154
|
+
if (err.alert === undefined) err.alert = desc;
|
|
2155
|
+
if (!err.code) err.code = code || ('ERR_SSL_' + (alert_name(desc) || 'alert_' + desc).toUpperCase());
|
|
2156
|
+
return err;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/**
|
|
2160
|
+
* A fatal alert the peer sent, as the error both transports report it — TLS
|
|
2161
|
+
* over TCP and DTLS alike: 'peer sent fatal alert: <name> (<code>)',
|
|
2162
|
+
* code ERR_TLS_ALERT_RECEIVED, err.alert the code. DTLS reported
|
|
2163
|
+
* 'DTLS alert: level=2 desc=121', no code, no name.
|
|
2164
|
+
*/
|
|
2165
|
+
function peer_alert_error(desc) {
|
|
2166
|
+
const err = new Error('peer sent fatal alert: ' + (alert_name(desc) || 'alert') + ' (' + desc + ')');
|
|
2167
|
+
err.code = 'ERR_TLS_ALERT_RECEIVED';
|
|
2168
|
+
err.alert = desc;
|
|
2169
|
+
return err;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2068
2172
|
export {
|
|
2173
|
+
build_dtls_fragment,
|
|
2174
|
+
parseError,
|
|
2175
|
+
local_alert_error,
|
|
2176
|
+
alert_name,
|
|
2177
|
+
peer_alert_error,
|
|
2178
|
+
read_peer_alert,
|
|
2179
|
+
MAX_WARNING_ALERTS,
|
|
2180
|
+
MAX_RECORD_PLAINTEXT,
|
|
2069
2181
|
HANDSHAKE_MESSAGE_MAX,
|
|
2070
2182
|
TLS_VERSION,
|
|
2071
2183
|
DTLS_VERSION,
|
|
2072
2184
|
is_tls13_wire,
|
|
2073
2185
|
is_tls12_wire,
|
|
2186
|
+
version_name,
|
|
2074
2187
|
TLS_CONTENT_TYPE,
|
|
2075
2188
|
TLS_ALERT_LEVEL,
|
|
2076
2189
|
TLS_ALERT,
|