lemon-tls 0.5.5 → 0.6.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 +1 -1
- package/src/compat.js +10 -0
- package/src/crypto.js +146 -48
- package/src/dtls_session.js +236 -53
- package/src/dtls_socket.js +7 -0
- package/src/record.js +88 -21
- package/src/session/ecdh.js +124 -0
- package/src/session/message.js +35 -0
- package/src/session/signing.js +240 -47
- package/src/tls_session.js +1826 -436
- package/src/tls_socket.js +378 -39
- package/src/wire.js +272 -14
package/src/wire.js
CHANGED
|
@@ -49,6 +49,7 @@ const TLS_ALERT = {
|
|
|
49
49
|
MISSING_EXTENSION: 109,
|
|
50
50
|
UNSUPPORTED_EXTENSION: 110,
|
|
51
51
|
UNRECOGNIZED_NAME: 112,
|
|
52
|
+
CERTIFICATE_REQUIRED: 116,
|
|
52
53
|
NO_APPLICATION_PROTOCOL: 120
|
|
53
54
|
};
|
|
54
55
|
|
|
@@ -151,21 +152,39 @@ function w_bytes(buf, off, b) {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
/* ============================ Binary read helpers ============================ */
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parse error carrying the TLS alert description to send.
|
|
158
|
+
* The session layer catches these at its single parse entry point and
|
|
159
|
+
* converts them into a fatal alert (default: decode_error, RFC 8446 §6.2).
|
|
160
|
+
* Keeping the alert code ON the error keeps wire.js policy-free — it only
|
|
161
|
+
* states WHAT is malformed; the session decides to abort.
|
|
162
|
+
*/
|
|
163
|
+
function parseError(msg, alertDesc) {
|
|
164
|
+
let e = new Error(msg);
|
|
165
|
+
e.alertDesc = (alertDesc === undefined) ? TLS_ALERT.DECODE_ERROR : alertDesc;
|
|
166
|
+
return e;
|
|
167
|
+
}
|
|
168
|
+
|
|
154
169
|
function r_u8(buf, off) {
|
|
170
|
+
if (off + 1 > buf.length) throw parseError('truncated u8 at ' + off);
|
|
155
171
|
return [buf[off++] >>> 0, off];
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
function r_u16(buf, off) {
|
|
175
|
+
if (off + 2 > buf.length) throw parseError('truncated u16 at ' + off);
|
|
159
176
|
let v = ((buf[off] << 8) | buf[off + 1]) >>> 0;
|
|
160
177
|
return [v, off + 2];
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
function r_u24(buf, off) {
|
|
181
|
+
if (off + 3 > buf.length) throw parseError('truncated u24 at ' + off);
|
|
164
182
|
let v = ((buf[off] << 16) | (buf[off + 1] << 8) | buf[off + 2]) >>> 0;
|
|
165
183
|
return [v, off + 3];
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
function r_bytes(buf, off, n) {
|
|
187
|
+
if (off + n > buf.length) throw parseError('truncated field: need ' + n + ' bytes at ' + off + ', have ' + (buf.length - off));
|
|
169
188
|
let slice;
|
|
170
189
|
if (buf instanceof Uint8Array) {
|
|
171
190
|
// real slice from Uint8Array
|
|
@@ -244,10 +263,94 @@ exts.COOKIE = { encode: null, decode: null };
|
|
|
244
263
|
exts.RENEGOTIATION_INFO = { encode: null, decode: null };
|
|
245
264
|
exts.SESSION_TICKET = { encode: null, decode: null };
|
|
246
265
|
exts.EXTENDED_MASTER_SECRET = { encode: null, decode: null };
|
|
266
|
+
exts.CERTIFICATE_AUTHORITIES = { encode: null, decode: null };
|
|
267
|
+
|
|
268
|
+
/* ---------------------- CERTIFICATE_AUTHORITIES (47) ---------------------- */
|
|
269
|
+
// RFC 8446 §4.2.4: DistinguishedName authorities<3..2^16-1> — the list is
|
|
270
|
+
// declared non-empty by the grammar itself, so an empty list is a peer
|
|
271
|
+
// encoding bug and must be rejected as decode_error (BoGo:
|
|
272
|
+
// RejectEmptyCertificateAuthorities).
|
|
273
|
+
exts.CERTIFICATE_AUTHORITIES.decode = function (data) {
|
|
274
|
+
let off = 0;
|
|
275
|
+
let list;
|
|
276
|
+
[list, off] = readVec(data, off, 2);
|
|
277
|
+
if (off !== data.length) throw parseError('trailing data after certificate_authorities');
|
|
278
|
+
if (list.length === 0) throw parseError('empty certificate_authorities list');
|
|
279
|
+
|
|
280
|
+
let out = [];
|
|
281
|
+
let o2 = 0;
|
|
282
|
+
while (o2 < list.length) {
|
|
283
|
+
let dn;
|
|
284
|
+
[dn, o2] = readVec(list, o2, 2);
|
|
285
|
+
if (dn.length === 0) throw parseError('empty DistinguishedName in certificate_authorities');
|
|
286
|
+
out.push(dn);
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/* ------------------------------ USE_SRTP (14) ------------------------------ */
|
|
292
|
+
/*
|
|
293
|
+
* RFC 5764 §4.1.1:
|
|
294
|
+
* struct {
|
|
295
|
+
* SRTPProtectionProfiles SRTPProtectionProfiles; // uint16 list, vector<2>
|
|
296
|
+
* opaque srtp_mki<0..255>;
|
|
297
|
+
* } UseSRTPData;
|
|
298
|
+
*
|
|
299
|
+
* The ClientHello carries every profile it accepts; the ServerHello (TLS 1.2)
|
|
300
|
+
* or EncryptedExtensions (TLS 1.3) carries exactly one. The MKI is opaque to
|
|
301
|
+
* the handshake and is simply carried through.
|
|
302
|
+
*/
|
|
303
|
+
exts.USE_SRTP = { encode: null, decode: null };
|
|
304
|
+
|
|
305
|
+
exts.USE_SRTP.encode = function (value) {
|
|
306
|
+
let profiles = (value && value.profiles) ? value.profiles : [];
|
|
307
|
+
let mki = toU8((value && value.mki) ? value.mki : new Uint8Array(0));
|
|
308
|
+
if (mki.length > 255) throw parseError('use_srtp MKI longer than 255 bytes');
|
|
309
|
+
|
|
310
|
+
let out = new Uint8Array(2 + profiles.length * 2 + 1 + mki.length);
|
|
311
|
+
let off = 0;
|
|
312
|
+
off = w_u16(out, off, profiles.length * 2);
|
|
313
|
+
for (let i = 0; i < profiles.length; i++) off = w_u16(out, off, profiles[i] & 0xFFFF);
|
|
314
|
+
off = w_u8(out, off, mki.length);
|
|
315
|
+
w_bytes(out, off, mki);
|
|
316
|
+
return out;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
exts.USE_SRTP.decode = function (data) {
|
|
320
|
+
let off = 0;
|
|
321
|
+
let listLen;
|
|
322
|
+
[listLen, off] = r_u16(data, off);
|
|
323
|
+
// The list is a vector of uint16, so an odd length is malformed.
|
|
324
|
+
if (listLen % 2 !== 0) throw parseError('use_srtp profile list has odd length');
|
|
325
|
+
if (off + listLen > data.length) throw parseError('use_srtp profile list overruns the extension');
|
|
326
|
+
|
|
327
|
+
let profiles = [];
|
|
328
|
+
let end = off + listLen;
|
|
329
|
+
while (off < end) {
|
|
330
|
+
let p;
|
|
331
|
+
[p, off] = r_u16(data, off);
|
|
332
|
+
profiles.push(p);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let mkiLen;
|
|
336
|
+
[mkiLen, off] = r_u8(data, off);
|
|
337
|
+
if (off + mkiLen > data.length) throw parseError('use_srtp MKI overruns the extension');
|
|
338
|
+
let mki;
|
|
339
|
+
[mki, off] = r_bytes(data, off, mkiLen);
|
|
340
|
+
|
|
341
|
+
return { profiles: profiles, mki: mki };
|
|
342
|
+
};
|
|
247
343
|
|
|
248
344
|
/* ------------------------------ SERVER_NAME (0) ------------------------------ */
|
|
249
345
|
exts.SERVER_NAME.encode = function (value) {
|
|
250
|
-
|
|
346
|
+
// Server-side acknowledgement: a zero-length body, no ServerNameList.
|
|
347
|
+
// Mirrors the decoder's asymmetry (RFC 6066 §3 / RFC 8446 §4.4.2) so the
|
|
348
|
+
// extension can round-trip in both directions through one implementation.
|
|
349
|
+
if (value === null || value === undefined || value === "") {
|
|
350
|
+
return new Uint8Array(0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let host = toU8(value);
|
|
251
354
|
|
|
252
355
|
// one name: type(1)=0, len(2), bytes
|
|
253
356
|
const inner = new Uint8Array(1 + 2 + host.length);
|
|
@@ -262,6 +365,20 @@ exts.SERVER_NAME.encode = function (value) {
|
|
|
262
365
|
};
|
|
263
366
|
|
|
264
367
|
exts.SERVER_NAME.decode = function (data) {
|
|
368
|
+
// The extension is ASYMMETRIC (RFC 6066 §3, RFC 8446 §4.4.2):
|
|
369
|
+
//
|
|
370
|
+
// client → server : a full ServerNameList (host_name entries)
|
|
371
|
+
// server → client : a COMPLETELY EMPTY extension — zero-length body — sent
|
|
372
|
+
// in EncryptedExtensions purely to acknowledge that the
|
|
373
|
+
// name was understood. There is no ServerNameList in
|
|
374
|
+
// this direction at all.
|
|
375
|
+
//
|
|
376
|
+
// Decoding both directions with one reader meant the acknowledgement was
|
|
377
|
+
// parsed as a truncated list ("truncated u16 at 0") and a perfectly legal
|
|
378
|
+
// handshake was rejected. An empty body is not a malformed list; it is the
|
|
379
|
+
// other half of the grammar.
|
|
380
|
+
if (!data || data.length === 0) return null;
|
|
381
|
+
|
|
265
382
|
let off = 0;
|
|
266
383
|
let list;
|
|
267
384
|
[list, off] = readVec(data, off, 2);
|
|
@@ -363,7 +480,15 @@ exts.SUPPORTED_GROUPS.decode = function (data) {
|
|
|
363
480
|
|
|
364
481
|
/* -------------------------- SIGNATURE_ALGORITHMS (13) -------------------------- */
|
|
365
482
|
exts.SIGNATURE_ALGORITHMS.encode = function (value) {
|
|
366
|
-
|
|
483
|
+
// A codec encodes what it is handed; the set of signature schemes is policy
|
|
484
|
+
// and belongs to signing.js (default_signature_schemes), not here. The old
|
|
485
|
+
// hardcoded fallback silently replaced a caller's real list whenever the
|
|
486
|
+
// value arrived empty — the same class of bug as the cipher_suites fallback
|
|
487
|
+
// that was removed from build_hello. Fail loudly instead.
|
|
488
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
489
|
+
throw new Error('SIGNATURE_ALGORITHMS.encode requires a non-empty scheme list');
|
|
490
|
+
}
|
|
491
|
+
let algs = value;
|
|
367
492
|
|
|
368
493
|
const body = new Uint8Array(2 + algs.length * 2);
|
|
369
494
|
let off = 0;
|
|
@@ -628,12 +753,22 @@ exts.ALPN.decode = function (data) {
|
|
|
628
753
|
[n, off] = r_u16(data, off);
|
|
629
754
|
|
|
630
755
|
let end = off + n;
|
|
756
|
+
if (end > data.length) throw parseError('ALPN list length exceeds extension body');
|
|
757
|
+
if (end < data.length) throw parseError('trailing data after ALPN protocol list');
|
|
758
|
+
|
|
631
759
|
let out = [];
|
|
632
760
|
|
|
633
761
|
while (off < end) {
|
|
634
762
|
let l;
|
|
635
763
|
[l, off] = r_u8(data, off);
|
|
636
764
|
|
|
765
|
+
// RFC 7301 §3.1 declares ProtocolName<1..2^8-1>: the vector's lower bound
|
|
766
|
+
// is 1, so a zero-length name is a grammar violation rather than an empty
|
|
767
|
+
// string. Accepting it produced a phantom "" protocol that could then be
|
|
768
|
+
// negotiated, and left the list length inconsistent with its contents.
|
|
769
|
+
if (l === 0) throw parseError('ALPN protocol name of zero length');
|
|
770
|
+
if (off + l > end) throw parseError('ALPN protocol name overruns the list');
|
|
771
|
+
|
|
637
772
|
let v;
|
|
638
773
|
[v, off] = r_bytes(data, off, l);
|
|
639
774
|
|
|
@@ -752,24 +887,54 @@ function build_extensions(list) {
|
|
|
752
887
|
return out;
|
|
753
888
|
}
|
|
754
889
|
|
|
755
|
-
|
|
890
|
+
/**
|
|
891
|
+
* Parse a bare extension LIST — the caller has already consumed the 2-byte
|
|
892
|
+
* vector length and hands us exactly the list bytes.
|
|
893
|
+
*
|
|
894
|
+
* Two conventions exist in this file and mixing them is a real bug source:
|
|
895
|
+
* - Structures where the extensions vector ENDS the enclosing message
|
|
896
|
+
* (hello, EncryptedExtensions, NewSessionTicket) pass the buffer WITH the
|
|
897
|
+
* length prefix → use parse_extensions().
|
|
898
|
+
* - Structures where the vector is an inner field followed by more data, or
|
|
899
|
+
* where the caller already read the prefix (Certificate entries,
|
|
900
|
+
* CertificateRequest) → use parse_extension_list().
|
|
901
|
+
* Both share this core so the duplicate/overflow rules apply identically.
|
|
902
|
+
*/
|
|
903
|
+
function parse_extension_list(buf) {
|
|
756
904
|
let off = 0;
|
|
757
|
-
let n;
|
|
758
|
-
[n, off] = r_u16(buf, off);
|
|
759
|
-
|
|
760
|
-
let end = off + n;
|
|
761
905
|
let out = [];
|
|
906
|
+
let seen = {}; // RFC 8446 §4.2: "There MUST NOT be more than one extension of the same type"
|
|
762
907
|
|
|
763
|
-
while (off <
|
|
908
|
+
while (off < buf.length) {
|
|
764
909
|
let t;
|
|
765
910
|
[t, off] = r_u16(buf, off);
|
|
766
911
|
|
|
767
912
|
let l;
|
|
768
913
|
[l, off] = r_u16(buf, off);
|
|
914
|
+
if (off + l > buf.length) throw parseError('extension body overflows extension list');
|
|
769
915
|
|
|
770
916
|
let d;
|
|
771
917
|
[d, off] = r_bytes(buf, off, l);
|
|
772
918
|
|
|
919
|
+
if (seen[t] === true) {
|
|
920
|
+
// decode_error (50), NOT illegal_parameter (47).
|
|
921
|
+
//
|
|
922
|
+
// RFC 8446 §4.2 says "There MUST NOT be more than one extension of the
|
|
923
|
+
// same type ... Receivers MUST check for this and abort with an
|
|
924
|
+
// 'illegal_parameter' alert", so 47 is a defensible reading and this
|
|
925
|
+
// code used to send it. BoringSSL — and therefore BoGo, and therefore
|
|
926
|
+
// most of the ecosystem that tests against it — treats a repeated
|
|
927
|
+
// extension as a malformed extensions VECTOR rather than a bad parameter
|
|
928
|
+
// value, and sends decode_error. Interoperating with deployed peers wins
|
|
929
|
+
// over the letter of the sentence here.
|
|
930
|
+
//
|
|
931
|
+
// Left as an explicit note because the RFC text genuinely reads the other
|
|
932
|
+
// way: without this comment the next person to check §4.2 will "fix" it
|
|
933
|
+
// back and silently break the same seven tests again.
|
|
934
|
+
throw parseError('duplicate extension type ' + t, TLS_ALERT.DECODE_ERROR);
|
|
935
|
+
}
|
|
936
|
+
seen[t] = true;
|
|
937
|
+
|
|
773
938
|
let name = ext_name_by_code(t);
|
|
774
939
|
let dec = exts[name] && exts[name].decode;
|
|
775
940
|
let val = dec ? dec(d) : null;
|
|
@@ -780,6 +945,25 @@ function parse_extensions(buf) {
|
|
|
780
945
|
return out;
|
|
781
946
|
}
|
|
782
947
|
|
|
948
|
+
/**
|
|
949
|
+
* Parse a length-prefixed extensions vector that ENDS the enclosing structure.
|
|
950
|
+
* `buf` must START with the 2-byte vector length and contain nothing after it.
|
|
951
|
+
*/
|
|
952
|
+
function parse_extensions(buf) {
|
|
953
|
+
let off = 0;
|
|
954
|
+
let n;
|
|
955
|
+
[n, off] = r_u16(buf, off);
|
|
956
|
+
|
|
957
|
+
let end = off + n;
|
|
958
|
+
// The declared vector length must fit the buffer, and the buffer must not
|
|
959
|
+
// carry trailing bytes past the vector — either way it's a framing error
|
|
960
|
+
// in the peer's encoder (decode_error, RFC 8446 §6.2).
|
|
961
|
+
if (end > buf.length) throw parseError('extensions vector length exceeds body');
|
|
962
|
+
if (end < buf.length) throw parseError('trailing data after extensions vector');
|
|
963
|
+
|
|
964
|
+
return parse_extension_list(buf.subarray(off, end));
|
|
965
|
+
}
|
|
966
|
+
|
|
783
967
|
|
|
784
968
|
/* ================================ Hello I/O ================================ */
|
|
785
969
|
function build_hello(params) {
|
|
@@ -796,7 +980,15 @@ function build_hello(params) {
|
|
|
796
980
|
let extsBuf = build_extensions(params.extensions || []);
|
|
797
981
|
|
|
798
982
|
if (kind === 'client') {
|
|
799
|
-
|
|
983
|
+
// A codec encodes what it is handed; it must not invent policy. This
|
|
984
|
+
// used to fall back to a hardcoded suite list, which silently replaced the
|
|
985
|
+
// caller's configuration whenever the field name didn't line up — the
|
|
986
|
+
// exact failure the `cipher_suites` plural fix elsewhere was chasing. Fail
|
|
987
|
+
// loudly instead, so a wiring mistake surfaces at the call site.
|
|
988
|
+
let cs = params.cipher_suites;
|
|
989
|
+
if (!Array.isArray(cs) || cs.length === 0) {
|
|
990
|
+
throw new Error('build_hello: ClientHello requires a non-empty cipher_suites array');
|
|
991
|
+
}
|
|
800
992
|
|
|
801
993
|
const csBlock = new Uint8Array(2 + cs.length * 2);
|
|
802
994
|
let o = 0;
|
|
@@ -1037,7 +1229,10 @@ function parse_certificate(body) {
|
|
|
1037
1229
|
let extLen; [extLen, off] = r_u16(body, off); // extensions vec<2>
|
|
1038
1230
|
let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
|
|
1039
1231
|
|
|
1040
|
-
|
|
1232
|
+
// extRaw is the list body (prefix already consumed above), so use
|
|
1233
|
+
// the list parser — parse_extensions() would misread the first two
|
|
1234
|
+
// bytes of the first extension as a vector length.
|
|
1235
|
+
const extensions = parse_extension_list(extRaw);
|
|
1041
1236
|
entries.push({ cert, extensions });
|
|
1042
1237
|
}
|
|
1043
1238
|
|
|
@@ -1105,6 +1300,10 @@ function parse_certificate_verify(body) {
|
|
|
1105
1300
|
let sig;
|
|
1106
1301
|
[sig, off] = r_bytes(body, off, slen);
|
|
1107
1302
|
|
|
1303
|
+
// The signature vector ends the message; anything after it is framing junk.
|
|
1304
|
+
if (off !== body.length) throw parseError('trailing data after CertificateVerify signature');
|
|
1305
|
+
if (slen === 0) throw parseError('empty CertificateVerify signature');
|
|
1306
|
+
|
|
1108
1307
|
return { scheme: alg, signature: sig };
|
|
1109
1308
|
}
|
|
1110
1309
|
|
|
@@ -1165,6 +1364,12 @@ function parse_new_session_ticket(body) {
|
|
|
1165
1364
|
let ticket;
|
|
1166
1365
|
[ticket, off] = r_bytes(body, off, tlen);
|
|
1167
1366
|
|
|
1367
|
+
// RFC 8446 §4.6.1 declares ticket<1..2^16-1>: the vector's lower bound is 1,
|
|
1368
|
+
// so a zero-length ticket is a grammar violation, not an empty-but-valid
|
|
1369
|
+
// value. Enforced here in the codec because it is a property of the wire
|
|
1370
|
+
// format; what a session then DOES with a ticket is policy and lives above.
|
|
1371
|
+
if (tlen === 0) throw parseError('NewSessionTicket carries an empty ticket');
|
|
1372
|
+
|
|
1168
1373
|
let extBuf = body.subarray(off);
|
|
1169
1374
|
let extensions = extBuf.length ? parse_extensions(extBuf) : [];
|
|
1170
1375
|
|
|
@@ -1197,9 +1402,44 @@ function build_certificate_request(params) {
|
|
|
1197
1402
|
|
|
1198
1403
|
if (v === TLS_VERSION.TLS1_3) {
|
|
1199
1404
|
let ctx = toU8((params && params.request_context) || new Uint8Array(0));
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1405
|
+
|
|
1406
|
+
// RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be specified"
|
|
1407
|
+
// in a TLS 1.3 CertificateRequest.
|
|
1408
|
+
//
|
|
1409
|
+
// Extension assembly belongs here, next to build_hello's, so callers pass
|
|
1410
|
+
// SEMANTICS (which algorithms) rather than pre-encoded extension records.
|
|
1411
|
+
// Previously only a pre-built `extensions` array was honoured while the
|
|
1412
|
+
// session passed `signature_algorithms` as a flat field — silently
|
|
1413
|
+
// dropped, producing a CertificateRequest with an empty extensions vector
|
|
1414
|
+
// that strict peers reject outright ("missing sigalgs extension").
|
|
1415
|
+
let extsBuf;
|
|
1416
|
+
if (params && params.extensions && !Array.isArray(params.extensions)) {
|
|
1417
|
+
// Pre-encoded extensions vector supplied verbatim.
|
|
1418
|
+
extsBuf = params.extensions;
|
|
1419
|
+
} else {
|
|
1420
|
+
let extList = Array.isArray(params && params.extensions)
|
|
1421
|
+
? params.extensions.slice()
|
|
1422
|
+
: [];
|
|
1423
|
+
|
|
1424
|
+
let hasSigAlgs = false;
|
|
1425
|
+
for (let i = 0; i < extList.length; i++) {
|
|
1426
|
+
let t = extList[i] && extList[i].type;
|
|
1427
|
+
if (t === 'SIGNATURE_ALGORITHMS' || t === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (!hasSigAlgs) {
|
|
1431
|
+
let sa = (params && params.signature_algorithms) || [];
|
|
1432
|
+
if (sa.length === 0) {
|
|
1433
|
+
throw new Error('build_certificate_request: TLS 1.3 requires a non-empty signature_algorithms list');
|
|
1434
|
+
}
|
|
1435
|
+
extList.unshift({ type: 'SIGNATURE_ALGORITHMS', value: sa });
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// certificate_authorities is OPTIONAL and its DistinguishedName list is
|
|
1439
|
+
// non-empty by grammar — an empty list means "no constraint", which is
|
|
1440
|
+
// expressed by omitting the extension, not by sending an empty one.
|
|
1441
|
+
extsBuf = build_extensions(extList);
|
|
1442
|
+
}
|
|
1203
1443
|
|
|
1204
1444
|
let ctxVec = veclen(1, ctx);
|
|
1205
1445
|
return concatUint8Arrays([ctxVec, extsBuf]);
|
|
@@ -1240,10 +1480,27 @@ function parse_certificate_request(body) {
|
|
|
1240
1480
|
if (1 + ctxLen + 2 + extLen === body.length) {
|
|
1241
1481
|
let ctx = body.subarray(1, 1 + ctxLen);
|
|
1242
1482
|
let extBuf = body.subarray(1 + ctxLen + 2);
|
|
1483
|
+
let crExts = parse_extension_list(extBuf);
|
|
1484
|
+
|
|
1485
|
+
// RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be
|
|
1486
|
+
// specified". Mirrors the same rule already enforced when BUILDING a
|
|
1487
|
+
// CertificateRequest — encoder and decoder agree on what the message
|
|
1488
|
+
// must contain, so we can neither emit nor accept one without it.
|
|
1489
|
+
let hasSigAlgs = false;
|
|
1490
|
+
for (let ci = 0; ci < crExts.length; ci++) {
|
|
1491
|
+
if (crExts[ci] && crExts[ci].type === TLS_EXT.SIGNATURE_ALGORITHMS) { hasSigAlgs = true; break; }
|
|
1492
|
+
}
|
|
1493
|
+
if (!hasSigAlgs) {
|
|
1494
|
+
throw parseError('TLS 1.3 CertificateRequest without the mandatory signature_algorithms extension');
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1243
1497
|
return {
|
|
1244
1498
|
version: TLS_VERSION.TLS1_3,
|
|
1245
1499
|
request_context: ctx,
|
|
1246
|
-
|
|
1500
|
+
// extBuf skips the 2-byte prefix (validated as extLen above), so it
|
|
1501
|
+
// is a bare list — this was reading the prefix twice and breaking
|
|
1502
|
+
// every CertificateRequest.
|
|
1503
|
+
extensions: crExts
|
|
1247
1504
|
};
|
|
1248
1505
|
}
|
|
1249
1506
|
}
|
|
@@ -1633,6 +1890,7 @@ export {
|
|
|
1633
1890
|
exts,
|
|
1634
1891
|
build_extensions,
|
|
1635
1892
|
parse_extensions,
|
|
1893
|
+
parse_extension_list,
|
|
1636
1894
|
|
|
1637
1895
|
build_message,
|
|
1638
1896
|
parse_message,
|