lemon-tls 0.4.0 → 0.5.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/README.md +104 -2
- package/index.js +10 -0
- package/package.json +1 -1
- package/src/compat.js +30 -17
- package/src/crypto.js +92 -2
- package/src/dtls_session.js +222 -43
- package/src/dtls_socket.js +22 -0
- package/src/record.js +133 -5
- package/src/session/ecdh.js +16 -1
- package/src/tls_session.js +269 -39
- package/src/tls_socket.js +44 -1
- package/src/utils.js +28 -1
package/README.md
CHANGED
|
@@ -210,7 +210,7 @@ High-level wrapper extending `stream.Duplex`, API-compatible with Node.js [`tls.
|
|
|
210
210
|
| `session` | `(Buffer)` | **Opaque session blob** — pass back to `connect({ session })` to resume |
|
|
211
211
|
| `keyUpdate` | `(direction)` | Traffic keys refreshed: `'send'` or `'receive'` |
|
|
212
212
|
| `keylog` | `(Buffer)` | SSLKEYLOGFILE-format line (for Wireshark) |
|
|
213
|
-
| `clienthello` | `(raw, parsed)` |
|
|
213
|
+
| `clienthello` | `(raw, parsed)` | ClientHello received (server side). Fires **synchronously before the ServerHello is built** — use it for JA3 fingerprinting, or to inspect the client's offered extensions (`parsed.extensions` / `getRemoteExtension`) and answer them via `set_context({ local_extensions })` (e.g. DTLS-SRTP `use_srtp`) |
|
|
214
214
|
| `handshakeMessage` | `(type, raw, parsed)` | Every handshake message (debugging) |
|
|
215
215
|
| `certificateRequest` | `(msg)` | Server requested a client certificate |
|
|
216
216
|
| `error` | `(Error)` | TLS or transport error |
|
|
@@ -234,7 +234,7 @@ High-level wrapper extending `stream.Duplex`, API-compatible with Node.js [`tls.
|
|
|
234
234
|
| `socket.getPeerFinished()` | Peer Finished verify_data (Buffer) |
|
|
235
235
|
| `socket.getSharedSigalgs()` | Array of shared signature algorithm names (server-side) |
|
|
236
236
|
| `socket.getEphemeralKeyInfo()` | `{ type: 'X25519', size: 253 }` |
|
|
237
|
-
| `socket.exportKeyingMaterial(len, label, ctx)` | RFC 5705
|
|
237
|
+
| `socket.exportKeyingMaterial(len, label, ctx)` | Exported keying material — RFC 5705 on (D)TLS 1.2, RFC 8446 §7.5 on (D)TLS 1.3. Returns `null` before secrets are ready (Node throws instead). Omit `ctx` for the no-context form (≠ empty context on 1.2!) — that's what DTLS-SRTP's `'EXTRACTOR-dtls_srtp'` uses |
|
|
238
238
|
| `socket.isSessionReused()` | `true` if session was resumed |
|
|
239
239
|
| `socket.setMaxSendFragment(size)` | Cap outgoing record plaintext size `[512, 16384]` |
|
|
240
240
|
| `socket.setServername(name)` | Set SNI (client-side, before handshake) |
|
|
@@ -262,6 +262,8 @@ High-level wrapper extending `stream.Duplex`, API-compatible with Node.js [`tls.
|
|
|
262
262
|
| `socket.getNegotiationResult()` | `{ version, cipher, group, sni, alpn, resumed, helloRetried, ... }` |
|
|
263
263
|
| `socket.rekeySend()` | Refresh outgoing encryption keys (TLS 1.3) |
|
|
264
264
|
| `socket.rekeyBoth()` | Refresh keys for both directions (TLS 1.3) |
|
|
265
|
+
| `socket.getRemoteExtensions()` | All extensions the peer sent in its hello: `[{ type, name, data, value }]`. On TLS 1.3 clients this is the union of ServerHello + EncryptedExtensions |
|
|
266
|
+
| `socket.getRemoteExtension(type)` | One peer extension by numeric type (e.g. `14` = use_srtp, `0x39` = QUIC transport params), or `null` |
|
|
265
267
|
|
|
266
268
|
### `TLSSession`
|
|
267
269
|
|
|
@@ -269,6 +271,8 @@ The **core state machine** for a TLS connection. Performs handshake, key derivat
|
|
|
269
271
|
|
|
270
272
|
This is the API to use for QUIC, DTLS, or any custom transport.
|
|
271
273
|
|
|
274
|
+
Key methods (in addition to `set_context` / `feedDatagram`-style plumbing): `getTrafficSecrets()`, `getHandshakeSecrets()`, `exportKeyingMaterial(length, label, context?)` (RFC 5705 / RFC 8446 §7.5, version-dispatched), `getRemoteExtensions()` / `getRemoteExtension(type)` for inspecting what the peer offered, and the `'clienthello'` event — emitted **synchronously before the ServerHello is built**, so a server can read the client's offered extensions and answer them via `set_context({ local_extensions })`. `DTLSSession` and both socket classes delegate all of these.
|
|
275
|
+
|
|
272
276
|
```js
|
|
273
277
|
import { TLSSession } from 'lemon-tls';
|
|
274
278
|
|
|
@@ -314,6 +318,45 @@ session.on('clienthello', (raw, parsed) => {
|
|
|
314
318
|
});
|
|
315
319
|
```
|
|
316
320
|
|
|
321
|
+
### DTLS API
|
|
322
|
+
|
|
323
|
+
Everything above has a DTLS twin. Same TLS state machine underneath, same
|
|
324
|
+
methods, plus datagram-specific plumbing (retransmission flights, epoch
|
|
325
|
+
handling, cookie exchange):
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
import { DTLSSession, DTLSSocket, createDTLSServer, connectDTLS } from 'lemon-tls';
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**`DTLSSession`** — transport-less state machine (the DTLS analog of
|
|
332
|
+
`TLSSession`). You move the datagrams:
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
const dtls = new DTLSSession({
|
|
336
|
+
cert, key,
|
|
337
|
+
isServer: true,
|
|
338
|
+
minVersion: 'DTLSv1.2', // DTLS 1.2 and 1.3 both supported
|
|
339
|
+
maxVersion: 'DTLSv1.3',
|
|
340
|
+
requestCert: true, // mutual auth (WebRTC-style)
|
|
341
|
+
rejectUnauthorized: false, // when doing fingerprint verification yourself
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
dtls.on('packet', (data) => udp.send(data, rport, raddr)); // outgoing datagrams
|
|
345
|
+
udp.on('message', (msg) => dtls.feedDatagram(msg)); // incoming datagrams
|
|
346
|
+
dtls.on('connect', () => { /* handshake done */ });
|
|
347
|
+
dtls.on('data', (plaintext) => { /* decrypted app data */ });
|
|
348
|
+
dtls.send(appData);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Delegated from the underlying `TLSSession` (identical semantics):
|
|
352
|
+
`exportKeyingMaterial`, `getRemoteExtensions` / `getRemoteExtension`,
|
|
353
|
+
`set_context`, `getNegotiationResult`, `getALPN`, `getPeerCertificate`,
|
|
354
|
+
the `'clienthello'` event, plus `.tls` for direct access to the session.
|
|
355
|
+
|
|
356
|
+
**`DTLSSocket` / `createDTLSServer` / `connectDTLS`** — UDP-transport
|
|
357
|
+
wrappers over `DTLSSession`, mirroring the TCP socket layer's shape.
|
|
358
|
+
The same delegated methods and events are available on the socket.
|
|
359
|
+
|
|
317
360
|
### Record Layer Module
|
|
318
361
|
|
|
319
362
|
Shared encrypt/decrypt primitives for QUIC, DTLS, and custom transport consumers:
|
|
@@ -328,6 +371,62 @@ const algo = getAeadAlgo(cipherSuite); // 'aes-128-gcm' | 'chacha20-poly1305'
|
|
|
328
371
|
const encrypted = encryptRecord(contentType, plaintext, key, nonce, algo);
|
|
329
372
|
```
|
|
330
373
|
|
|
374
|
+
|
|
375
|
+
### DTLS-SRTP (WebRTC-style key export)
|
|
376
|
+
|
|
377
|
+
The full RFC 5764 flow — negotiate an SRTP profile via the `use_srtp`
|
|
378
|
+
extension, then export the SRTP master keys from the handshake:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
import { DTLSSession } from 'lemon-tls';
|
|
382
|
+
|
|
383
|
+
// Client side: offer profiles (GCM preferred, CM fallback):
|
|
384
|
+
// body = u16 list_len | u16 profiles[] | u8 mki_len(0)
|
|
385
|
+
client.set_context({ local_extensions: [
|
|
386
|
+
{ type: 14, data: new Uint8Array([0x00,0x04, 0x00,0x07, 0x00,0x01, 0x00]) },
|
|
387
|
+
]});
|
|
388
|
+
|
|
389
|
+
// Server side: pick ONE from the client's offer, before ServerHello:
|
|
390
|
+
server.on('clienthello', (raw, parsed) => {
|
|
391
|
+
const offer = parsed.extensions.find(e => e.type === 14);
|
|
392
|
+
// ...parse offer.data, pick a profile `p`...
|
|
393
|
+
server.set_context({ local_extensions: [
|
|
394
|
+
{ type: 14, data: new Uint8Array([0x00,0x02, p >> 8, p & 0xFF, 0x00]) },
|
|
395
|
+
]});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// After 'connect' on both sides — identical on client and server:
|
|
399
|
+
const km = session.exportKeyingMaterial(60, 'EXTRACTOR-dtls_srtp');
|
|
400
|
+
// (60 bytes for AES_CM_128_HMAC_SHA1_80, 56 for AEAD_AES_128_GCM.
|
|
401
|
+
// Feed straight into rtp-packet's SrtpSession.fromDtlsKeyingMaterial.)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Works on DTLS 1.2 (answer in ServerHello) and DTLS 1.3 (answer in
|
|
405
|
+
EncryptedExtensions — `getRemoteExtension` sees both transparently).
|
|
406
|
+
|
|
407
|
+
### Crypto Primitives Module
|
|
408
|
+
|
|
409
|
+
The key-schedule building blocks are exported for consumers that hold raw
|
|
410
|
+
secrets (QUIC stacks, offline capture analysis, custom transports):
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
import { crypto } from 'lemon-tls';
|
|
414
|
+
|
|
415
|
+
crypto.hkdf_extract(hash, salt, ikm);
|
|
416
|
+
crypto.hkdf_expand(hash, prk, info, len);
|
|
417
|
+
crypto.hkdf_expand_label(hash, secret, label, context, len); // TLS 1.3 form
|
|
418
|
+
crypto.hmac(hash, key, data);
|
|
419
|
+
crypto.tls12_prf(secret, label, seed, len, hash); // RFC 5246 PRF
|
|
420
|
+
crypto.TLS_CIPHER_SUITES; // suite registry
|
|
421
|
+
|
|
422
|
+
// Standalone keying-material exporters — the same primitives
|
|
423
|
+
// session.exportKeyingMaterial() dispatches to. Use these directly only
|
|
424
|
+
// when you hold the raw secrets yourself; live connections should call
|
|
425
|
+
// the session/socket method, which picks the right one per version:
|
|
426
|
+
crypto.tls12_exporter(hash, masterSecret, label, clientRandom, serverRandom, ctxOrNull, len); // RFC 5705
|
|
427
|
+
crypto.tls13_exporter(hash, exporterMasterSecret, label, ctxOrNull, len); // RFC 8446 §7.5
|
|
428
|
+
```
|
|
429
|
+
|
|
331
430
|
## 🔧 Advanced Options (Not Available in Node.js)
|
|
332
431
|
|
|
333
432
|
LemonTLS gives you control that Node.js doesn't expose - without `openssl.cnf` hacks:
|
|
@@ -444,6 +543,9 @@ For a pure-JavaScript implementation with zero native dependencies, this is with
|
|
|
444
543
|
| ✅ | Node.js `tls` compat — **41 API methods/properties verified** |
|
|
445
544
|
| ✅ | TypeScript typings bundled (`index.d.ts`) |
|
|
446
545
|
| ✅ | DTLS 1.2 baseline (via `DTLSSocket` / `createDTLSServer`) |
|
|
546
|
+
| ✅ | `exportKeyingMaterial` — RFC 5705 (1.2) + RFC 8446 §7.5 (1.3, proper two-stage `exporter_master_secret` derivation) |
|
|
547
|
+
| ✅ | Peer-extension introspection (`getRemoteExtension(s)`) + custom `local_extensions` answered in the 1.2 ServerHello and 1.3 EncryptedExtensions |
|
|
548
|
+
| ✅ | DTLS-SRTP keying flow (use_srtp negotiation + key export) verified end-to-end against rtp-packet |
|
|
447
549
|
| ✅ | Zero dependencies - `node:crypto` only |
|
|
448
550
|
| ✅ | **72 automated tests** (compat, resumption, data transfer) |
|
|
449
551
|
| ✅ | Browser-tested (Chrome) |
|
package/index.js
CHANGED
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
hkdf_expand,
|
|
10
10
|
hkdf_expand_label,
|
|
11
11
|
hmac,
|
|
12
|
+
tls12_prf,
|
|
13
|
+
tls12_exporter,
|
|
14
|
+
tls13_exporter,
|
|
12
15
|
} from './src/crypto.js';
|
|
13
16
|
import * as wire from './src/wire.js';
|
|
14
17
|
import * as record from './src/record.js';
|
|
@@ -43,6 +46,13 @@ var crypto = {
|
|
|
43
46
|
hkdf_expand,
|
|
44
47
|
hkdf_expand_label,
|
|
45
48
|
hmac,
|
|
49
|
+
// Keying-material exporters (RFC 5705 / RFC 8446 §7.5) — standalone
|
|
50
|
+
// primitives for consumers that hold raw secrets (offline analysis,
|
|
51
|
+
// custom transports). Live sessions should prefer
|
|
52
|
+
// session.exportKeyingMaterial(), which picks the right one.
|
|
53
|
+
tls12_prf,
|
|
54
|
+
tls12_exporter,
|
|
55
|
+
tls13_exporter,
|
|
46
56
|
};
|
|
47
57
|
|
|
48
58
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemon-tls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "TLS 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",
|
package/src/compat.js
CHANGED
|
@@ -433,48 +433,61 @@ function toHex(buf) {
|
|
|
433
433
|
function addCompatMethods(socket) {
|
|
434
434
|
let session = socket._getTLSSession();
|
|
435
435
|
|
|
436
|
+
// Polyfill semantics: only define a method if the socket doesn't already
|
|
437
|
+
// provide it natively. TLSSocket has been growing first-class
|
|
438
|
+
// implementations (exportKeyingMaterial, getRemoteExtension(s), …) and a
|
|
439
|
+
// blind `socket.x = …` here would clobber them with a thinner shim —
|
|
440
|
+
// exactly the kind of silent duplication this layer must not create.
|
|
441
|
+
// compat.js stays what it claims to be: a gap-filler, never an override.
|
|
442
|
+
function def(name, fn) {
|
|
443
|
+
if (typeof socket[name] !== 'function') socket[name] = fn;
|
|
444
|
+
}
|
|
445
|
+
|
|
436
446
|
/** Node.js compat: isSessionReused() — method form (same as isResumed getter) */
|
|
437
|
-
|
|
447
|
+
def('isSessionReused', function() {
|
|
438
448
|
return socket.isResumed;
|
|
439
|
-
};
|
|
449
|
+
});
|
|
440
450
|
|
|
441
451
|
/** Node.js compat: getFinished() */
|
|
442
|
-
|
|
452
|
+
def('getFinished', function() {
|
|
443
453
|
return session.getFinished ? session.getFinished() : null;
|
|
444
|
-
};
|
|
454
|
+
});
|
|
445
455
|
|
|
446
456
|
/** Node.js compat: getPeerFinished() */
|
|
447
|
-
|
|
457
|
+
def('getPeerFinished', function() {
|
|
448
458
|
return session.getPeerFinished ? session.getPeerFinished() : null;
|
|
449
|
-
};
|
|
459
|
+
});
|
|
450
460
|
|
|
451
|
-
/** Node.js compat: exportKeyingMaterial(length, label, context)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
461
|
+
/** Node.js compat: exportKeyingMaterial(length, label, context).
|
|
462
|
+
* TLSSocket now ships this natively (RFC 5705 / RFC 8446 §7.5 via the
|
|
463
|
+
* session) — this shim only covers older socket-likes. Same
|
|
464
|
+
* null-until-ready convention as the native method. */
|
|
465
|
+
def('exportKeyingMaterial', function(length, label, context) {
|
|
466
|
+
return session.exportKeyingMaterial ? session.exportKeyingMaterial(length, label, context) : null;
|
|
467
|
+
});
|
|
455
468
|
|
|
456
469
|
/** Node.js compat: getEphemeralKeyInfo() */
|
|
457
|
-
|
|
470
|
+
def('getEphemeralKeyInfo', function() {
|
|
458
471
|
let group = session.context.selected_group;
|
|
459
472
|
if (group === 0x001d) return { type: 'X25519', size: 253 };
|
|
460
473
|
if (group === 0x0017) return { type: 'ECDH', name: 'prime256v1', size: 256 };
|
|
461
474
|
if (group === 0x0018) return { type: 'ECDH', name: 'secp384r1', size: 384 };
|
|
462
475
|
return {};
|
|
463
|
-
};
|
|
476
|
+
});
|
|
464
477
|
|
|
465
478
|
/** Node.js compat: setServername(name) */
|
|
466
|
-
|
|
479
|
+
def('setServername', function(name) {
|
|
467
480
|
session.set_context({ local_sni: name });
|
|
468
|
-
};
|
|
481
|
+
});
|
|
469
482
|
|
|
470
483
|
/** Node.js compat: disableRenegotiation() — no-op (renegotiation not supported) */
|
|
471
|
-
|
|
484
|
+
def('disableRenegotiation', function() {});
|
|
472
485
|
|
|
473
486
|
/** Node.js compat: address() — delegates to underlying transport */
|
|
474
|
-
|
|
487
|
+
def('address', function() {
|
|
475
488
|
try { return session.context && session.context.transport ? session.context.transport.address() : {}; }
|
|
476
489
|
catch(e) { return {}; }
|
|
477
|
-
};
|
|
490
|
+
});
|
|
478
491
|
}
|
|
479
492
|
|
|
480
493
|
// ===================== Exports =====================
|
package/src/crypto.js
CHANGED
|
@@ -136,8 +136,8 @@ const TLS_CIPHER_SUITES = {
|
|
|
136
136
|
cipher: 'CHACHA20_POLY1305',
|
|
137
137
|
aead: true,
|
|
138
138
|
keylen: 32,
|
|
139
|
-
fixed_ivlen:
|
|
140
|
-
record_ivlen:
|
|
139
|
+
fixed_ivlen: 12, // RFC 7905: ChaCha20 uses a full 12-byte IV, no explicit nonce
|
|
140
|
+
record_ivlen: 0,
|
|
141
141
|
ivlen: 12,
|
|
142
142
|
hash: 'sha256'
|
|
143
143
|
},
|
|
@@ -671,6 +671,93 @@ function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_h
|
|
|
671
671
|
// DTLS 1.3: record number encryption key (RFC 9147 §5.9)
|
|
672
672
|
// ============================================================
|
|
673
673
|
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
// ============================================================
|
|
677
|
+
// Keying-material exporters (RFC 5705 / RFC 8446 §7.5)
|
|
678
|
+
// ============================================================
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* TLS 1.3: derive the exporter_master_secret (RFC 8446 §7.1).
|
|
682
|
+
*
|
|
683
|
+
* exporter_master_secret = Derive-Secret(Master Secret, "exp master",
|
|
684
|
+
* ClientHello...server Finished)
|
|
685
|
+
*
|
|
686
|
+
* Called at the same point (and with the same transcript hash) as the
|
|
687
|
+
* application traffic secrets.
|
|
688
|
+
*/
|
|
689
|
+
function derive_exporter_master_secret_with_hash(hashName, master_secret, transcript_hash) {
|
|
690
|
+
let hashLen = getHashLen(hashName);
|
|
691
|
+
return hkdf_expand_label(hashName, master_secret, 'exp master', transcript_hash, hashLen);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* TLS 1.3 exporter (RFC 8446 §7.5) — two-stage derivation:
|
|
696
|
+
*
|
|
697
|
+
* TLS-Exporter(label, context_value, key_length) =
|
|
698
|
+
* HKDF-Expand-Label(
|
|
699
|
+
* Derive-Secret(exporter_master_secret, label, ""),
|
|
700
|
+
* "exporter", Hash(context_value), key_length)
|
|
701
|
+
*
|
|
702
|
+
* Note the FIRST stage uses Hash("") as its context (Derive-Secret over an
|
|
703
|
+
* empty transcript), and only the SECOND stage hashes the caller's context.
|
|
704
|
+
* A single-stage HKDF-Expand-Label(secret, label, ...) is NOT conformant
|
|
705
|
+
* and will not interoperate with OpenSSL/BoringSSL exporters.
|
|
706
|
+
*
|
|
707
|
+
* @param {string} hashName 'sha256' | 'sha384'
|
|
708
|
+
* @param {Uint8Array} exporter_master_secret
|
|
709
|
+
* @param {string} label
|
|
710
|
+
* @param {Uint8Array|null} context_value null/undefined ≡ empty (per RFC 8446)
|
|
711
|
+
* @param {number} length
|
|
712
|
+
* @returns {Uint8Array}
|
|
713
|
+
*/
|
|
714
|
+
function tls13_exporter(hashName, exporter_master_secret, label, context_value, length) {
|
|
715
|
+
let hashFn = getHashFn(hashName);
|
|
716
|
+
let hashLen = getHashLen(hashName);
|
|
717
|
+
let h_empty = hashFn(new Uint8Array(0));
|
|
718
|
+
let derived = hkdf_expand_label(hashName, exporter_master_secret, label, h_empty, hashLen);
|
|
719
|
+
let h_ctx = hashFn(context_value || new Uint8Array(0));
|
|
720
|
+
return hkdf_expand(hashName, derived, build_hkdf_label('exporter', h_ctx, length), length);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* TLS 1.2 exporter (RFC 5705).
|
|
725
|
+
*
|
|
726
|
+
* If no context: PRF(master_secret, label, client_random + server_random)
|
|
727
|
+
* With context: PRF(master_secret, label,
|
|
728
|
+
* client_random + server_random +
|
|
729
|
+
* uint16(len(context)) + context)
|
|
730
|
+
*
|
|
731
|
+
* "No context" and "empty context" produce DIFFERENT output in TLS 1.2
|
|
732
|
+
* (unlike 1.3) — pass context_value === null/undefined for the no-context
|
|
733
|
+
* form. DTLS-SRTP (RFC 5764) uses the no-context form with label
|
|
734
|
+
* 'EXTRACTOR-dtls_srtp'.
|
|
735
|
+
*
|
|
736
|
+
* The PRF hash follows the negotiated cipher suite (RFC 5246 §5 /
|
|
737
|
+
* RFC 5289): SHA-256 for *_SHA256 suites, SHA-384 for *_SHA384 suites.
|
|
738
|
+
*
|
|
739
|
+
* @param {string} hashName cipher suite's PRF hash
|
|
740
|
+
* @param {Uint8Array} master_secret
|
|
741
|
+
* @param {string} label
|
|
742
|
+
* @param {Uint8Array} client_random 32 bytes
|
|
743
|
+
* @param {Uint8Array} server_random 32 bytes
|
|
744
|
+
* @param {Uint8Array|null} context_value
|
|
745
|
+
* @param {number} length
|
|
746
|
+
* @returns {Uint8Array}
|
|
747
|
+
*/
|
|
748
|
+
function tls12_exporter(hashName, master_secret, label, client_random, server_random, context_value, length) {
|
|
749
|
+
let seedLen = 64 + (context_value != null ? 2 + context_value.length : 0);
|
|
750
|
+
let seed = new Uint8Array(seedLen);
|
|
751
|
+
seed.set(client_random, 0);
|
|
752
|
+
seed.set(server_random, 32);
|
|
753
|
+
if (context_value != null) {
|
|
754
|
+
seed[64] = (context_value.length >>> 8) & 0xFF;
|
|
755
|
+
seed[65] = context_value.length & 0xFF;
|
|
756
|
+
seed.set(context_value, 66);
|
|
757
|
+
}
|
|
758
|
+
return tls12_prf(master_secret, label, seed, length, hashName);
|
|
759
|
+
}
|
|
760
|
+
|
|
674
761
|
function derive_sn_key(hashName, traffic_secret, cipher_suite) {
|
|
675
762
|
let keylen = TLS_CIPHER_SUITES[cipher_suite].keylen;
|
|
676
763
|
return hkdf_expand_label(hashName, traffic_secret, 'sn', new Uint8Array(0), keylen);
|
|
@@ -708,4 +795,7 @@ export {
|
|
|
708
795
|
get_handshake_finished,
|
|
709
796
|
get_handshake_finished_with_hash,
|
|
710
797
|
derive_sn_key,
|
|
798
|
+
derive_exporter_master_secret_with_hash,
|
|
799
|
+
tls13_exporter,
|
|
800
|
+
tls12_exporter,
|
|
711
801
|
};
|