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/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
- let host = toU8(value || "");
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
- let algs = Array.isArray(value) && value.length>0 ? value : [0x0403, 0x0804, 0x0401];
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
- function parse_extensions(buf) {
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 < end) {
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
- let cs = params.cipher_suites || [0x1301, 0x1302, 0x1303, 0xC02F, 0xC02B];
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 === TLS_VERSION.TLS1_3) {
973
- let ctx = toU8(params.request_context || new Uint8Array(0));
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
- const extensions = extLen ? parse_extensions(extRaw) : [];
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 === TLS_VERSION.TLS1_3) {
1199
- let ctx = toU8((params && params.request_context) || new Uint8Array(0));
1200
- let extsBuf = Array.isArray(params && params.extensions)
1201
- ? build_extensions(params.extensions)
1202
- : (params && params.extensions) || veclen(2, new Uint8Array(0));
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
- extensions: parse_extensions(extBuf)
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,