lemon-tls 0.5.5 → 0.7.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/package.json +2 -7
- package/src/compat.js +10 -0
- package/src/crypto.js +160 -51
- package/src/dtls_session.js +636 -73
- package/src/dtls_socket.js +7 -0
- package/src/record.js +226 -43
- package/src/session/ecdh.js +124 -0
- package/src/session/message.js +40 -1
- package/src/session/signing.js +240 -47
- package/src/session/ticket.js +6 -3
- package/src/tls_session.js +2000 -454
- package/src/tls_socket.js +378 -40
- package/src/utils.js +7 -1
- package/src/wire.js +344 -18
package/src/wire.js
CHANGED
|
@@ -17,6 +17,36 @@ const DTLS_VERSION = {
|
|
|
17
17
|
DTLS1_3: 0xFEFC
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Does `v` denote the TLS 1.3 *message* format?
|
|
22
|
+
*
|
|
23
|
+
* SINGLE SOURCE OF TRUTH for every "is this 1.3?" branch in this file.
|
|
24
|
+
* The reason it has to exist: DTLS 1.3 is TLS 1.3 at the message layer, but
|
|
25
|
+
* its version constant is 0xFEFC, not 0x0304. Every `v === TLS_VERSION.TLS1_3`
|
|
26
|
+
* comparison is therefore silently FALSE for DTLS 1.3, and silently selects
|
|
27
|
+
* the legacy TLS 1.2 encoding of whatever message is being built.
|
|
28
|
+
*
|
|
29
|
+
* That is not hypothetical: build_certificate below did exactly this, so a
|
|
30
|
+
* DTLS 1.3 server emitted a TLS 1.2 Certificate (no certificate_request_context,
|
|
31
|
+
* no per-entry extensions vector). The peer parsed byte 0 as the context length
|
|
32
|
+
* — which happened to be 0x00 and so passed — then read the next three bytes as
|
|
33
|
+
* certificate_list length and got a value far past the end of the message.
|
|
34
|
+
* BoringSSL answers that with decode_error(50).
|
|
35
|
+
*
|
|
36
|
+
* It only ever bit the server role, because the client-side Certificate call
|
|
37
|
+
* sites hardcode TLS_VERSION.TLS1_3 instead of passing the negotiated version.
|
|
38
|
+
* Two copies of this library also agree with each other, because
|
|
39
|
+
* parse_certificate sniffs both encodings — so only a conforming peer objects.
|
|
40
|
+
*/
|
|
41
|
+
function is_tls13_wire(v) {
|
|
42
|
+
return v === TLS_VERSION.TLS1_3 || v === DTLS_VERSION.DTLS1_3;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Companion to is_tls13_wire for the 1.2 message format. */
|
|
46
|
+
function is_tls12_wire(v) {
|
|
47
|
+
return v === TLS_VERSION.TLS1_2 || v === DTLS_VERSION.DTLS1_2;
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
const TLS_CONTENT_TYPE = {
|
|
21
51
|
CHANGE_CIPHER_SPEC: 20,
|
|
22
52
|
ALERT: 21,
|
|
@@ -49,6 +79,7 @@ const TLS_ALERT = {
|
|
|
49
79
|
MISSING_EXTENSION: 109,
|
|
50
80
|
UNSUPPORTED_EXTENSION: 110,
|
|
51
81
|
UNRECOGNIZED_NAME: 112,
|
|
82
|
+
CERTIFICATE_REQUIRED: 116,
|
|
52
83
|
NO_APPLICATION_PROTOCOL: 120
|
|
53
84
|
};
|
|
54
85
|
|
|
@@ -145,27 +176,64 @@ function w_u48(buf, off, v) {
|
|
|
145
176
|
return off;
|
|
146
177
|
}
|
|
147
178
|
|
|
179
|
+
/**
|
|
180
|
+
* 64-bit big-endian write. Split into two 32-bit halves rather than using
|
|
181
|
+
* BigInt: DTLS epochs and sequence numbers are both far below 2^53, so Number
|
|
182
|
+
* arithmetic is exact here and roughly an order of magnitude faster.
|
|
183
|
+
*/
|
|
184
|
+
function w_u64(buf, off, v) {
|
|
185
|
+
let hi = Math.floor(v / 0x100000000);
|
|
186
|
+
let lo = v >>> 0;
|
|
187
|
+
buf[off++] = (hi >>> 24) & 0xFF;
|
|
188
|
+
buf[off++] = (hi >>> 16) & 0xFF;
|
|
189
|
+
buf[off++] = (hi >>> 8) & 0xFF;
|
|
190
|
+
buf[off++] = (hi ) & 0xFF;
|
|
191
|
+
buf[off++] = (lo >>> 24) & 0xFF;
|
|
192
|
+
buf[off++] = (lo >>> 16) & 0xFF;
|
|
193
|
+
buf[off++] = (lo >>> 8) & 0xFF;
|
|
194
|
+
buf[off++] = (lo ) & 0xFF;
|
|
195
|
+
return off;
|
|
196
|
+
}
|
|
197
|
+
|
|
148
198
|
function w_bytes(buf, off, b) {
|
|
149
199
|
buf.set(b, off);
|
|
150
200
|
return off + b.length;
|
|
151
201
|
}
|
|
152
202
|
|
|
153
203
|
/* ============================ Binary read helpers ============================ */
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Parse error carrying the TLS alert description to send.
|
|
207
|
+
* The session layer catches these at its single parse entry point and
|
|
208
|
+
* converts them into a fatal alert (default: decode_error, RFC 8446 §6.2).
|
|
209
|
+
* Keeping the alert code ON the error keeps wire.js policy-free — it only
|
|
210
|
+
* states WHAT is malformed; the session decides to abort.
|
|
211
|
+
*/
|
|
212
|
+
function parseError(msg, alertDesc) {
|
|
213
|
+
let e = new Error(msg);
|
|
214
|
+
e.alertDesc = (alertDesc === undefined) ? TLS_ALERT.DECODE_ERROR : alertDesc;
|
|
215
|
+
return e;
|
|
216
|
+
}
|
|
217
|
+
|
|
154
218
|
function r_u8(buf, off) {
|
|
219
|
+
if (off + 1 > buf.length) throw parseError('truncated u8 at ' + off);
|
|
155
220
|
return [buf[off++] >>> 0, off];
|
|
156
221
|
}
|
|
157
222
|
|
|
158
223
|
function r_u16(buf, off) {
|
|
224
|
+
if (off + 2 > buf.length) throw parseError('truncated u16 at ' + off);
|
|
159
225
|
let v = ((buf[off] << 8) | buf[off + 1]) >>> 0;
|
|
160
226
|
return [v, off + 2];
|
|
161
227
|
}
|
|
162
228
|
|
|
163
229
|
function r_u24(buf, off) {
|
|
230
|
+
if (off + 3 > buf.length) throw parseError('truncated u24 at ' + off);
|
|
164
231
|
let v = ((buf[off] << 16) | (buf[off + 1] << 8) | buf[off + 2]) >>> 0;
|
|
165
232
|
return [v, off + 3];
|
|
166
233
|
}
|
|
167
234
|
|
|
168
235
|
function r_bytes(buf, off, n) {
|
|
236
|
+
if (off + n > buf.length) throw parseError('truncated field: need ' + n + ' bytes at ' + off + ', have ' + (buf.length - off));
|
|
169
237
|
let slice;
|
|
170
238
|
if (buf instanceof Uint8Array) {
|
|
171
239
|
// real slice from Uint8Array
|
|
@@ -244,10 +312,94 @@ exts.COOKIE = { encode: null, decode: null };
|
|
|
244
312
|
exts.RENEGOTIATION_INFO = { encode: null, decode: null };
|
|
245
313
|
exts.SESSION_TICKET = { encode: null, decode: null };
|
|
246
314
|
exts.EXTENDED_MASTER_SECRET = { encode: null, decode: null };
|
|
315
|
+
exts.CERTIFICATE_AUTHORITIES = { encode: null, decode: null };
|
|
316
|
+
|
|
317
|
+
/* ---------------------- CERTIFICATE_AUTHORITIES (47) ---------------------- */
|
|
318
|
+
// RFC 8446 §4.2.4: DistinguishedName authorities<3..2^16-1> — the list is
|
|
319
|
+
// declared non-empty by the grammar itself, so an empty list is a peer
|
|
320
|
+
// encoding bug and must be rejected as decode_error (BoGo:
|
|
321
|
+
// RejectEmptyCertificateAuthorities).
|
|
322
|
+
exts.CERTIFICATE_AUTHORITIES.decode = function (data) {
|
|
323
|
+
let off = 0;
|
|
324
|
+
let list;
|
|
325
|
+
[list, off] = readVec(data, off, 2);
|
|
326
|
+
if (off !== data.length) throw parseError('trailing data after certificate_authorities');
|
|
327
|
+
if (list.length === 0) throw parseError('empty certificate_authorities list');
|
|
328
|
+
|
|
329
|
+
let out = [];
|
|
330
|
+
let o2 = 0;
|
|
331
|
+
while (o2 < list.length) {
|
|
332
|
+
let dn;
|
|
333
|
+
[dn, o2] = readVec(list, o2, 2);
|
|
334
|
+
if (dn.length === 0) throw parseError('empty DistinguishedName in certificate_authorities');
|
|
335
|
+
out.push(dn);
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/* ------------------------------ USE_SRTP (14) ------------------------------ */
|
|
341
|
+
/*
|
|
342
|
+
* RFC 5764 §4.1.1:
|
|
343
|
+
* struct {
|
|
344
|
+
* SRTPProtectionProfiles SRTPProtectionProfiles; // uint16 list, vector<2>
|
|
345
|
+
* opaque srtp_mki<0..255>;
|
|
346
|
+
* } UseSRTPData;
|
|
347
|
+
*
|
|
348
|
+
* The ClientHello carries every profile it accepts; the ServerHello (TLS 1.2)
|
|
349
|
+
* or EncryptedExtensions (TLS 1.3) carries exactly one. The MKI is opaque to
|
|
350
|
+
* the handshake and is simply carried through.
|
|
351
|
+
*/
|
|
352
|
+
exts.USE_SRTP = { encode: null, decode: null };
|
|
353
|
+
|
|
354
|
+
exts.USE_SRTP.encode = function (value) {
|
|
355
|
+
let profiles = (value && value.profiles) ? value.profiles : [];
|
|
356
|
+
let mki = toU8((value && value.mki) ? value.mki : new Uint8Array(0));
|
|
357
|
+
if (mki.length > 255) throw parseError('use_srtp MKI longer than 255 bytes');
|
|
358
|
+
|
|
359
|
+
let out = new Uint8Array(2 + profiles.length * 2 + 1 + mki.length);
|
|
360
|
+
let off = 0;
|
|
361
|
+
off = w_u16(out, off, profiles.length * 2);
|
|
362
|
+
for (let i = 0; i < profiles.length; i++) off = w_u16(out, off, profiles[i] & 0xFFFF);
|
|
363
|
+
off = w_u8(out, off, mki.length);
|
|
364
|
+
w_bytes(out, off, mki);
|
|
365
|
+
return out;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
exts.USE_SRTP.decode = function (data) {
|
|
369
|
+
let off = 0;
|
|
370
|
+
let listLen;
|
|
371
|
+
[listLen, off] = r_u16(data, off);
|
|
372
|
+
// The list is a vector of uint16, so an odd length is malformed.
|
|
373
|
+
if (listLen % 2 !== 0) throw parseError('use_srtp profile list has odd length');
|
|
374
|
+
if (off + listLen > data.length) throw parseError('use_srtp profile list overruns the extension');
|
|
375
|
+
|
|
376
|
+
let profiles = [];
|
|
377
|
+
let end = off + listLen;
|
|
378
|
+
while (off < end) {
|
|
379
|
+
let p;
|
|
380
|
+
[p, off] = r_u16(data, off);
|
|
381
|
+
profiles.push(p);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let mkiLen;
|
|
385
|
+
[mkiLen, off] = r_u8(data, off);
|
|
386
|
+
if (off + mkiLen > data.length) throw parseError('use_srtp MKI overruns the extension');
|
|
387
|
+
let mki;
|
|
388
|
+
[mki, off] = r_bytes(data, off, mkiLen);
|
|
389
|
+
|
|
390
|
+
return { profiles: profiles, mki: mki };
|
|
391
|
+
};
|
|
247
392
|
|
|
248
393
|
/* ------------------------------ SERVER_NAME (0) ------------------------------ */
|
|
249
394
|
exts.SERVER_NAME.encode = function (value) {
|
|
250
|
-
|
|
395
|
+
// Server-side acknowledgement: a zero-length body, no ServerNameList.
|
|
396
|
+
// Mirrors the decoder's asymmetry (RFC 6066 §3 / RFC 8446 §4.4.2) so the
|
|
397
|
+
// extension can round-trip in both directions through one implementation.
|
|
398
|
+
if (value === null || value === undefined || value === "") {
|
|
399
|
+
return new Uint8Array(0);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let host = toU8(value);
|
|
251
403
|
|
|
252
404
|
// one name: type(1)=0, len(2), bytes
|
|
253
405
|
const inner = new Uint8Array(1 + 2 + host.length);
|
|
@@ -262,6 +414,20 @@ exts.SERVER_NAME.encode = function (value) {
|
|
|
262
414
|
};
|
|
263
415
|
|
|
264
416
|
exts.SERVER_NAME.decode = function (data) {
|
|
417
|
+
// The extension is ASYMMETRIC (RFC 6066 §3, RFC 8446 §4.4.2):
|
|
418
|
+
//
|
|
419
|
+
// client → server : a full ServerNameList (host_name entries)
|
|
420
|
+
// server → client : a COMPLETELY EMPTY extension — zero-length body — sent
|
|
421
|
+
// in EncryptedExtensions purely to acknowledge that the
|
|
422
|
+
// name was understood. There is no ServerNameList in
|
|
423
|
+
// this direction at all.
|
|
424
|
+
//
|
|
425
|
+
// Decoding both directions with one reader meant the acknowledgement was
|
|
426
|
+
// parsed as a truncated list ("truncated u16 at 0") and a perfectly legal
|
|
427
|
+
// handshake was rejected. An empty body is not a malformed list; it is the
|
|
428
|
+
// other half of the grammar.
|
|
429
|
+
if (!data || data.length === 0) return null;
|
|
430
|
+
|
|
265
431
|
let off = 0;
|
|
266
432
|
let list;
|
|
267
433
|
[list, off] = readVec(data, off, 2);
|
|
@@ -363,7 +529,15 @@ exts.SUPPORTED_GROUPS.decode = function (data) {
|
|
|
363
529
|
|
|
364
530
|
/* -------------------------- SIGNATURE_ALGORITHMS (13) -------------------------- */
|
|
365
531
|
exts.SIGNATURE_ALGORITHMS.encode = function (value) {
|
|
366
|
-
|
|
532
|
+
// A codec encodes what it is handed; the set of signature schemes is policy
|
|
533
|
+
// and belongs to signing.js (default_signature_schemes), not here. The old
|
|
534
|
+
// hardcoded fallback silently replaced a caller's real list whenever the
|
|
535
|
+
// value arrived empty — the same class of bug as the cipher_suites fallback
|
|
536
|
+
// that was removed from build_hello. Fail loudly instead.
|
|
537
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
538
|
+
throw new Error('SIGNATURE_ALGORITHMS.encode requires a non-empty scheme list');
|
|
539
|
+
}
|
|
540
|
+
let algs = value;
|
|
367
541
|
|
|
368
542
|
const body = new Uint8Array(2 + algs.length * 2);
|
|
369
543
|
let off = 0;
|
|
@@ -628,12 +802,22 @@ exts.ALPN.decode = function (data) {
|
|
|
628
802
|
[n, off] = r_u16(data, off);
|
|
629
803
|
|
|
630
804
|
let end = off + n;
|
|
805
|
+
if (end > data.length) throw parseError('ALPN list length exceeds extension body');
|
|
806
|
+
if (end < data.length) throw parseError('trailing data after ALPN protocol list');
|
|
807
|
+
|
|
631
808
|
let out = [];
|
|
632
809
|
|
|
633
810
|
while (off < end) {
|
|
634
811
|
let l;
|
|
635
812
|
[l, off] = r_u8(data, off);
|
|
636
813
|
|
|
814
|
+
// RFC 7301 §3.1 declares ProtocolName<1..2^8-1>: the vector's lower bound
|
|
815
|
+
// is 1, so a zero-length name is a grammar violation rather than an empty
|
|
816
|
+
// string. Accepting it produced a phantom "" protocol that could then be
|
|
817
|
+
// negotiated, and left the list length inconsistent with its contents.
|
|
818
|
+
if (l === 0) throw parseError('ALPN protocol name of zero length');
|
|
819
|
+
if (off + l > end) throw parseError('ALPN protocol name overruns the list');
|
|
820
|
+
|
|
637
821
|
let v;
|
|
638
822
|
[v, off] = r_bytes(data, off, l);
|
|
639
823
|
|
|
@@ -752,24 +936,54 @@ function build_extensions(list) {
|
|
|
752
936
|
return out;
|
|
753
937
|
}
|
|
754
938
|
|
|
755
|
-
|
|
939
|
+
/**
|
|
940
|
+
* Parse a bare extension LIST — the caller has already consumed the 2-byte
|
|
941
|
+
* vector length and hands us exactly the list bytes.
|
|
942
|
+
*
|
|
943
|
+
* Two conventions exist in this file and mixing them is a real bug source:
|
|
944
|
+
* - Structures where the extensions vector ENDS the enclosing message
|
|
945
|
+
* (hello, EncryptedExtensions, NewSessionTicket) pass the buffer WITH the
|
|
946
|
+
* length prefix → use parse_extensions().
|
|
947
|
+
* - Structures where the vector is an inner field followed by more data, or
|
|
948
|
+
* where the caller already read the prefix (Certificate entries,
|
|
949
|
+
* CertificateRequest) → use parse_extension_list().
|
|
950
|
+
* Both share this core so the duplicate/overflow rules apply identically.
|
|
951
|
+
*/
|
|
952
|
+
function parse_extension_list(buf) {
|
|
756
953
|
let off = 0;
|
|
757
|
-
let n;
|
|
758
|
-
[n, off] = r_u16(buf, off);
|
|
759
|
-
|
|
760
|
-
let end = off + n;
|
|
761
954
|
let out = [];
|
|
955
|
+
let seen = {}; // RFC 8446 §4.2: "There MUST NOT be more than one extension of the same type"
|
|
762
956
|
|
|
763
|
-
while (off <
|
|
957
|
+
while (off < buf.length) {
|
|
764
958
|
let t;
|
|
765
959
|
[t, off] = r_u16(buf, off);
|
|
766
960
|
|
|
767
961
|
let l;
|
|
768
962
|
[l, off] = r_u16(buf, off);
|
|
963
|
+
if (off + l > buf.length) throw parseError('extension body overflows extension list');
|
|
769
964
|
|
|
770
965
|
let d;
|
|
771
966
|
[d, off] = r_bytes(buf, off, l);
|
|
772
967
|
|
|
968
|
+
if (seen[t] === true) {
|
|
969
|
+
// decode_error (50), NOT illegal_parameter (47).
|
|
970
|
+
//
|
|
971
|
+
// RFC 8446 §4.2 says "There MUST NOT be more than one extension of the
|
|
972
|
+
// same type ... Receivers MUST check for this and abort with an
|
|
973
|
+
// 'illegal_parameter' alert", so 47 is a defensible reading and this
|
|
974
|
+
// code used to send it. BoringSSL — and therefore BoGo, and therefore
|
|
975
|
+
// most of the ecosystem that tests against it — treats a repeated
|
|
976
|
+
// extension as a malformed extensions VECTOR rather than a bad parameter
|
|
977
|
+
// value, and sends decode_error. Interoperating with deployed peers wins
|
|
978
|
+
// over the letter of the sentence here.
|
|
979
|
+
//
|
|
980
|
+
// Left as an explicit note because the RFC text genuinely reads the other
|
|
981
|
+
// way: without this comment the next person to check §4.2 will "fix" it
|
|
982
|
+
// back and silently break the same seven tests again.
|
|
983
|
+
throw parseError('duplicate extension type ' + t, TLS_ALERT.DECODE_ERROR);
|
|
984
|
+
}
|
|
985
|
+
seen[t] = true;
|
|
986
|
+
|
|
773
987
|
let name = ext_name_by_code(t);
|
|
774
988
|
let dec = exts[name] && exts[name].decode;
|
|
775
989
|
let val = dec ? dec(d) : null;
|
|
@@ -780,6 +994,25 @@ function parse_extensions(buf) {
|
|
|
780
994
|
return out;
|
|
781
995
|
}
|
|
782
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Parse a length-prefixed extensions vector that ENDS the enclosing structure.
|
|
999
|
+
* `buf` must START with the 2-byte vector length and contain nothing after it.
|
|
1000
|
+
*/
|
|
1001
|
+
function parse_extensions(buf) {
|
|
1002
|
+
let off = 0;
|
|
1003
|
+
let n;
|
|
1004
|
+
[n, off] = r_u16(buf, off);
|
|
1005
|
+
|
|
1006
|
+
let end = off + n;
|
|
1007
|
+
// The declared vector length must fit the buffer, and the buffer must not
|
|
1008
|
+
// carry trailing bytes past the vector — either way it's a framing error
|
|
1009
|
+
// in the peer's encoder (decode_error, RFC 8446 §6.2).
|
|
1010
|
+
if (end > buf.length) throw parseError('extensions vector length exceeds body');
|
|
1011
|
+
if (end < buf.length) throw parseError('trailing data after extensions vector');
|
|
1012
|
+
|
|
1013
|
+
return parse_extension_list(buf.subarray(off, end));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
783
1016
|
|
|
784
1017
|
/* ================================ Hello I/O ================================ */
|
|
785
1018
|
function build_hello(params) {
|
|
@@ -796,7 +1029,15 @@ function build_hello(params) {
|
|
|
796
1029
|
let extsBuf = build_extensions(params.extensions || []);
|
|
797
1030
|
|
|
798
1031
|
if (kind === 'client') {
|
|
799
|
-
|
|
1032
|
+
// A codec encodes what it is handed; it must not invent policy. This
|
|
1033
|
+
// used to fall back to a hardcoded suite list, which silently replaced the
|
|
1034
|
+
// caller's configuration whenever the field name didn't line up — the
|
|
1035
|
+
// exact failure the `cipher_suites` plural fix elsewhere was chasing. Fail
|
|
1036
|
+
// loudly instead, so a wiring mistake surfaces at the call site.
|
|
1037
|
+
let cs = params.cipher_suites;
|
|
1038
|
+
if (!Array.isArray(cs) || cs.length === 0) {
|
|
1039
|
+
throw new Error('build_hello: ClientHello requires a non-empty cipher_suites array');
|
|
1040
|
+
}
|
|
800
1041
|
|
|
801
1042
|
const csBlock = new Uint8Array(2 + cs.length * 2);
|
|
802
1043
|
let o = 0;
|
|
@@ -959,6 +1200,14 @@ function build_certificate(params) {
|
|
|
959
1200
|
// TLS 1.3 → מחזיר: request_context (vec<1>) || certificate_list (vec<3> של [ cert(vec<3>) || extensions(vec<2>) ]*)
|
|
960
1201
|
// TLS 1.2 → מחזיר: certificate_list (vec<3> של cert(vec<3>)*), מתעלם מ-extensions/request_context
|
|
961
1202
|
|
|
1203
|
+
// NOTE the asymmetry with build_certificate_request, which defaults to
|
|
1204
|
+
// TLS1_3. Both defaults are kept as they were, because callers outside this
|
|
1205
|
+
// file may rely on them; every call site inside the library passes `version`
|
|
1206
|
+
// explicitly, so the default is never actually exercised here. What matters
|
|
1207
|
+
// is that the branch below asks is_tls13_wire(v), not v === TLS1_3 — the
|
|
1208
|
+
// mismatched defaults are what made the two messages in the SAME server
|
|
1209
|
+
// flight come out in two different encodings, which is why this stayed
|
|
1210
|
+
// hidden for so long.
|
|
962
1211
|
let v = params.version || TLS_VERSION.TLS1_2;
|
|
963
1212
|
|
|
964
1213
|
// Normalize to entries[] always
|
|
@@ -969,8 +1218,14 @@ function build_certificate(params) {
|
|
|
969
1218
|
}
|
|
970
1219
|
if (!entries) entries = [];
|
|
971
1220
|
|
|
972
|
-
if (v
|
|
973
|
-
|
|
1221
|
+
if (is_tls13_wire(v)) {
|
|
1222
|
+
// Accept either spelling of the context field. build_tls_message callers
|
|
1223
|
+
// pass `certificate_request_context` (the RFC 8446 field name); this
|
|
1224
|
+
// builder originally read only `request_context`, so the value was
|
|
1225
|
+
// dropped without a word. Harmless during the initial handshake, where
|
|
1226
|
+
// the context is always empty — but post-handshake client authentication
|
|
1227
|
+
// is identified BY that context, so it would have failed silently.
|
|
1228
|
+
let ctx = toU8(params.request_context || params.certificate_request_context || new Uint8Array(0));
|
|
974
1229
|
|
|
975
1230
|
let entryParts = [];
|
|
976
1231
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -1037,7 +1292,10 @@ function parse_certificate(body) {
|
|
|
1037
1292
|
let extLen; [extLen, off] = r_u16(body, off); // extensions vec<2>
|
|
1038
1293
|
let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
|
|
1039
1294
|
|
|
1040
|
-
|
|
1295
|
+
// extRaw is the list body (prefix already consumed above), so use
|
|
1296
|
+
// the list parser — parse_extensions() would misread the first two
|
|
1297
|
+
// bytes of the first extension as a vector length.
|
|
1298
|
+
const extensions = parse_extension_list(extRaw);
|
|
1041
1299
|
entries.push({ cert, extensions });
|
|
1042
1300
|
}
|
|
1043
1301
|
|
|
@@ -1105,6 +1363,10 @@ function parse_certificate_verify(body) {
|
|
|
1105
1363
|
let sig;
|
|
1106
1364
|
[sig, off] = r_bytes(body, off, slen);
|
|
1107
1365
|
|
|
1366
|
+
// The signature vector ends the message; anything after it is framing junk.
|
|
1367
|
+
if (off !== body.length) throw parseError('trailing data after CertificateVerify signature');
|
|
1368
|
+
if (slen === 0) throw parseError('empty CertificateVerify signature');
|
|
1369
|
+
|
|
1108
1370
|
return { scheme: alg, signature: sig };
|
|
1109
1371
|
}
|
|
1110
1372
|
|
|
@@ -1165,6 +1427,12 @@ function parse_new_session_ticket(body) {
|
|
|
1165
1427
|
let ticket;
|
|
1166
1428
|
[ticket, off] = r_bytes(body, off, tlen);
|
|
1167
1429
|
|
|
1430
|
+
// RFC 8446 §4.6.1 declares ticket<1..2^16-1>: the vector's lower bound is 1,
|
|
1431
|
+
// so a zero-length ticket is a grammar violation, not an empty-but-valid
|
|
1432
|
+
// value. Enforced here in the codec because it is a property of the wire
|
|
1433
|
+
// format; what a session then DOES with a ticket is policy and lives above.
|
|
1434
|
+
if (tlen === 0) throw parseError('NewSessionTicket carries an empty ticket');
|
|
1435
|
+
|
|
1168
1436
|
let extBuf = body.subarray(off);
|
|
1169
1437
|
let extensions = extBuf.length ? parse_extensions(extBuf) : [];
|
|
1170
1438
|
|
|
@@ -1195,11 +1463,48 @@ function parse_new_session_ticket(body) {
|
|
|
1195
1463
|
function build_certificate_request(params) {
|
|
1196
1464
|
let v = params && params.version || TLS_VERSION.TLS1_3;
|
|
1197
1465
|
|
|
1198
|
-
if (v
|
|
1199
|
-
|
|
1200
|
-
let
|
|
1201
|
-
|
|
1202
|
-
|
|
1466
|
+
if (is_tls13_wire(v)) {
|
|
1467
|
+
// Same dual-spelling accommodation as build_certificate above.
|
|
1468
|
+
let ctx = toU8((params && (params.request_context || params.certificate_request_context)) ||
|
|
1469
|
+
new Uint8Array(0));
|
|
1470
|
+
|
|
1471
|
+
// RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be specified"
|
|
1472
|
+
// in a TLS 1.3 CertificateRequest.
|
|
1473
|
+
//
|
|
1474
|
+
// Extension assembly belongs here, next to build_hello's, so callers pass
|
|
1475
|
+
// SEMANTICS (which algorithms) rather than pre-encoded extension records.
|
|
1476
|
+
// Previously only a pre-built `extensions` array was honoured while the
|
|
1477
|
+
// session passed `signature_algorithms` as a flat field — silently
|
|
1478
|
+
// dropped, producing a CertificateRequest with an empty extensions vector
|
|
1479
|
+
// that strict peers reject outright ("missing sigalgs extension").
|
|
1480
|
+
let extsBuf;
|
|
1481
|
+
if (params && params.extensions && !Array.isArray(params.extensions)) {
|
|
1482
|
+
// Pre-encoded extensions vector supplied verbatim.
|
|
1483
|
+
extsBuf = params.extensions;
|
|
1484
|
+
} else {
|
|
1485
|
+
let extList = Array.isArray(params && params.extensions)
|
|
1486
|
+
? params.extensions.slice()
|
|
1487
|
+
: [];
|
|
1488
|
+
|
|
1489
|
+
let hasSigAlgs = false;
|
|
1490
|
+
for (let i = 0; i < extList.length; i++) {
|
|
1491
|
+
let t = extList[i] && extList[i].type;
|
|
1492
|
+
if (t === 'SIGNATURE_ALGORITHMS' || t === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (!hasSigAlgs) {
|
|
1496
|
+
let sa = (params && params.signature_algorithms) || [];
|
|
1497
|
+
if (sa.length === 0) {
|
|
1498
|
+
throw new Error('build_certificate_request: TLS 1.3 requires a non-empty signature_algorithms list');
|
|
1499
|
+
}
|
|
1500
|
+
extList.unshift({ type: 'SIGNATURE_ALGORITHMS', value: sa });
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// certificate_authorities is OPTIONAL and its DistinguishedName list is
|
|
1504
|
+
// non-empty by grammar — an empty list means "no constraint", which is
|
|
1505
|
+
// expressed by omitting the extension, not by sending an empty one.
|
|
1506
|
+
extsBuf = build_extensions(extList);
|
|
1507
|
+
}
|
|
1203
1508
|
|
|
1204
1509
|
let ctxVec = veclen(1, ctx);
|
|
1205
1510
|
return concatUint8Arrays([ctxVec, extsBuf]);
|
|
@@ -1240,10 +1545,27 @@ function parse_certificate_request(body) {
|
|
|
1240
1545
|
if (1 + ctxLen + 2 + extLen === body.length) {
|
|
1241
1546
|
let ctx = body.subarray(1, 1 + ctxLen);
|
|
1242
1547
|
let extBuf = body.subarray(1 + ctxLen + 2);
|
|
1548
|
+
let crExts = parse_extension_list(extBuf);
|
|
1549
|
+
|
|
1550
|
+
// RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be
|
|
1551
|
+
// specified". Mirrors the same rule already enforced when BUILDING a
|
|
1552
|
+
// CertificateRequest — encoder and decoder agree on what the message
|
|
1553
|
+
// must contain, so we can neither emit nor accept one without it.
|
|
1554
|
+
let hasSigAlgs = false;
|
|
1555
|
+
for (let ci = 0; ci < crExts.length; ci++) {
|
|
1556
|
+
if (crExts[ci] && crExts[ci].type === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
|
|
1557
|
+
}
|
|
1558
|
+
if (!hasSigAlgs) {
|
|
1559
|
+
throw parseError('TLS 1.3 CertificateRequest without the mandatory signature_algorithms extension');
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1243
1562
|
return {
|
|
1244
1563
|
version: TLS_VERSION.TLS1_3,
|
|
1245
1564
|
request_context: ctx,
|
|
1246
|
-
|
|
1565
|
+
// extBuf skips the 2-byte prefix (validated as extLen above), so it
|
|
1566
|
+
// is a bare list — this was reading the prefix twice and breaking
|
|
1567
|
+
// every CertificateRequest.
|
|
1568
|
+
extensions: crExts
|
|
1247
1569
|
};
|
|
1248
1570
|
}
|
|
1249
1571
|
}
|
|
@@ -1612,6 +1934,8 @@ function parse_hello_verify_request(body) {
|
|
|
1612
1934
|
export {
|
|
1613
1935
|
TLS_VERSION,
|
|
1614
1936
|
DTLS_VERSION,
|
|
1937
|
+
is_tls13_wire,
|
|
1938
|
+
is_tls12_wire,
|
|
1615
1939
|
TLS_CONTENT_TYPE,
|
|
1616
1940
|
TLS_ALERT_LEVEL,
|
|
1617
1941
|
TLS_ALERT,
|
|
@@ -1622,6 +1946,7 @@ export {
|
|
|
1622
1946
|
w_u16,
|
|
1623
1947
|
w_u24,
|
|
1624
1948
|
w_u48,
|
|
1949
|
+
w_u64,
|
|
1625
1950
|
w_bytes,
|
|
1626
1951
|
r_u8,
|
|
1627
1952
|
r_u16,
|
|
@@ -1633,6 +1958,7 @@ export {
|
|
|
1633
1958
|
exts,
|
|
1634
1959
|
build_extensions,
|
|
1635
1960
|
parse_extensions,
|
|
1961
|
+
parse_extension_list,
|
|
1636
1962
|
|
|
1637
1963
|
build_message,
|
|
1638
1964
|
parse_message,
|