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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemon-tls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "TLS 1.3/1.2 DTLS 1.3/1.2 implementation for Node.js - full control over cryptographic keys, record layer, and handshake. Drop-in replacement for node:tls with advanced options impossible in OpenSSL.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"tls-1.3",
|
|
40
40
|
"tls-1.2",
|
|
41
41
|
"ssl",
|
|
42
|
+
"bogo",
|
|
42
43
|
"https",
|
|
43
44
|
"secure",
|
|
44
45
|
"crypto",
|
|
@@ -70,7 +71,6 @@
|
|
|
70
71
|
"ja3",
|
|
71
72
|
"ja3-fingerprint",
|
|
72
73
|
"fingerprint",
|
|
73
|
-
"certificate-pinning",
|
|
74
74
|
"keylog",
|
|
75
75
|
"wireshark",
|
|
76
76
|
"quic",
|
|
@@ -79,13 +79,8 @@
|
|
|
79
79
|
"pure-javascript",
|
|
80
80
|
"zero-dependencies",
|
|
81
81
|
"no-openssl",
|
|
82
|
-
"no-native",
|
|
83
|
-
"nodejs",
|
|
84
|
-
"node",
|
|
85
|
-
"javascript",
|
|
86
82
|
"security",
|
|
87
83
|
"network",
|
|
88
|
-
"protocol",
|
|
89
84
|
"drop-in-replacement"
|
|
90
85
|
],
|
|
91
86
|
"author": "colocohen",
|
package/src/compat.js
CHANGED
|
@@ -281,6 +281,16 @@ function Server(options, connectionListener) {
|
|
|
281
281
|
maxRecordSize: options.maxRecordSize,
|
|
282
282
|
sessionTickets: options.sessionTickets,
|
|
283
283
|
requestCert: options.requestCert,
|
|
284
|
+
// Client-authentication policy. These were missing, so a server's
|
|
285
|
+
// rejectUnauthorized/ca never reached TLSSession and it fell back to the
|
|
286
|
+
// session default (rejectUnauthorized: true). The effect was invisible
|
|
287
|
+
// until client auth was actually exercised: `requestCert: true` WITHOUT
|
|
288
|
+
// `rejectUnauthorized` is the standard "ask for a certificate but accept
|
|
289
|
+
// clients that have none" configuration, and it rejected them instead.
|
|
290
|
+
// requestCert was forwarded and its two companions were not, which is
|
|
291
|
+
// exactly the kind of half-wired option that looks correct at a glance.
|
|
292
|
+
rejectUnauthorized: options.rejectUnauthorized,
|
|
293
|
+
ca: options.ca,
|
|
284
294
|
maxHandshakeSize: options.maxHandshakeSize,
|
|
285
295
|
allowedCipherSuites: options.allowedCipherSuites,
|
|
286
296
|
handshakeTimeout: options.handshakeTimeout,
|
package/src/crypto.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import {
|
|
3
2
|
concatUint8Arrays
|
|
4
3
|
} from './utils.js';
|
|
@@ -303,8 +302,26 @@ function hkdf_expand(hashName, prkU8, infoU8, length) {
|
|
|
303
302
|
// to handshakes (TextEncoder construction is not free in V8).
|
|
304
303
|
const _TEXT_ENCODER = new TextEncoder();
|
|
305
304
|
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
// Label prefixes (RFC 8446 §7.1 / RFC 9147 §5.9). Exported so transports pick
|
|
306
|
+
// one explicitly instead of spelling the string out at each derivation site.
|
|
307
|
+
const LABEL_PREFIX_TLS13 = 'tls13 '; // trailing space is part of the label
|
|
308
|
+
const LABEL_PREFIX_DTLS13 = 'dtls13'; // no trailing space — deliberate
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* RFC 8446 §7.1 HkdfLabel, with the protocol's label prefix.
|
|
312
|
+
*
|
|
313
|
+
* TLS 1.3 uses "tls13 " (trailing space). DTLS 1.3 uses "dtls13" with NO
|
|
314
|
+
* trailing space — RFC 9147 §5.9: "For DTLS 1.3, that label SHALL be
|
|
315
|
+
* 'dtls13'. This ensures key separation between DTLS 1.3 and TLS 1.3. Note
|
|
316
|
+
* that there is no trailing space; this is necessary in order to keep the
|
|
317
|
+
* overall label size inside of one hash iteration."
|
|
318
|
+
*
|
|
319
|
+
* The prefix is threaded as a parameter rather than read from module state
|
|
320
|
+
* because it is a property of ONE session's key schedule; two sessions of
|
|
321
|
+
* different protocols can be live in the same process.
|
|
322
|
+
*/
|
|
323
|
+
function build_hkdf_label(label, context, length, labelPrefix) {
|
|
324
|
+
const full = _TEXT_ENCODER.encode((labelPrefix || LABEL_PREFIX_TLS13) + label);
|
|
308
325
|
const info = new Uint8Array(2 + 1 + full.length + 1 + context.length);
|
|
309
326
|
|
|
310
327
|
info[0] = (length >>> 8) & 0xff;
|
|
@@ -319,8 +336,8 @@ function build_hkdf_label(label, context, length) {
|
|
|
319
336
|
return info;
|
|
320
337
|
}
|
|
321
338
|
|
|
322
|
-
function hkdf_expand_label(hashName, secret, label, context, length) {
|
|
323
|
-
let info = build_hkdf_label(label, context, length | 0);
|
|
339
|
+
function hkdf_expand_label(hashName, secret, label, context, length, labelPrefix) {
|
|
340
|
+
let info = build_hkdf_label(label, context, length | 0, labelPrefix);
|
|
324
341
|
return hkdf_expand(hashName, secret, info, length | 0);
|
|
325
342
|
}
|
|
326
343
|
|
|
@@ -329,7 +346,7 @@ function hkdf_expand_label(hashName, secret, label, context, length) {
|
|
|
329
346
|
// TLS 1.3: derive handshake traffic secrets
|
|
330
347
|
// ============================================================
|
|
331
348
|
|
|
332
|
-
function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
|
|
349
|
+
function derive_handshake_traffic_secrets(hashName, shared_secret, transcript, labelPrefix) {
|
|
333
350
|
let hashFn = getHashFn(hashName);
|
|
334
351
|
let hashLen = hashFn.outputLen | 0;
|
|
335
352
|
const empty = new Uint8Array(0);
|
|
@@ -337,11 +354,11 @@ function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
|
|
|
337
354
|
|
|
338
355
|
let early_secret = hkdf_extract(hashName, empty, zeros);
|
|
339
356
|
let h_empty = hashFn(empty);
|
|
340
|
-
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
357
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
341
358
|
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
342
359
|
let transcript_hash = hashFn(transcript);
|
|
343
|
-
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
344
|
-
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
360
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
361
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
345
362
|
|
|
346
363
|
return {
|
|
347
364
|
handshake_secret: handshake_secret,
|
|
@@ -355,7 +372,7 @@ function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
|
|
|
355
372
|
* hash — skips the hashFn(transcript) step and its allocation. Use this when
|
|
356
373
|
* the caller has already computed the hash via an incremental running hash.
|
|
357
374
|
*/
|
|
358
|
-
function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, transcript_hash) {
|
|
375
|
+
function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, transcript_hash, labelPrefix) {
|
|
359
376
|
let hashFn = getHashFn(hashName);
|
|
360
377
|
let hashLen = hashFn.outputLen | 0;
|
|
361
378
|
const empty = new Uint8Array(0);
|
|
@@ -363,10 +380,10 @@ function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, tra
|
|
|
363
380
|
|
|
364
381
|
let early_secret = hkdf_extract(hashName, empty, zeros);
|
|
365
382
|
let h_empty = hashFn(empty);
|
|
366
|
-
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
383
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
367
384
|
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
368
|
-
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
369
|
-
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
385
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
386
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
370
387
|
|
|
371
388
|
return {
|
|
372
389
|
handshake_secret: handshake_secret,
|
|
@@ -380,18 +397,18 @@ function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, tra
|
|
|
380
397
|
// TLS 1.3: derive application traffic secrets
|
|
381
398
|
// ============================================================
|
|
382
399
|
|
|
383
|
-
function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
|
|
400
|
+
function derive_app_traffic_secrets(hashName, handshake_secret, transcript, labelPrefix) {
|
|
384
401
|
let hashFn = getHashFn(hashName);
|
|
385
402
|
let hashLen = hashFn.outputLen | 0;
|
|
386
403
|
const empty = new Uint8Array(0);
|
|
387
404
|
const zeros = new Uint8Array(hashLen);
|
|
388
405
|
|
|
389
406
|
let h_empty = hashFn(empty);
|
|
390
|
-
let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
|
|
407
|
+
let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
391
408
|
let master_secret = hkdf_extract(hashName, derived_secret, zeros);
|
|
392
409
|
let transcript_hash = hashFn(transcript);
|
|
393
|
-
let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
|
|
394
|
-
let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
|
|
410
|
+
let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen, labelPrefix);
|
|
411
|
+
let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen, labelPrefix);
|
|
395
412
|
|
|
396
413
|
return {
|
|
397
414
|
client_app_traffic_secret: client_app_traffic_secret,
|
|
@@ -403,17 +420,17 @@ function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
|
|
|
403
420
|
/**
|
|
404
421
|
* Like derive_app_traffic_secrets but accepts a pre-computed transcript hash.
|
|
405
422
|
*/
|
|
406
|
-
function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transcript_hash) {
|
|
423
|
+
function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transcript_hash, labelPrefix) {
|
|
407
424
|
let hashFn = getHashFn(hashName);
|
|
408
425
|
let hashLen = hashFn.outputLen | 0;
|
|
409
426
|
const empty = new Uint8Array(0);
|
|
410
427
|
const zeros = new Uint8Array(hashLen);
|
|
411
428
|
|
|
412
429
|
let h_empty = hashFn(empty);
|
|
413
|
-
let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
|
|
430
|
+
let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
414
431
|
let master_secret = hkdf_extract(hashName, derived_secret, zeros);
|
|
415
|
-
let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
|
|
416
|
-
let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
|
|
432
|
+
let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen, labelPrefix);
|
|
433
|
+
let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen, labelPrefix);
|
|
417
434
|
|
|
418
435
|
return {
|
|
419
436
|
client_app_traffic_secret: client_app_traffic_secret,
|
|
@@ -431,28 +448,28 @@ function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transc
|
|
|
431
448
|
* Derive the resumption_master_secret from the master_secret.
|
|
432
449
|
* transcript = all handshake messages including both Finished.
|
|
433
450
|
*/
|
|
434
|
-
function derive_resumption_master_secret(hashName, master_secret, transcript) {
|
|
451
|
+
function derive_resumption_master_secret(hashName, master_secret, transcript, labelPrefix) {
|
|
435
452
|
let hashFn = getHashFn(hashName);
|
|
436
453
|
let hashLen = hashFn.outputLen | 0;
|
|
437
454
|
let transcript_hash = hashFn(transcript);
|
|
438
|
-
return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen);
|
|
455
|
+
return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen, labelPrefix);
|
|
439
456
|
}
|
|
440
457
|
|
|
441
458
|
/**
|
|
442
459
|
* Like derive_resumption_master_secret but accepts a pre-computed transcript hash.
|
|
443
460
|
*/
|
|
444
|
-
function derive_resumption_master_secret_with_hash(hashName, master_secret, transcript_hash) {
|
|
461
|
+
function derive_resumption_master_secret_with_hash(hashName, master_secret, transcript_hash, labelPrefix) {
|
|
445
462
|
let hashLen = getHashLen(hashName);
|
|
446
|
-
return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen);
|
|
463
|
+
return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen, labelPrefix);
|
|
447
464
|
}
|
|
448
465
|
|
|
449
466
|
/**
|
|
450
467
|
* Derive PSK from a resumption_master_secret + ticket_nonce.
|
|
451
468
|
* Used by the server when creating a ticket, and by the client when resuming.
|
|
452
469
|
*/
|
|
453
|
-
function derive_psk(hashName, resumption_master_secret, ticket_nonce) {
|
|
470
|
+
function derive_psk(hashName, resumption_master_secret, ticket_nonce, labelPrefix) {
|
|
454
471
|
let hashLen = getHashFn(hashName).outputLen | 0;
|
|
455
|
-
return hkdf_expand_label(hashName, resumption_master_secret, 'resumption', ticket_nonce, hashLen);
|
|
472
|
+
return hkdf_expand_label(hashName, resumption_master_secret, 'resumption', ticket_nonce, hashLen, labelPrefix);
|
|
456
473
|
}
|
|
457
474
|
|
|
458
475
|
/**
|
|
@@ -460,7 +477,7 @@ function derive_psk(hashName, resumption_master_secret, ticket_nonce) {
|
|
|
460
477
|
* For resumption PSK, label is "res binder".
|
|
461
478
|
* For external PSK, label is "ext binder".
|
|
462
479
|
*/
|
|
463
|
-
function derive_binder_key(hashName, psk, isExternal) {
|
|
480
|
+
function derive_binder_key(hashName, psk, isExternal, labelPrefix) {
|
|
464
481
|
let hashFn = getHashFn(hashName);
|
|
465
482
|
let hashLen = hashFn.outputLen | 0;
|
|
466
483
|
const empty = new Uint8Array(0);
|
|
@@ -469,7 +486,7 @@ function derive_binder_key(hashName, psk, isExternal) {
|
|
|
469
486
|
let early_secret = hkdf_extract(hashName, empty, psk);
|
|
470
487
|
let h_empty = hashFn(empty);
|
|
471
488
|
let label = isExternal ? 'ext binder' : 'res binder';
|
|
472
|
-
return hkdf_expand_label(hashName, early_secret, label, h_empty, hashLen);
|
|
489
|
+
return hkdf_expand_label(hashName, early_secret, label, h_empty, hashLen, labelPrefix);
|
|
473
490
|
}
|
|
474
491
|
|
|
475
492
|
/**
|
|
@@ -484,11 +501,11 @@ function derive_binder_key(hashName, psk, isExternal) {
|
|
|
484
501
|
* So we must derive a finished_key from binder_key (as in get_handshake_finished)
|
|
485
502
|
* before the HMAC, NOT use binder_key directly.
|
|
486
503
|
*/
|
|
487
|
-
function compute_psk_binder(hashName, binder_key, truncated_transcript) {
|
|
504
|
+
function compute_psk_binder(hashName, binder_key, truncated_transcript, labelPrefix) {
|
|
488
505
|
let hashFn = getHashFn(hashName);
|
|
489
506
|
let hashLen = hashFn.outputLen | 0;
|
|
490
507
|
const empty = new Uint8Array(0);
|
|
491
|
-
let finished_key = hkdf_expand_label(hashName, binder_key, 'finished', empty, hashLen);
|
|
508
|
+
let finished_key = hkdf_expand_label(hashName, binder_key, 'finished', empty, hashLen, labelPrefix);
|
|
492
509
|
let transcript_hash = hashFn(truncated_transcript);
|
|
493
510
|
return hmac(hashName, finished_key, transcript_hash);
|
|
494
511
|
}
|
|
@@ -497,18 +514,18 @@ function compute_psk_binder(hashName, binder_key, truncated_transcript) {
|
|
|
497
514
|
* Derive handshake secrets for a PSK-based handshake (with ECDHE).
|
|
498
515
|
* Uses the PSK as input to early_secret instead of zeros.
|
|
499
516
|
*/
|
|
500
|
-
function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, transcript) {
|
|
517
|
+
function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, transcript, labelPrefix) {
|
|
501
518
|
let hashFn = getHashFn(hashName);
|
|
502
519
|
let hashLen = hashFn.outputLen | 0;
|
|
503
520
|
const empty = new Uint8Array(0);
|
|
504
521
|
|
|
505
522
|
let early_secret = hkdf_extract(hashName, empty, psk);
|
|
506
523
|
let h_empty = hashFn(empty);
|
|
507
|
-
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
524
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
508
525
|
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
509
526
|
let transcript_hash = hashFn(transcript);
|
|
510
|
-
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
511
|
-
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
527
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
528
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
512
529
|
|
|
513
530
|
return {
|
|
514
531
|
handshake_secret: handshake_secret,
|
|
@@ -520,17 +537,17 @@ function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, tran
|
|
|
520
537
|
/**
|
|
521
538
|
* Like derive_handshake_traffic_secrets_psk but accepts a pre-computed transcript hash.
|
|
522
539
|
*/
|
|
523
|
-
function derive_handshake_traffic_secrets_psk_with_hash(hashName, psk, shared_secret, transcript_hash) {
|
|
540
|
+
function derive_handshake_traffic_secrets_psk_with_hash(hashName, psk, shared_secret, transcript_hash, labelPrefix) {
|
|
524
541
|
let hashFn = getHashFn(hashName);
|
|
525
542
|
let hashLen = hashFn.outputLen | 0;
|
|
526
543
|
const empty = new Uint8Array(0);
|
|
527
544
|
|
|
528
545
|
let early_secret = hkdf_extract(hashName, empty, psk);
|
|
529
546
|
let h_empty = hashFn(empty);
|
|
530
|
-
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
547
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
|
|
531
548
|
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
532
|
-
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
533
|
-
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
549
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
550
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
|
|
534
551
|
|
|
535
552
|
return {
|
|
536
553
|
handshake_secret: handshake_secret,
|
|
@@ -648,10 +665,10 @@ function build_cert_verify_tbs_with_hash(hashName, isServer, transcript_hash) {
|
|
|
648
665
|
// TLS 1.3 Finished verify_data
|
|
649
666
|
// ============================================================
|
|
650
667
|
|
|
651
|
-
function get_handshake_finished(hashName, traffic_secret, transcript) {
|
|
668
|
+
function get_handshake_finished(hashName, traffic_secret, transcript, labelPrefix) {
|
|
652
669
|
let hashLen = getHashLen(hashName);
|
|
653
670
|
const empty = new Uint8Array(0);
|
|
654
|
-
let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
|
|
671
|
+
let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen, labelPrefix);
|
|
655
672
|
let transcript_hash = getHashFn(hashName)(transcript);
|
|
656
673
|
return hmac(hashName, finished_key, transcript_hash);
|
|
657
674
|
}
|
|
@@ -659,10 +676,10 @@ function get_handshake_finished(hashName, traffic_secret, transcript) {
|
|
|
659
676
|
/**
|
|
660
677
|
* Like get_handshake_finished but accepts a pre-computed transcript hash.
|
|
661
678
|
*/
|
|
662
|
-
function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_hash) {
|
|
679
|
+
function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_hash, labelPrefix) {
|
|
663
680
|
let hashLen = getHashLen(hashName);
|
|
664
681
|
const empty = new Uint8Array(0);
|
|
665
|
-
let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
|
|
682
|
+
let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen, labelPrefix);
|
|
666
683
|
return hmac(hashName, finished_key, transcript_hash);
|
|
667
684
|
}
|
|
668
685
|
|
|
@@ -686,9 +703,9 @@ function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_h
|
|
|
686
703
|
* Called at the same point (and with the same transcript hash) as the
|
|
687
704
|
* application traffic secrets.
|
|
688
705
|
*/
|
|
689
|
-
function derive_exporter_master_secret_with_hash(hashName, master_secret, transcript_hash) {
|
|
706
|
+
function derive_exporter_master_secret_with_hash(hashName, master_secret, transcript_hash, labelPrefix) {
|
|
690
707
|
let hashLen = getHashLen(hashName);
|
|
691
|
-
return hkdf_expand_label(hashName, master_secret, 'exp master', transcript_hash, hashLen);
|
|
708
|
+
return hkdf_expand_label(hashName, master_secret, 'exp master', transcript_hash, hashLen, labelPrefix);
|
|
692
709
|
}
|
|
693
710
|
|
|
694
711
|
/**
|
|
@@ -711,13 +728,25 @@ function derive_exporter_master_secret_with_hash(hashName, master_secret, transc
|
|
|
711
728
|
* @param {number} length
|
|
712
729
|
* @returns {Uint8Array}
|
|
713
730
|
*/
|
|
714
|
-
function tls13_exporter(hashName, exporter_master_secret, label, context_value, length) {
|
|
731
|
+
function tls13_exporter(hashName, exporter_master_secret, label, context_value, length, labelPrefix) {
|
|
715
732
|
let hashFn = getHashFn(hashName);
|
|
716
733
|
let hashLen = getHashLen(hashName);
|
|
717
734
|
let h_empty = hashFn(new Uint8Array(0));
|
|
718
|
-
let derived = hkdf_expand_label(hashName, exporter_master_secret, label, h_empty, hashLen);
|
|
735
|
+
let derived = hkdf_expand_label(hashName, exporter_master_secret, label, h_empty, hashLen, labelPrefix);
|
|
719
736
|
let h_ctx = hashFn(context_value || new Uint8Array(0));
|
|
720
|
-
|
|
737
|
+
// BOTH expansions carry the prefix. RFC 8446 §7.5 defines the exporter
|
|
738
|
+
// as two HKDF-Expand-Label steps, and in DTLS 1.3 both use "dtls13"
|
|
739
|
+
// (RFC 9147 §5.9) — this second one was omitting it and falling back
|
|
740
|
+
// to "tls13 ", so the exported key differed from a conforming peer's.
|
|
741
|
+
// For WebRTC that key IS the DTLS-SRTP keying material: the handshake
|
|
742
|
+
// completes, SRTP reports ready, packets flow in both directions, and
|
|
743
|
+
// NOTHING decrypts. Two peers running this library derive the same
|
|
744
|
+
// wrong key and interoperate perfectly with each other, which is why
|
|
745
|
+
// it only appears against Chrome — and appears as "media does not
|
|
746
|
+
// flow" rather than as a crypto error, since a failed unprotect is
|
|
747
|
+
// just a dropped packet.
|
|
748
|
+
return hkdf_expand(hashName, derived,
|
|
749
|
+
build_hkdf_label('exporter', h_ctx, length, labelPrefix), length);
|
|
721
750
|
}
|
|
722
751
|
|
|
723
752
|
/**
|
|
@@ -758,9 +787,9 @@ function tls12_exporter(hashName, master_secret, label, client_random, server_ra
|
|
|
758
787
|
return tls12_prf(master_secret, label, seed, length, hashName);
|
|
759
788
|
}
|
|
760
789
|
|
|
761
|
-
function derive_sn_key(hashName, traffic_secret, cipher_suite) {
|
|
790
|
+
function derive_sn_key(hashName, traffic_secret, cipher_suite, labelPrefix) {
|
|
762
791
|
let keylen = TLS_CIPHER_SUITES[cipher_suite].keylen;
|
|
763
|
-
return hkdf_expand_label(hashName, traffic_secret, 'sn', new Uint8Array(0), keylen);
|
|
792
|
+
return hkdf_expand_label(hashName, traffic_secret, 'sn', new Uint8Array(0), keylen, labelPrefix);
|
|
764
793
|
}
|
|
765
794
|
|
|
766
795
|
|
|
@@ -768,7 +797,87 @@ function derive_sn_key(hashName, traffic_secret, cipher_suite) {
|
|
|
768
797
|
// Exports — identical API surface
|
|
769
798
|
// ============================================================
|
|
770
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Default cipher-suite offers, in preference order.
|
|
802
|
+
*
|
|
803
|
+
* SINGLE SOURCE OF TRUTH, alongside the TLS_CIPHER_SUITES table this module
|
|
804
|
+
* already owns — the same rule as ecdh.js/SUPPORTED_GROUPS and
|
|
805
|
+
* signing.js/default_signature_schemes. Previously TLSSocket, DTLSSession and
|
|
806
|
+
* the TLSSession client default each carried their own hardcoded list, and
|
|
807
|
+
* they had already drifted (one offered ChaCha20 for 1.2, another offered
|
|
808
|
+
* AES-256-ECDSA instead).
|
|
809
|
+
*/
|
|
810
|
+
const DEFAULT_CIPHER_SUITES_TLS13 = [
|
|
811
|
+
0x1301, // TLS_AES_128_GCM_SHA256
|
|
812
|
+
0x1302, // TLS_AES_256_GCM_SHA384
|
|
813
|
+
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
|
814
|
+
];
|
|
815
|
+
|
|
816
|
+
const DEFAULT_CIPHER_SUITES_TLS12 = [
|
|
817
|
+
0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
818
|
+
0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
819
|
+
0xC02B, // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
820
|
+
0xC02C, // ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
|
821
|
+
0xCCA8, // ECDHE_RSA_WITH_CHACHA20_POLY1305
|
|
822
|
+
0xCCA9, // ECDHE_ECDSA_WITH_CHACHA20_POLY1305
|
|
823
|
+
];
|
|
824
|
+
|
|
825
|
+
/** Default offer list for a version range. */
|
|
826
|
+
/**
|
|
827
|
+
* Can the record layer actually protect traffic with this cipher suite?
|
|
828
|
+
*
|
|
829
|
+
* TLS_CIPHER_SUITES describes suites the code can NAME — including CBC ones
|
|
830
|
+
* kept for parsing peer offers and for reporting — but record.js implements
|
|
831
|
+
* AEAD only. A suite that is merely described is not a capability: selecting
|
|
832
|
+
* one produces a handshake that agrees on parameters and then cannot encrypt a
|
|
833
|
+
* single record.
|
|
834
|
+
*
|
|
835
|
+
* This mirrors ecdh.js's is_supported_group, and exists for the same reason: a
|
|
836
|
+
* configured list is not an implementation. Selection asks this, never the
|
|
837
|
+
* table's mere presence.
|
|
838
|
+
*/
|
|
839
|
+
function is_usable_cipher_suite(id) {
|
|
840
|
+
let m = TLS_CIPHER_SUITES[id];
|
|
841
|
+
return !!(m && m.aead === true);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* May this cipher suite be used with this negotiated version?
|
|
846
|
+
*
|
|
847
|
+
* RFC 8446 §B.4 defines 0x1301..0x1303 for TLS 1.3 ONLY; they are not valid
|
|
848
|
+
* TLS 1.2 suites and a 1.2 peer must ignore them. The converse holds too — an
|
|
849
|
+
* ECDHE_RSA_WITH_AES_128_GCM suite has no meaning in a 1.3 handshake, where
|
|
850
|
+
* key exchange and authentication are negotiated separately from the AEAD.
|
|
851
|
+
*
|
|
852
|
+
* The suite table already records which version each entry belongs to (`tls`:
|
|
853
|
+
* 12 or 13); this makes selection ask. Without it a server whose configured
|
|
854
|
+
* list happened to contain a 1.3 suite would select it while negotiating 1.2,
|
|
855
|
+
* producing a ServerHello no 1.2 client can act on.
|
|
856
|
+
*/
|
|
857
|
+
function suite_matches_version(id, negotiatedVersion) {
|
|
858
|
+
let m = TLS_CIPHER_SUITES[id];
|
|
859
|
+
if (!m) return false;
|
|
860
|
+
if (negotiatedVersion === null || negotiatedVersion === undefined) return true;
|
|
861
|
+
let is13 = negotiatedVersion === 0x0304 || negotiatedVersion === 0xFEFC;
|
|
862
|
+
return is13 ? (m.tls === 13) : (m.tls === 12);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function default_cipher_suites(includeTls13, includeTls12) {
|
|
866
|
+
let out = [];
|
|
867
|
+
if (includeTls13) out = out.concat(DEFAULT_CIPHER_SUITES_TLS13);
|
|
868
|
+
if (includeTls12) out = out.concat(DEFAULT_CIPHER_SUITES_TLS12);
|
|
869
|
+
return out;
|
|
870
|
+
}
|
|
871
|
+
|
|
771
872
|
export {
|
|
873
|
+
is_usable_cipher_suite,
|
|
874
|
+
suite_matches_version,
|
|
875
|
+
LABEL_PREFIX_TLS13,
|
|
876
|
+
LABEL_PREFIX_DTLS13,
|
|
877
|
+
DEFAULT_CIPHER_SUITES_TLS13,
|
|
878
|
+
DEFAULT_CIPHER_SUITES_TLS12,
|
|
879
|
+
default_cipher_suites,
|
|
880
|
+
|
|
772
881
|
TLS_CIPHER_SUITES,
|
|
773
882
|
getHashFn,
|
|
774
883
|
getHashLen,
|
|
@@ -798,4 +907,4 @@ export {
|
|
|
798
907
|
derive_exporter_master_secret_with_hash,
|
|
799
908
|
tls13_exporter,
|
|
800
909
|
tls12_exporter,
|
|
801
|
-
};
|
|
910
|
+
};
|