lemon-tls 0.2.0 → 0.2.2

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.
@@ -28,7 +28,7 @@ import * as wire from './wire.js';
28
28
  // Extracted modules
29
29
  import { pick_scheme, sign_with_scheme } from './session/signing.js';
30
30
  import createSecureContext from './secure_context.js';
31
- import { x25519_get_public_key, x25519_get_shared_secret, p256_generate_keypair, p256_get_shared_secret } from './session/ecdh.js';
31
+ import { x25519_get_public_key, x25519_get_shared_secret, p256_generate_keypair, p256_get_shared_secret, p384_generate_keypair, p384_get_shared_secret } from './session/ecdh.js';
32
32
  import { build_tls_message, parse_tls_message } from './session/message.js';
33
33
 
34
34
 
@@ -57,7 +57,7 @@ function TLSSession(options){
57
57
 
58
58
  //local stuff...
59
59
  local_sni: options.servername || null,
60
- local_session_id: null,
60
+ local_session_id: 'sessionId' in options ? options.sessionId : null,
61
61
 
62
62
  local_random: null,
63
63
  local_extensions: [],
@@ -108,6 +108,7 @@ function TLSSession(options){
108
108
 
109
109
 
110
110
  transcript: [],
111
+ transcriptHook: null, // DTLSSession sets this to transform transcript entries
111
112
 
112
113
 
113
114
  //both
@@ -162,6 +163,9 @@ function TLSSession(options){
162
163
  // HelloRetryRequest
163
164
  helloRetried: false, // true if HRR was sent/received
164
165
 
166
+ // DTLS cookie (set by DTLSSession via set_context)
167
+ dtls_cookie: undefined, // Uint8Array or undefined
168
+
165
169
  // TLS 1.3 resumption
166
170
  tls13_master_secret: null,
167
171
  resumption_master_secret: null,
@@ -173,6 +177,19 @@ function TLSSession(options){
173
177
  isResumed: false, // true if PSK was accepted
174
178
  };
175
179
 
180
+ /**
181
+ * Push a handshake message to the transcript.
182
+ * If a transcriptHook is set (by DTLSSession), it transforms the data first.
183
+ * This allows DTLS 1.2 to store DTLS-format entries (with reconstruction data)
184
+ * while TLS and DTLS 1.3 store standard TLS-format entries.
185
+ */
186
+ function pushTranscript(data) {
187
+ if (context.transcriptHook) {
188
+ data = context.transcriptHook(data);
189
+ }
190
+ context.transcript.push(data);
191
+ }
192
+
176
193
  function process_income_message(data){
177
194
 
178
195
  // Track handshake start time
@@ -192,7 +209,7 @@ function TLSSession(options){
192
209
 
193
210
  if((context.isServer==false && message.type=='server_hello') || (context.isServer==true && message.type=='client_hello')){
194
211
 
195
- context.transcript.push(data);
212
+ pushTranscript(data);
196
213
 
197
214
  // Save raw ClientHello + emit event (server side)
198
215
  if (context.isServer && message.type === 'client_hello') {
@@ -267,19 +284,26 @@ function TLSSession(options){
267
284
  if (!hrrCipher) hrrCipher = 0x1301;
268
285
  let hashName = TLS_CIPHER_SUITES[hrrCipher] ? TLS_CIPHER_SUITES[hrrCipher].hash : 'sha256';
269
286
 
270
- // Replace transcript: CH1 → message_hash
287
+ // Replace transcript: CH1 → message_hash (RFC 8446 §4.4.1)
288
+ // BUG FIX: The HRR was already pushed to transcript at the top of this block (line 195).
289
+ // We must remove it before hashing, since message_hash = Hash(ClientHello1) only.
290
+ let hrrData = context.transcript.pop(); // remove HRR
271
291
  let ch1_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
272
292
  let message_hash = wire.build_message(wire.TLS_MESSAGE_TYPE.MESSAGE_HASH, ch1_hash);
273
- context.transcript = [message_hash, data]; // message_hash + HRR
293
+ context.transcript = [message_hash, hrrData]; // message_hash + HRR
274
294
 
275
- // Find the requested group from HRR extensions
295
+ // Find the requested group from HRR key_share extension
296
+ // After wire.js fix, key_groups contains [{group: N, key_exchange: empty}] for HRR
276
297
  let requestedGroup = null;
277
- if (message.supported_groups && message.supported_groups.length > 0) {
278
- requestedGroup = message.supported_groups[0];
279
- } else if (message.key_groups && message.key_groups.length > 0) {
298
+ if (message.key_groups && message.key_groups.length > 0) {
280
299
  requestedGroup = message.key_groups[0].group;
300
+ } else if (message.supported_groups && message.supported_groups.length > 0) {
301
+ requestedGroup = message.supported_groups[0];
281
302
  }
282
303
 
304
+ // Extract cookie from HRR (if present, must be echoed in CH2)
305
+ let hrrCookie = message.cookie || null;
306
+
283
307
  if (requestedGroup) {
284
308
  // Generate key for the requested group
285
309
  let newKeyGroup = null;
@@ -292,31 +316,63 @@ function TLSSession(options){
292
316
  let kp = p256_generate_keypair();
293
317
  newKeyGroup = { group: requestedGroup, public_key: kp.public_key, private_key: kp.private_key };
294
318
  context.local_key_groups[requestedGroup] = { public_key: kp.public_key, private_key: kp.private_key };
319
+ } else if (requestedGroup === 0x0018) {
320
+ let kp = p384_generate_keypair();
321
+ newKeyGroup = { group: requestedGroup, public_key: kp.public_key, private_key: kp.private_key };
322
+ context.local_key_groups[requestedGroup] = { public_key: kp.public_key, private_key: kp.private_key };
295
323
  }
296
324
 
297
325
  if (newKeyGroup) {
298
- // Build and send new ClientHello (CH2) with key_share for requested group
326
+ // Build and send new ClientHello (CH2) with:
327
+ // - key_share for requested group
328
+ // - cookie (if HRR included one)
329
+ // - ALPN (same as CH1)
330
+ // - custom extensions (QUIC transport params etc.)
331
+ // - same cipher_suites, session_id, random as CH1
299
332
  let extensions = [
300
333
  { type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions },
301
334
  { type: 'SUPPORTED_GROUPS', value: context.local_supported_groups },
302
335
  { type: 'KEY_SHARE', value: [{ group: requestedGroup, key_exchange: newKeyGroup.public_key }] },
303
- { type: 'SIGNATURE_ALGORITHMS', value: context.local_supported_signature_algorithms.length > 0 ?
304
- context.local_supported_signature_algorithms : [0x0804, 0x0805, 0x0806, 0x0403, 0x0503, 0x0603, 0x0401, 0x0501, 0x0601] },
336
+ { type: 'SIGNATURE_ALGORITHMS', value: [
337
+ // Must match CH1 exactly (RFC 8446 §4.1.2)
338
+ 0x0804, 0x0805, 0x0806,
339
+ 0x0403, 0x0503, 0x0603,
340
+ 0x0807, 0x0808,
341
+ 0x0401, 0x0501, 0x0601
342
+ ] },
305
343
  { type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
306
- { type: 23, data: new Uint8Array(0) },
344
+ { type: 23, data: new Uint8Array(0) }, // extended_master_secret
307
345
  ];
346
+
347
+ // SNI (must be first)
308
348
  if (context.local_sni) extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
309
349
 
350
+ // ALPN (same as CH1 — required for QUIC/h3)
351
+ if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
352
+ extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
353
+ }
354
+
355
+ // Cookie from HRR (RFC 8446 §4.2.2 — MUST echo if present)
356
+ if (hrrCookie) {
357
+ extensions.push({ type: 'COOKIE', value: hrrCookie });
358
+ }
359
+
360
+ // Custom extensions (e.g. QUIC transport params 0x39)
361
+ for (let ci in context.local_extensions) {
362
+ extensions.push(context.local_extensions[ci]);
363
+ }
364
+
310
365
  let ch2 = build_tls_message({
311
366
  type: 'client_hello',
312
367
  version: 0x0303,
313
368
  random: context.local_random,
314
369
  session_id: context.local_session_id,
370
+ cookie: context.dtls_cookie,
315
371
  cipher_suite: context.local_supported_cipher_suites,
316
372
  extensions: extensions,
317
373
  });
318
374
 
319
- context.transcript.push(ch2);
375
+ pushTranscript(ch2);
320
376
  ev.emit('message', 0, context.message_sent_seq, 'hello', ch2);
321
377
  context.message_sent_seq++;
322
378
  }
@@ -330,7 +386,9 @@ function TLSSession(options){
330
386
  remote_random: message.random || null,
331
387
  remote_sni: message.sni || null,
332
388
  remote_session_id: message.session_id || null,
333
- remote_supported_versions: message.supported_versions || [],
389
+ remote_supported_versions: (message.supported_versions && message.supported_versions.length > 0)
390
+ ? message.supported_versions
391
+ : (message.legacy_version ? [message.legacy_version] : []),
334
392
  remote_supported_alpns: message.alpn || [],
335
393
  remote_supported_cipher_suites: message.cipher_suites || [],
336
394
  remote_supported_signature_algorithms: message.signature_algorithms || [],
@@ -358,21 +416,27 @@ function TLSSession(options){
358
416
 
359
417
  }else if(message.type=='client_key_exchange' || message.type=='server_key_exchange'){
360
418
 
361
- context.transcript.push(data);
419
+ pushTranscript(data);
362
420
 
363
421
  if ([0xC02F,0xC02B,0xC030,0xC02C,0xC013,0xC014,0xC009,0xC00A].includes(context.selected_cipher_suite)==true) {//ECDHE
364
422
 
365
423
  // ServerKeyExchange carries the group; ClientKeyExchange does not (server already chose it)
366
424
  let kex_group = message.group || context.selected_group;
367
425
 
368
- set_context({
426
+ let kex_updates = {
369
427
  add_remote_key_groups: [
370
428
  {
371
429
  group: kex_group,
372
430
  public_key: message.public_key
373
431
  }
374
432
  ],
375
- });
433
+ };
434
+ // TLS 1.2 client: selected_group isn't set from ServerHello (no supported_groups ext).
435
+ // Set it from the SKE group so the reactive loop can generate a keypair and build CKE.
436
+ if (context.selected_group === null && kex_group) {
437
+ kex_updates.selected_group = kex_group;
438
+ }
439
+ set_context(kex_updates);
376
440
 
377
441
  }else if ([0x009E,0x009F,0x0033,0x0039,0x0067,0x006B].includes(context.selected_cipher_suite)==true) {//DHE
378
442
 
@@ -388,7 +452,7 @@ function TLSSession(options){
388
452
 
389
453
  }else if(message.type=='server_hello_done'){
390
454
 
391
- context.transcript.push(data);
455
+ pushTranscript(data);
392
456
 
393
457
 
394
458
  set_context({
@@ -397,7 +461,7 @@ function TLSSession(options){
397
461
 
398
462
  }else if(message.type=='encrypted_extensions'){
399
463
 
400
- context.transcript.push(data);
464
+ pushTranscript(data);
401
465
 
402
466
  set_context({
403
467
  remote_supported_groups: message.supported_groups || [],
@@ -405,7 +469,7 @@ function TLSSession(options){
405
469
 
406
470
  }else if(message.type=='certificate'){
407
471
 
408
- context.transcript.push(data);
472
+ pushTranscript(data);
409
473
 
410
474
  set_context({
411
475
  remote_cert_chain: message.entries,
@@ -420,7 +484,7 @@ function TLSSession(options){
420
484
 
421
485
  }else if(message.type=='certificate_verify'){
422
486
 
423
- context.transcript.push(data);
487
+ pushTranscript(data);
424
488
 
425
489
  }else if(message.type=='finished'){
426
490
 
@@ -449,7 +513,7 @@ function TLSSession(options){
449
513
  }else if(message.type=='key_update'){
450
514
 
451
515
  // Peer is updating their traffic secret (we update our read key)
452
- if(context.state==='connected' && context.selected_version === wire.TLS_VERSION.TLS1_3){
516
+ if(context.state==='connected' && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
453
517
  let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
454
518
  let hashLen = getHashLen(hashName);
455
519
  let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
@@ -474,7 +538,7 @@ function TLSSession(options){
474
538
 
475
539
  // Server is requesting a client certificate (TLS 1.3)
476
540
  if(!context.isServer){
477
- context.transcript.push(data);
541
+ pushTranscript(data);
478
542
  context.certificateRequested = true;
479
543
  context.certificateRequestContext = message.certificate_request_context || new Uint8Array(0);
480
544
  context.certificateRequestSigAlgs = message.signature_algorithms || [];
@@ -780,6 +844,9 @@ function TLSSession(options){
780
844
  if(context.remote_app_traffic_secret==null && options.remote_app_traffic_secret!==null){
781
845
  context.remote_app_traffic_secret=options.remote_app_traffic_secret;
782
846
  has_changed=true;
847
+ if(context.local_app_traffic_secret!==null){
848
+ ev.emit('appSecrets', context.local_app_traffic_secret, context.remote_app_traffic_secret);
849
+ }
783
850
  }
784
851
  }
785
852
 
@@ -787,6 +854,9 @@ function TLSSession(options){
787
854
  if(context.local_app_traffic_secret==null && options.local_app_traffic_secret!==null){
788
855
  context.local_app_traffic_secret=options.local_app_traffic_secret;
789
856
  has_changed=true;
857
+ if(context.remote_app_traffic_secret!==null){
858
+ ev.emit('appSecrets', context.local_app_traffic_secret, context.remote_app_traffic_secret);
859
+ }
790
860
  }
791
861
  }
792
862
 
@@ -827,7 +897,10 @@ function TLSSession(options){
827
897
  }
828
898
  }
829
899
 
830
-
900
+ if('dtls_cookie' in options){
901
+ context.dtls_cookie=options.dtls_cookie;
902
+ has_changed=true;
903
+ }
831
904
 
832
905
 
833
906
  }
@@ -857,6 +930,16 @@ function TLSSession(options){
857
930
 
858
931
  if('selected_version' in params_to_set==false || params_to_set.selected_version==null){
859
932
  }
933
+
934
+ // TLS 1.2: clear key_share groups from ClientHello.
935
+ // key_share is a TLS 1.3 extension; in TLS 1.2, keys come from CKE/SKE.
936
+ // Without this, the server would compute the shared secret too early
937
+ // (using CH key_share instead of waiting for CKE).
938
+ if (context.isServer && params_to_set.selected_version !== null &&
939
+ params_to_set.selected_version !== wire.TLS_VERSION.TLS1_3 &&
940
+ params_to_set.selected_version !== wire.DTLS_VERSION.DTLS1_3) {
941
+ context.remote_key_groups = {};
942
+ }
860
943
  }
861
944
 
862
945
  //select selected_cipher...
@@ -948,6 +1031,20 @@ function TLSSession(options){
948
1031
  }
949
1032
  ];
950
1033
 
1034
+ } else if (context.selected_group === 0x0018) {
1035
+
1036
+ let kp = p384_generate_keypair();
1037
+ let private_key = kp.private_key;
1038
+ let public_key = kp.public_key;
1039
+
1040
+ params_to_set['add_local_key_groups']=[
1041
+ {
1042
+ group: context.selected_group,
1043
+ private_key: private_key,
1044
+ public_key: public_key
1045
+ }
1046
+ ];
1047
+
951
1048
  }
952
1049
 
953
1050
 
@@ -974,6 +1071,12 @@ function TLSSession(options){
974
1071
 
975
1072
  params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
976
1073
 
1074
+ } else if (context.selected_group === 0x0018) { // secp384r1 (P-384)
1075
+
1076
+ let ecdhe_shared_secret = p384_get_shared_secret(local_private_key, remote_public_key);
1077
+
1078
+ params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
1079
+
977
1080
  }
978
1081
 
979
1082
  }
@@ -986,7 +1089,7 @@ function TLSSession(options){
986
1089
  if(context.isServer==true){
987
1090
 
988
1091
  // HelloRetryRequest: if we selected a group but client didn't send a key_share for it
989
- if(context.hello_sent==false && !context.helloRetried && context.selected_version === wire.TLS_VERSION.TLS1_3 &&
1092
+ if(context.hello_sent==false && !context.helloRetried && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
990
1093
  context.selected_group !== null && context.selected_cipher_suite !== null &&
991
1094
  !(context.selected_group in context.remote_key_groups)){
992
1095
 
@@ -1001,11 +1104,12 @@ function TLSSession(options){
1001
1104
  // Build and send HRR (it's a ServerHello with magic random)
1002
1105
  let hrr_body = wire.build_hello_retry_request({
1003
1106
  cipher_suite: context.selected_cipher_suite,
1004
- selected_version: wire.TLS_VERSION.TLS1_3,
1107
+ selected_version: context.selected_version,
1005
1108
  selected_group: context.selected_group,
1109
+ session_id: context.remote_session_id,
1006
1110
  });
1007
1111
  let hrr_data = wire.build_message(wire.TLS_MESSAGE_TYPE.SERVER_HELLO, hrr_body);
1008
- context.transcript.push(hrr_data);
1112
+ pushTranscript(hrr_data);
1009
1113
 
1010
1114
  ev.emit('message', 0, context.message_sent_seq, 'hello_retry_request', hrr_data);
1011
1115
  context.message_sent_seq++;
@@ -1025,11 +1129,14 @@ function TLSSession(options){
1025
1129
  if(context.hello_sent==false){
1026
1130
 
1027
1131
  if(context.selected_version!==null && context.selected_cipher_suite!==null && context.selected_session_id!==null){
1028
- if(context.selected_version === wire.TLS_VERSION.TLS1_3){
1132
+ if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1029
1133
  if(context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
1030
- can_send_hello=true;
1134
+ // After HRR, don't send ServerHello until CH2 provides the requested key_share
1135
+ if (!context.helloRetried || (context.selected_group in context.remote_key_groups)) {
1136
+ can_send_hello=true;
1137
+ }
1031
1138
  }
1032
- }else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
1139
+ }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
1033
1140
  can_send_hello=true;
1034
1141
  }
1035
1142
  }
@@ -1043,12 +1150,12 @@ function TLSSession(options){
1043
1150
 
1044
1151
  let build_message_params=null;
1045
1152
 
1046
- if(context.selected_version==wire.TLS_VERSION.TLS1_3){
1153
+ if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1047
1154
 
1048
1155
  let shExtensions = [
1049
1156
  {
1050
1157
  type: 'SUPPORTED_VERSIONS',
1051
- value: wire.TLS_VERSION.TLS1_3
1158
+ value: context.selected_version
1052
1159
  },
1053
1160
  {
1054
1161
  type: 'KEY_SHARE',
@@ -1074,7 +1181,7 @@ function TLSSession(options){
1074
1181
  };
1075
1182
 
1076
1183
 
1077
- }else if(context.selected_version==wire.TLS_VERSION.TLS1_2){
1184
+ }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
1078
1185
 
1079
1186
 
1080
1187
  // TLS 1.2 ServerHello: no SUPPORTED_VERSIONS or KEY_SHARE.
@@ -1116,7 +1223,7 @@ function TLSSession(options){
1116
1223
 
1117
1224
  let message_data = build_tls_message(build_message_params);
1118
1225
 
1119
- context.transcript.push(message_data);
1226
+ pushTranscript(message_data);
1120
1227
 
1121
1228
  context.hello_sent=true;
1122
1229
 
@@ -1136,7 +1243,7 @@ function TLSSession(options){
1136
1243
 
1137
1244
  //get base_secret
1138
1245
  if (context.base_secret==null && context.selected_cipher_suite !== null){
1139
- if(context.selected_version == wire.TLS_VERSION.TLS1_3 && (context.ecdhe_shared_secret !== null)){
1246
+ if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && (context.ecdhe_shared_secret !== null)){
1140
1247
 
1141
1248
  let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
1142
1249
  let result;
@@ -1158,7 +1265,7 @@ function TLSSession(options){
1158
1265
  params_to_set['remote_handshake_traffic_secret']=result.server_handshake_traffic_secret;
1159
1266
  }
1160
1267
 
1161
- }else if(context.selected_version === wire.TLS_VERSION.TLS1_2 && context.local_random!==null && context.remote_random!==null){
1268
+ }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.local_random!==null && context.remote_random!==null){
1162
1269
  if(context.ecdhe_shared_secret !== null){
1163
1270
 
1164
1271
 
@@ -1178,7 +1285,11 @@ function TLSSession(options){
1178
1285
  if(context.isServer || context.key_exchange_sent){
1179
1286
  let hashFn = getHashFn(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
1180
1287
 
1181
- let transcript_hash = hashFn(concatUint8Arrays(context.transcript));
1288
+ // Use snapshot up to CKE if available (excludes CertificateVerify)
1289
+ let emsTranscript = context._emsTranscriptLen
1290
+ ? context.transcript.slice(0, context._emsTranscriptLen)
1291
+ : context.transcript;
1292
+ let transcript_hash = hashFn(concatUint8Arrays(emsTranscript));
1182
1293
 
1183
1294
  let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
1184
1295
 
@@ -1202,7 +1313,7 @@ function TLSSession(options){
1202
1313
 
1203
1314
 
1204
1315
  //send encrypted_extensions...
1205
- if (context.isServer==true && context.selected_version === wire.TLS_VERSION.TLS1_3){
1316
+ if (context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1206
1317
  if(context.encrypted_exts_sent==false && context.hello_sent==true && context.local_handshake_traffic_secret!==null){
1207
1318
 
1208
1319
  let extensions=[];
@@ -1220,7 +1331,7 @@ function TLSSession(options){
1220
1331
  extensions: extensions
1221
1332
  });
1222
1333
 
1223
- context.transcript.push(message_data);
1334
+ pushTranscript(message_data);
1224
1335
 
1225
1336
  context.encrypted_exts_sent=true;
1226
1337
 
@@ -1234,31 +1345,31 @@ function TLSSession(options){
1234
1345
 
1235
1346
  //send certificate... (skip for PSK resumption — no cert needed)
1236
1347
  // But first: send CertificateRequest if requestCert is set (TLS 1.3 only, between EE and Cert)
1237
- if(context.isServer==true && context.requestCert==true && !context.certificateRequestSent && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null && context.selected_version === wire.TLS_VERSION.TLS1_3 && !context.psk_accepted){
1348
+ if(context.isServer==true && context.requestCert==true && !context.certificateRequestSent && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && !context.psk_accepted){
1238
1349
  let cr_data = build_tls_message({
1239
1350
  type: 'certificate_request',
1240
1351
  certificate_request_context: new Uint8Array(0),
1241
1352
  signature_algorithms: context.local_supported_signature_algorithms,
1242
1353
  });
1243
- context.transcript.push(cr_data);
1354
+ pushTranscript(cr_data);
1244
1355
  context.certificateRequestSent = true;
1245
1356
  ev.emit('message', 1, context.message_sent_seq, 'certificate_request', cr_data);
1246
1357
  context.message_sent_seq++;
1247
1358
  }
1248
1359
 
1249
1360
  if(context.isServer==true && context.cert_sent==false && context.local_cert_chain!==null && !context.psk_accepted){
1250
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null) || (context.selected_version === wire.TLS_VERSION.TLS1_2 && context.hello_sent==true)){
1361
+ if(((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null) || ((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.hello_sent==true)){
1251
1362
 
1252
1363
  let message_data = build_tls_message({
1253
1364
  type: 'certificate',
1254
1365
  version: context.selected_version,
1255
1366
  entries: context.local_cert_chain
1256
1367
  });
1257
- context.transcript.push(message_data);
1368
+ pushTranscript(message_data);
1258
1369
 
1259
1370
  context.cert_sent=true;
1260
1371
 
1261
- if (context.selected_version === wire.TLS_VERSION.TLS1_3){
1372
+ if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1262
1373
  ev.emit('message',1,context.message_sent_seq,'certificate',message_data);
1263
1374
  }else{
1264
1375
  ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
@@ -1276,7 +1387,7 @@ function TLSSession(options){
1276
1387
 
1277
1388
 
1278
1389
  //send certificate verify...
1279
- if (context.isServer==true && context.selected_version === wire.TLS_VERSION.TLS1_3){
1390
+ if (context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1280
1391
  if(context.cert_sent==true && context.cert_verify_sent==false && context.local_cert_chain!==null && context.local_handshake_traffic_secret!==null && context.selected_cipher_suite!==null){
1281
1392
 
1282
1393
  let tbs_data = build_cert_verify_tbs(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,true,concatUint8Arrays(context.transcript));
@@ -1386,7 +1497,7 @@ function TLSSession(options){
1386
1497
 
1387
1498
 
1388
1499
 
1389
- context.transcript.push(message_data);
1500
+ pushTranscript(message_data);
1390
1501
 
1391
1502
  context.cert_verify_sent=true;
1392
1503
 
@@ -1409,26 +1520,90 @@ function TLSSession(options){
1409
1520
 
1410
1521
 
1411
1522
  // client/server key exchange - 1.2 only...
1412
- if (context.key_exchange_sent == false && context.selected_version == wire.TLS_VERSION.TLS1_2) {
1523
+ if (context.key_exchange_sent == false && (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)) {
1413
1524
  if(context.selected_group!==null && context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
1414
1525
 
1415
1526
  if (context.isServer==false && context.remote_hello_done==true) {
1416
1527
 
1528
+ // TLS 1.2: send Certificate before CKE if server requested client auth
1529
+ if (context.certificateRequested && !context.clientCertSent) {
1530
+ context.clientCertSent = true;
1531
+
1532
+ // Build TLS 1.2 Certificate message
1533
+ let certEntries = [];
1534
+ if (context.local_cert_chain && context.local_cert_chain.length > 0) {
1535
+ certEntries = context.local_cert_chain;
1536
+ }
1537
+ // TLS 1.2 Certificate: certificate_list<0..2^24-1>
1538
+ // Each entry: cert_length<3> + cert_der
1539
+ let totalLen = 0;
1540
+ for (let ci = 0; ci < certEntries.length; ci++) {
1541
+ totalLen += 3 + certEntries[ci].cert.length;
1542
+ }
1543
+ let certBody = new Uint8Array(3 + totalLen);
1544
+ certBody[0] = (totalLen >> 16) & 0xff;
1545
+ certBody[1] = (totalLen >> 8) & 0xff;
1546
+ certBody[2] = totalLen & 0xff;
1547
+ let off = 3;
1548
+ for (let ci = 0; ci < certEntries.length; ci++) {
1549
+ let der = certEntries[ci].cert;
1550
+ certBody[off] = (der.length >> 16) & 0xff;
1551
+ certBody[off+1] = (der.length >> 8) & 0xff;
1552
+ certBody[off+2] = der.length & 0xff;
1553
+ certBody.set(der, off + 3);
1554
+ off += 3 + der.length;
1555
+ }
1556
+ let cert_data = wire.build_message(wire.TLS_MESSAGE_TYPE.CERTIFICATE, certBody);
1557
+ pushTranscript(cert_data);
1558
+ ev.emit('message', 0, context.message_sent_seq, 'certificate', cert_data);
1559
+ context.message_sent_seq++;
1560
+ }
1561
+
1417
1562
  let public_key = context.local_key_groups[context.selected_group].public_key;
1418
1563
 
1419
1564
  let message_data = build_tls_message({
1420
1565
  type: 'client_key_exchange',
1421
1566
  public_key: public_key,
1422
1567
  });
1423
- context.transcript.push(message_data);
1568
+ pushTranscript(message_data);
1424
1569
 
1425
1570
  // Set via params_to_set to trigger re-evaluation (EMS needs this)
1426
1571
  params_to_set['key_exchange_sent'] = true;
1572
+
1573
+ // Save transcript length for EMS: session_hash includes up to CKE only (RFC 7627)
1574
+ context._emsTranscriptLen = context.transcript.length;
1427
1575
 
1428
1576
  ev.emit('message', 0, context.message_sent_seq, 'client_key_exchange', message_data);
1429
1577
 
1430
1578
  context.message_sent_seq++;
1431
1579
 
1580
+ // TLS 1.2 CertificateVerify: if we sent a non-empty Certificate, prove we own the private key
1581
+ if (context.certificateRequested && context.cert_private_key && context.local_cert_chain && context.local_cert_chain.length > 0) {
1582
+ // sign_with_scheme hashes internally, so pass RAW transcript (not pre-hashed)
1583
+ let transcript_data = concatUint8Arrays(context.transcript);
1584
+
1585
+ // Pick scheme matching our cert + server's requested algorithms
1586
+ let cert_key_obj = crypto.createPrivateKey({ key: Buffer.from(context.cert_private_key), format: 'der', type: 'pkcs8' });
1587
+ let reqAlgs = context.certificateRequestSigAlgs.length > 0
1588
+ ? context.certificateRequestSigAlgs
1589
+ : context.local_supported_signature_algorithms;
1590
+ let scheme = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_key_obj, reqAlgs);
1591
+ let signature = sign_with_scheme(wire.TLS_VERSION.TLS1_2, scheme, transcript_data, cert_key_obj);
1592
+
1593
+ // Build CertificateVerify: scheme(2) + sig_length(2) + sig
1594
+ let cvBody = new Uint8Array(2 + 2 + signature.length);
1595
+ cvBody[0] = (scheme >> 8) & 0xff;
1596
+ cvBody[1] = scheme & 0xff;
1597
+ cvBody[2] = (signature.length >> 8) & 0xff;
1598
+ cvBody[3] = signature.length & 0xff;
1599
+ cvBody.set(signature, 4);
1600
+
1601
+ let cv_data = wire.build_message(wire.TLS_MESSAGE_TYPE.CERTIFICATE_VERIFY, cvBody);
1602
+ pushTranscript(cv_data);
1603
+ ev.emit('message', 0, context.message_sent_seq, 'certificate_verify', cv_data);
1604
+ context.message_sent_seq++;
1605
+ }
1606
+
1432
1607
 
1433
1608
  }else if (context.isServer==true && context.cert_sent == true) {
1434
1609
 
@@ -1460,7 +1635,7 @@ function TLSSession(options){
1460
1635
  sig_alg: scheme12,
1461
1636
  signature: sig_data
1462
1637
  });
1463
- context.transcript.push(message_data);
1638
+ pushTranscript(message_data);
1464
1639
 
1465
1640
  context.key_exchange_sent = true;
1466
1641
 
@@ -1473,16 +1648,16 @@ function TLSSession(options){
1473
1648
  }
1474
1649
 
1475
1650
  //server hello done - 1.2 only...
1476
- if(context.isServer==true && context.selected_version == wire.TLS_VERSION.TLS1_2){
1651
+ if(context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
1477
1652
  if(context.hello_done_sent==false && context.key_exchange_sent==true){
1478
1653
 
1479
1654
  let message_data = build_tls_message({
1480
1655
  type: 'server_hello_done'});
1481
- context.transcript.push(message_data);
1656
+ pushTranscript(message_data);
1482
1657
 
1483
1658
  context.hello_done_sent=true;
1484
1659
 
1485
- ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
1660
+ ev.emit('message',0,context.message_sent_seq,'server_hello_done',message_data);
1486
1661
 
1487
1662
  context.message_sent_seq++;
1488
1663
 
@@ -1506,7 +1681,7 @@ function TLSSession(options){
1506
1681
  entries: certCtx.certificateChain,
1507
1682
  certificate_request_context: context.certificateRequestContext || new Uint8Array(0),
1508
1683
  });
1509
- context.transcript.push(cert_data);
1684
+ pushTranscript(cert_data);
1510
1685
  ev.emit('message', 1, context.message_sent_seq, 'certificate', cert_data);
1511
1686
  context.message_sent_seq++;
1512
1687
 
@@ -1520,7 +1695,7 @@ function TLSSession(options){
1520
1695
  scheme: scheme,
1521
1696
  signature: signature,
1522
1697
  });
1523
- context.transcript.push(cv_data);
1698
+ pushTranscript(cv_data);
1524
1699
  ev.emit('message', 1, context.message_sent_seq, 'certificate_verify', cv_data);
1525
1700
  context.message_sent_seq++;
1526
1701
  } else {
@@ -1531,7 +1706,7 @@ function TLSSession(options){
1531
1706
  entries: [],
1532
1707
  certificate_request_context: context.certificateRequestContext || new Uint8Array(0),
1533
1708
  });
1534
- context.transcript.push(cert_data);
1709
+ pushTranscript(cert_data);
1535
1710
  ev.emit('message', 1, context.message_sent_seq, 'certificate', cert_data);
1536
1711
  context.message_sent_seq++;
1537
1712
  }
@@ -1541,7 +1716,7 @@ function TLSSession(options){
1541
1716
  // base_secret may be null after app secrets are derived, so we also check handshake secret.
1542
1717
  if (context.finished_sent==false && context.selected_cipher_suite!==null && (context.base_secret!==null || context.local_handshake_traffic_secret!==null)){
1543
1718
 
1544
- if(context.selected_version === wire.TLS_VERSION.TLS1_3 && context.local_handshake_traffic_secret!==null){
1719
+ if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.local_handshake_traffic_secret!==null){
1545
1720
 
1546
1721
  if((context.isServer==false && context.remote_finished_ok==true && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (context.isServer==true && context.cert_verify_sent==true && context.local_cert_chain!==null) || (context.isServer==true && context.psk_accepted==true && context.encrypted_exts_sent==true)){
1547
1722
 
@@ -1553,7 +1728,7 @@ function TLSSession(options){
1553
1728
  data: finished_data
1554
1729
  });
1555
1730
 
1556
- context.transcript.push(message_data);
1731
+ pushTranscript(message_data);
1557
1732
 
1558
1733
  context.finished_sent=true;
1559
1734
 
@@ -1563,7 +1738,7 @@ function TLSSession(options){
1563
1738
 
1564
1739
  }
1565
1740
 
1566
- }else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
1741
+ }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
1567
1742
 
1568
1743
  if((context.isServer==true && context.remote_finished_ok==true) || (context.isServer==false && context.key_exchange_sent==true)){
1569
1744
 
@@ -1583,7 +1758,7 @@ function TLSSession(options){
1583
1758
  data: finished_data
1584
1759
  });
1585
1760
 
1586
- context.transcript.push(message_data);
1761
+ pushTranscript(message_data);
1587
1762
 
1588
1763
  context.finished_sent=true;
1589
1764
 
@@ -1598,7 +1773,7 @@ function TLSSession(options){
1598
1773
  }
1599
1774
 
1600
1775
  //get app traffic secret...
1601
- if (context.selected_version === wire.TLS_VERSION.TLS1_3){
1776
+ if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
1602
1777
  if(context.base_secret!==null && context.local_app_traffic_secret==null && context.remote_app_traffic_secret==null){
1603
1778
 
1604
1779
  if((context.isServer==true && context.finished_sent==true && context.remote_finished_ok==false) || (context.isServer==false && context.finished_sent==false && context.remote_finished_ok==true)){
@@ -1624,7 +1799,7 @@ function TLSSession(options){
1624
1799
  //expected_remote_finished...
1625
1800
  if (context.expected_remote_finished==null && context.selected_cipher_suite!==null){
1626
1801
 
1627
- if(context.selected_version == wire.TLS_VERSION.TLS1_3 && context.remote_handshake_traffic_secret!==null){
1802
+ if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.remote_handshake_traffic_secret!==null){
1628
1803
 
1629
1804
  if((context.isServer==true && context.finished_sent==true) || (context.isServer==false && context.remote_finished !== null)){
1630
1805
 
@@ -1632,7 +1807,7 @@ function TLSSession(options){
1632
1807
 
1633
1808
  }
1634
1809
 
1635
- }else if(context.selected_version === wire.TLS_VERSION.TLS1_2 && context.base_secret!==null){
1810
+ }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.base_secret!==null){
1636
1811
 
1637
1812
  if(context.remote_finished!==null){
1638
1813
 
@@ -1668,7 +1843,7 @@ function TLSSession(options){
1668
1843
  data: context.remote_finished
1669
1844
  });
1670
1845
 
1671
- context.transcript.push(message_data);
1846
+ pushTranscript(message_data);
1672
1847
 
1673
1848
  params_to_set['remote_finished_ok']=true;
1674
1849
 
@@ -1687,13 +1862,13 @@ function TLSSession(options){
1687
1862
 
1688
1863
 
1689
1864
 
1690
- if(context.state!=='connected' && context.remote_finished_ok==true && ((context.selected_version === wire.TLS_VERSION.TLS1_3 && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || context.selected_version === wire.TLS_VERSION.TLS1_2)){
1865
+ if(context.state!=='connected' && context.remote_finished_ok==true && (((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2))){
1691
1866
  context.state='connected';
1692
1867
  context.handshakeEndTime = Date.now();
1693
1868
  ev.emit('secureConnect');
1694
1869
 
1695
1870
  // TLS 1.3: compute resumption_master_secret (both client and server need it)
1696
- if (context.selected_version === wire.TLS_VERSION.TLS1_3 && context.tls13_master_secret && !context.resumption_master_secret) {
1871
+ if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.tls13_master_secret && !context.resumption_master_secret) {
1697
1872
  let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
1698
1873
  context.resumption_master_secret = derive_resumption_master_secret(
1699
1874
  hashName, context.tls13_master_secret, concatUint8Arrays(context.transcript)
@@ -1701,7 +1876,7 @@ function TLSSession(options){
1701
1876
  }
1702
1877
 
1703
1878
  // TLS 1.3 server: send NewSessionTicket
1704
- if (context.selected_version === wire.TLS_VERSION.TLS1_3 && context.isServer && !context.session_ticket_sent && !context.noTickets && context.resumption_master_secret) {
1879
+ if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.isServer && !context.session_ticket_sent && !context.noTickets && context.resumption_master_secret) {
1705
1880
  context.session_ticket_sent = true;
1706
1881
 
1707
1882
  let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
@@ -1926,6 +2101,16 @@ function TLSSession(options){
1926
2101
  extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
1927
2102
  }
1928
2103
 
2104
+ // Add ALPN if provided (e.g. 'h3' for QUIC)
2105
+ if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
2106
+ extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
2107
+ }
2108
+
2109
+ // Add custom extensions (e.g. QUIC transport params 0x39)
2110
+ for (let i in context.local_extensions) {
2111
+ extensions.push(context.local_extensions[i]);
2112
+ }
2113
+
1929
2114
  // PSK resumption: check if session/psk was provided
1930
2115
  let pskData = options.session || options.psk || null;
1931
2116
  let message_data;
@@ -1966,6 +2151,7 @@ function TLSSession(options){
1966
2151
  version: 0x0303,
1967
2152
  random: context.local_random,
1968
2153
  session_id: context.local_session_id,
2154
+ cookie: context.dtls_cookie,
1969
2155
  cipher_suite: context.local_supported_cipher_suites,
1970
2156
  extensions: extensions
1971
2157
  };
@@ -1990,13 +2176,14 @@ function TLSSession(options){
1990
2176
  version: 0x0303,
1991
2177
  random: context.local_random,
1992
2178
  session_id: context.local_session_id,
2179
+ cookie: context.dtls_cookie,
1993
2180
  cipher_suite: context.local_supported_cipher_suites,
1994
2181
  extensions: extensions
1995
2182
  };
1996
2183
  message_data = build_tls_message(build_message_params);
1997
2184
  }
1998
2185
 
1999
- context.transcript.push(message_data);
2186
+ pushTranscript(message_data);
2000
2187
 
2001
2188
  context.hello_sent=true;
2002
2189
 
@@ -2140,11 +2327,11 @@ function TLSSession(options){
2140
2327
  let cipherInfo = context.selected_cipher_suite ? TLS_CIPHER_SUITES[context.selected_cipher_suite] : null;
2141
2328
  return {
2142
2329
  version: context.selected_version,
2143
- versionName: context.selected_version === 0x0304 ? 'TLSv1.3' : context.selected_version === 0x0303 ? 'TLSv1.2' : null,
2330
+ versionName: context.selected_version === 0x0304 ? 'TLSv1.3' : context.selected_version === 0xFEFC ? 'DTLSv1.3' : context.selected_version === 0x0303 ? 'TLSv1.2' : context.selected_version === 0xFEFD ? 'DTLSv1.2' : null,
2144
2331
  cipher: context.selected_cipher_suite,
2145
2332
  cipherName: cipherInfo ? cipherInfo.name : null,
2146
2333
  group: context.selected_group,
2147
- groupName: context.selected_group === 0x001d ? 'X25519' : context.selected_group === 0x0017 ? 'P-256' : null,
2334
+ groupName: context.selected_group === 0x001d ? 'X25519' : context.selected_group === 0x0017 ? 'P-256' : context.selected_group === 0x0018 ? 'P-384' : null,
2148
2335
  signatureAlgorithm: context.selected_signature_algorithm,
2149
2336
  alpn: context.selected_alpn,
2150
2337
  sni: context.selected_sni || context.local_sni,
@@ -2177,7 +2364,7 @@ function TLSSession(options){
2177
2364
 
2178
2365
  /** Request a TLS 1.3 Key Update. requestPeer=true means ask the other side to update too. */
2179
2366
  requestKeyUpdate: function(requestPeer){
2180
- if (context.state !== 'connected' || context.selected_version !== wire.TLS_VERSION.TLS1_3) return;
2367
+ if (context.state !== 'connected' || (context.selected_version !== wire.TLS_VERSION.TLS1_3 && context.selected_version !== wire.DTLS_VERSION.DTLS1_3)) return;
2181
2368
  let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
2182
2369
  let hashLen = getHashLen(hashName);
2183
2370