lemon-tls 0.7.0 → 0.10.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 +258 -20
- package/index.cjs +9 -7
- package/index.d.ts +323 -23
- package/index.js +126 -0
- package/package.json +39 -6
- package/src/compat.js +325 -46
- package/src/crypto.js +19 -0
- package/src/dtls_session.js +141 -17
- package/src/dtls_socket.js +32 -0
- package/src/node_options.js +138 -0
- package/src/record.js +192 -57
- package/src/secure_context.js +63 -4
- package/src/session/cert_compression.js +157 -0
- package/src/session/ecdh.js +237 -10
- package/src/session/ech.js +715 -0
- package/src/session/hpke.js +414 -0
- package/src/session/message.js +39 -4
- package/src/session/signing.js +56 -3
- package/src/session/ticket.js +38 -25
- package/src/tls_session.js +2835 -205
- package/src/tls_socket.js +839 -148
- package/src/utils.js +68 -1
- package/src/wire.js +148 -9
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
package/index.d.ts
CHANGED
|
@@ -7,9 +7,58 @@ import type { X509Certificate } from 'node:crypto';
|
|
|
7
7
|
|
|
8
8
|
// ======================== Types ========================
|
|
9
9
|
|
|
10
|
+
/** A key or certificate: PEM or DER bytes, or a file path. */
|
|
11
|
+
export type PemInput = Buffer | Uint8Array | string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* createSecureContext()'s options. `key` and `cert` as arrays — node:tls's
|
|
15
|
+
* form — for several certificates of different kinds (an ML-DSA one for
|
|
16
|
+
* clients that ask for post-quantum authentication, an ECDSA or RSA one for
|
|
17
|
+
* the rest): each key is paired with the chain whose leaf carries its public
|
|
18
|
+
* key, the first being the default.
|
|
19
|
+
*/
|
|
20
|
+
export interface SecureContextOptions {
|
|
21
|
+
key?: PemInput | Array<PemInput | { pem: PemInput; passphrase?: string }>;
|
|
22
|
+
cert?: PemInput | PemInput[];
|
|
23
|
+
ca?: PemInput | PemInput[];
|
|
24
|
+
passphrase?: string;
|
|
25
|
+
pfx?: PemInput;
|
|
26
|
+
/** A stapled OCSP response (DER) for the leaf. */
|
|
27
|
+
ocsp?: PemInput;
|
|
28
|
+
ticketKeys?: Buffer | Uint8Array;
|
|
29
|
+
[option: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
export interface SecureContext {
|
|
11
33
|
certificateChain: Array<{ cert: Buffer }>;
|
|
12
34
|
privateKey: any;
|
|
35
|
+
ca?: any[];
|
|
36
|
+
/** The other key/certificate pairs (key/cert arrays). */
|
|
37
|
+
alternates?: Array<{ certificateChain: Array<{ cert: Buffer }>; privateKey: any }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** An Encrypted Client Hello key (RFC 9849), from generateEchKey(). */
|
|
41
|
+
export interface EchKey {
|
|
42
|
+
config: Uint8Array;
|
|
43
|
+
privateKey: Uint8Array;
|
|
44
|
+
configList: Uint8Array;
|
|
45
|
+
/** The value of `ech=` in the domain's HTTPS record. */
|
|
46
|
+
configListBase64: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A server's `ech` option. `retry: false`: still accepted, no longer offered to clients. */
|
|
50
|
+
export interface EchServerOption {
|
|
51
|
+
keys: Array<{ config: Uint8Array | Buffer | string; privateKey: Uint8Array | Buffer | string; retry?: boolean }>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A saved session ('session' event's Buffer) described without its secrets: sessionInfo(). */
|
|
55
|
+
export interface SessionInfo {
|
|
56
|
+
version: 'TLSv1.3' | 'TLSv1.2' | null;
|
|
57
|
+
servername: string | null;
|
|
58
|
+
alpn: string | null;
|
|
59
|
+
created: number | null;
|
|
60
|
+
expiresAt: number | null;
|
|
61
|
+
earlyData: boolean;
|
|
13
62
|
}
|
|
14
63
|
|
|
15
64
|
export interface CipherInfo {
|
|
@@ -19,6 +68,7 @@ export interface CipherInfo {
|
|
|
19
68
|
}
|
|
20
69
|
|
|
21
70
|
export interface PeerCertificate {
|
|
71
|
+
/** RDN fields as node:tls has them: { C, ST, L, O, OU, CN }. */
|
|
22
72
|
subject: any;
|
|
23
73
|
issuer: any;
|
|
24
74
|
subjectaltname?: string;
|
|
@@ -31,16 +81,39 @@ export interface PeerCertificate {
|
|
|
31
81
|
}
|
|
32
82
|
|
|
33
83
|
export interface EphemeralKeyInfo {
|
|
34
|
-
|
|
84
|
+
/** 'TLSGroup' for a group that is not a plain curve, as Node reports it —
|
|
85
|
+
* X25519MLKEM768 among them (no size). Client side only; {} on a server. */
|
|
86
|
+
type: 'X25519' | 'ECDH' | 'DH' | 'TLSGroup';
|
|
35
87
|
name?: string;
|
|
36
|
-
size
|
|
88
|
+
size?: number;
|
|
37
89
|
}
|
|
38
90
|
|
|
39
|
-
|
|
91
|
+
/** A TLS fingerprint: `hash` is the fingerprint, `raw` what was hashed. */
|
|
92
|
+
export interface FingerprintResult {
|
|
40
93
|
hash: string;
|
|
41
94
|
raw: string;
|
|
42
95
|
}
|
|
43
96
|
|
|
97
|
+
export interface FingerprintOptions {
|
|
98
|
+
/** Transport character, for a caller driving the session from a transport
|
|
99
|
+
* it cannot see: 't' TCP (default), 'q' QUIC, 'd' DTLS. */
|
|
100
|
+
protocol?: 't' | 'q' | 'd';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Server: this connection's session ticket, from the first connection on.
|
|
105
|
+
* `data` is what the ticketData option put in the ticket — the one this
|
|
106
|
+
* connection resumed with, or, on a full handshake, the one made for the
|
|
107
|
+
* ticket issued to it (null until the server's Finished is sent).
|
|
108
|
+
* `offered && !resumed`: the client presented a ticket that was turned away
|
|
109
|
+
* (expired, issued before a restart, no compatible cipher).
|
|
110
|
+
*/
|
|
111
|
+
export interface TicketInfo {
|
|
112
|
+
data: any;
|
|
113
|
+
resumed: boolean;
|
|
114
|
+
offered: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
44
117
|
export interface NegotiationResult {
|
|
45
118
|
version: number | null;
|
|
46
119
|
versionName: string | null;
|
|
@@ -84,21 +157,96 @@ export interface CertificateCallbackInfo {
|
|
|
84
157
|
export interface TLSSocketOptions {
|
|
85
158
|
isServer?: boolean;
|
|
86
159
|
servername?: string;
|
|
87
|
-
SNICallback?: (servername: string, cb: (err: Error | null, ctx
|
|
160
|
+
SNICallback?: (servername: string, cb: (err: Error | null, ctx?: SecureContext | null) => void) => void;
|
|
161
|
+
/** Server, node:tls's: pick the protocol from what the client offers (exclusive with ALPNProtocols on a Server). */
|
|
162
|
+
ALPNCallback?: (info: { servername: string | null; protocols: string[] }) => string | undefined;
|
|
88
163
|
minVersion?: 'TLSv1.2' | 'TLSv1.3';
|
|
89
164
|
maxVersion?: 'TLSv1.2' | 'TLSv1.3';
|
|
90
165
|
ALPNProtocols?: string[];
|
|
91
166
|
rejectUnauthorized?: boolean;
|
|
92
|
-
|
|
167
|
+
/** Client: the identity check (node:tls's); undefined for a match, an Error otherwise. */
|
|
168
|
+
checkServerIdentity?: (hostname: string, cert: PeerCertificate) => Error | undefined;
|
|
169
|
+
ca?: PemInput | PemInput[];
|
|
170
|
+
/** A context from createSecureContext(), in place of key/cert/ca. */
|
|
171
|
+
secureContext?: SecureContext;
|
|
172
|
+
passphrase?: string;
|
|
93
173
|
ticketKeys?: Buffer;
|
|
174
|
+
/**
|
|
175
|
+
* Server: what to carry inside each session ticket issued. Called once per
|
|
176
|
+
* connection as ticketData(resumed) — `resumed` is the data of the ticket
|
|
177
|
+
* this connection resumed with, null on a full handshake. The return value
|
|
178
|
+
* (JSON-safe, at most 256 bytes serialized) rides in the ticket, encrypted
|
|
179
|
+
* with ticketKeys; read it back with getTicketInfo().
|
|
180
|
+
*/
|
|
181
|
+
ticketData?: (resumed: any) => any;
|
|
94
182
|
/**
|
|
95
183
|
* Opaque serialized session from a previous 'session' event.
|
|
96
184
|
* Pass this back to resume a TLS 1.2/1.3 session.
|
|
97
185
|
*/
|
|
98
186
|
session?: Buffer | Uint8Array;
|
|
99
187
|
requestCert?: boolean;
|
|
100
|
-
cert?:
|
|
101
|
-
key?:
|
|
188
|
+
cert?: PemInput;
|
|
189
|
+
key?: PemInput;
|
|
190
|
+
|
|
191
|
+
// node:tls's option strings — taken as Node takes them. lemon-tls's code
|
|
192
|
+
// lists (allowedCipherSuites, groups, signatureAlgorithms) win when given.
|
|
193
|
+
/** 'TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256…' */
|
|
194
|
+
ciphers?: string;
|
|
195
|
+
/** 'X25519:P-256', or 'auto'. */
|
|
196
|
+
ecdhCurve?: string;
|
|
197
|
+
/** 'ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256…' */
|
|
198
|
+
sigalgs?: string;
|
|
199
|
+
/** Server: the cipher suite by the server's order (default: the client's). */
|
|
200
|
+
honorCipherOrder?: boolean;
|
|
201
|
+
|
|
202
|
+
// ---- Encrypted Client Hello (RFC 9849) ----
|
|
203
|
+
/** Server: { keys }. Client: the server's ECHConfigList (bytes, or base64 — `ech=` of its HTTPS record). */
|
|
204
|
+
ech?: EchServerOption | Uint8Array | Buffer | string;
|
|
205
|
+
/** Client: GREASE ECH, without a config. */
|
|
206
|
+
echGrease?: boolean;
|
|
207
|
+
|
|
208
|
+
// ---- OCSP stapling ----
|
|
209
|
+
/** Server: asked when a client requests status; callback(null, response DER) or (null, null). */
|
|
210
|
+
ocspRequest?: (certificate: Buffer, issuer: Buffer | null, callback: (err: Error | null, response: Buffer | Uint8Array | null) => void) => void;
|
|
211
|
+
/** Client: ask the server for a stapled response ('OCSPResponse'). */
|
|
212
|
+
requestOCSP?: boolean;
|
|
213
|
+
|
|
214
|
+
/** RFC 8879: ['brotli', 'zlib', 'zstd'] in order, true (all this build has) or false (none). */
|
|
215
|
+
certificateCompression?: Array<'brotli' | 'zlib' | 'zstd'> | boolean;
|
|
216
|
+
|
|
217
|
+
// ---- 0-RTT (RFC 8446 §4.2.10) ----
|
|
218
|
+
/** Server: issue tickets that allow it, and take it. Client: with `session`, what is written before 'secureConnect' goes early. */
|
|
219
|
+
earlyData?: boolean;
|
|
220
|
+
/** Server: ms the client's ticket age may differ from ours. */
|
|
221
|
+
earlyDataMaxAge?: number;
|
|
222
|
+
/** Server: the anti-replay store — cb(null, true) when `id` is new (keep it `ttl` ms). */
|
|
223
|
+
earlyDataReplay?: (id: string, ttl: number, cb: (err: Error | null, fresh: boolean) => void) => void;
|
|
224
|
+
/** Server: bytes of early data a ticket allows (16 KB). */
|
|
225
|
+
maxEarlyData?: number;
|
|
226
|
+
|
|
227
|
+
// ---- ClientHello shaping (client) ----
|
|
228
|
+
grease?: boolean;
|
|
229
|
+
permuteExtensions?: boolean;
|
|
230
|
+
|
|
231
|
+
/** Client: trust the bundled CAs when no `ca` is given (default: true). */
|
|
232
|
+
useDefaultCA?: boolean;
|
|
233
|
+
allowHalfOpen?: boolean;
|
|
234
|
+
highWaterMark?: number;
|
|
235
|
+
|
|
236
|
+
// TCP options, as in node:net. connect() passes them (and the rest of
|
|
237
|
+
// net.connect's: localAddress, localPort, family, lookup, timeout) to
|
|
238
|
+
// net.connect; createServer() applies noDelay/keepAlive to every
|
|
239
|
+
// connection it accepts. Defaults as in Node: off.
|
|
240
|
+
/** Disable Nagle's algorithm (TCP_NODELAY). Default: false. */
|
|
241
|
+
noDelay?: boolean;
|
|
242
|
+
/** Enable TCP keep-alive. Default: false. */
|
|
243
|
+
keepAlive?: boolean;
|
|
244
|
+
/** Milliseconds before the first keep-alive probe. Default: 0. */
|
|
245
|
+
keepAliveInitialDelay?: number;
|
|
246
|
+
localAddress?: string;
|
|
247
|
+
localPort?: number;
|
|
248
|
+
family?: number;
|
|
249
|
+
timeout?: number;
|
|
102
250
|
|
|
103
251
|
// LemonTLS-only options
|
|
104
252
|
noTickets?: boolean;
|
|
@@ -116,6 +264,27 @@ export interface TLSSocketOptions {
|
|
|
116
264
|
customExtensions?: Array<{ type: number; data: Uint8Array }>;
|
|
117
265
|
}
|
|
118
266
|
|
|
267
|
+
/** connect()'s: the socket's options, where, and node:net's. */
|
|
268
|
+
export interface ConnectOptions extends TLSSocketOptions {
|
|
269
|
+
port?: number;
|
|
270
|
+
host?: string;
|
|
271
|
+
/** STARTTLS: an existing socket to run TLS on (no net.connect). */
|
|
272
|
+
socket?: Duplex;
|
|
273
|
+
lookup?: (...args: any[]) => void;
|
|
274
|
+
[option: string]: any;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** createServer()'s: the server side of TLSSocketOptions, and node:tls's server ones. */
|
|
278
|
+
export interface ServerOptions extends Omit<TLSSocketOptions, 'key' | 'cert'> {
|
|
279
|
+
key?: SecureContextOptions['key'];
|
|
280
|
+
cert?: SecureContextOptions['cert'];
|
|
281
|
+
/** Seconds a session stays good — tickets and TLS 1.2 Session IDs (Node's default, 300). */
|
|
282
|
+
sessionTimeout?: number;
|
|
283
|
+
/** Sessions under one context do not resume under another. */
|
|
284
|
+
sessionIdContext?: string | Buffer;
|
|
285
|
+
pauseOnConnect?: boolean;
|
|
286
|
+
}
|
|
287
|
+
|
|
119
288
|
// ======================== TLSSocket ========================
|
|
120
289
|
|
|
121
290
|
export class TLSSocket extends Duplex {
|
|
@@ -124,7 +293,8 @@ export class TLSSocket extends Duplex {
|
|
|
124
293
|
// ------- Node.js tls.TLSSocket compatible API -------
|
|
125
294
|
getProtocol(): string | null;
|
|
126
295
|
getCipher(): CipherInfo | null;
|
|
127
|
-
|
|
296
|
+
/** `detailed` is accepted for node:tls's signature; the leaf is described either way. */
|
|
297
|
+
getPeerCertificate(detailed?: boolean): PeerCertificate | null;
|
|
128
298
|
/** Returns our LOCAL cert (what we presented). Empty object if none. */
|
|
129
299
|
getCertificate(): PeerCertificate | {};
|
|
130
300
|
/** Returns the peer cert as a native crypto.X509Certificate object (Node 15.9+). */
|
|
@@ -134,10 +304,16 @@ export class TLSSocket extends Duplex {
|
|
|
134
304
|
isSessionReused(): boolean;
|
|
135
305
|
getFinished(): Buffer | null;
|
|
136
306
|
getPeerFinished(): Buffer | null;
|
|
137
|
-
|
|
138
|
-
|
|
307
|
+
/** null before the handshake completes (Node throws). */
|
|
308
|
+
exportKeyingMaterial(length: number, label: string, context?: Buffer | Uint8Array): Buffer | null;
|
|
309
|
+
/** null on a server socket, as in Node. */
|
|
310
|
+
getEphemeralKeyInfo(): EphemeralKeyInfo | {} | null;
|
|
311
|
+
/** On sockets from connect() and createServer() (compat layer). */
|
|
139
312
|
disableRenegotiation(): void;
|
|
313
|
+
/** On sockets from connect() and createServer() (compat layer). */
|
|
140
314
|
setServername(name: string): void;
|
|
315
|
+
/** The transport's bound address, as node:tls's. */
|
|
316
|
+
address(): { port?: number; family?: string; address?: string };
|
|
141
317
|
/** Returns the opaque serialized session Buffer, or undefined. */
|
|
142
318
|
getSession(): Buffer | undefined;
|
|
143
319
|
/** Returns the TLS 1.2 session ticket as a Buffer, or undefined (incl. for TLS 1.3). */
|
|
@@ -179,7 +355,29 @@ export class TLSSocket extends Duplex {
|
|
|
179
355
|
/** Returns the underlying TLSSession for low-level access. */
|
|
180
356
|
session: TLSSession;
|
|
181
357
|
readonly handshakeDuration: number | null;
|
|
182
|
-
|
|
358
|
+
/** 0-RTT. Server: what is read now came early (it may be a replay). Client: it is sending early. */
|
|
359
|
+
readonly isEarlyData: boolean;
|
|
360
|
+
readonly earlyDataBytes: number;
|
|
361
|
+
readonly earlyDataStatus: 'offered' | 'accepted' | 'rejected' | null;
|
|
362
|
+
/** Encrypted Client Hello: what happened to it on this connection. */
|
|
363
|
+
readonly echStatus: string | null;
|
|
364
|
+
readonly echAccepted: boolean;
|
|
365
|
+
/** Server: the certificate to present, from inside ALPNCallback (a context or { key, cert }). */
|
|
366
|
+
setKeyCert(context: SecureContext | SecureContextOptions): void;
|
|
367
|
+
/** The peer's hello extensions: [{ type, name, data, value }]. */
|
|
368
|
+
getRemoteExtensions(): Array<{ type: number; name: string | null; data: Uint8Array; value: any }>;
|
|
369
|
+
getRemoteExtension(type: number): { type: number; name: string | null; data: Uint8Array; value: any } | null;
|
|
370
|
+
/** JA4 of the ClientHello — the published format, what fingerprint
|
|
371
|
+
* databases match. Changes when a client resumes (pre_shared_key). */
|
|
372
|
+
getJA4(opts?: FingerprintOptions): FingerprintResult | null;
|
|
373
|
+
/** JA5 of the ClientHello (ntop's draft): as JA4 without the extensions that
|
|
374
|
+
* vary per connection, plus supported_groups — the same for a client
|
|
375
|
+
* whether it resumed or not. */
|
|
376
|
+
getJA5(opts?: FingerprintOptions): FingerprintResult | null;
|
|
377
|
+
/** JA4S of the ServerHello. */
|
|
378
|
+
getJA4S(opts?: FingerprintOptions): FingerprintResult | null;
|
|
379
|
+
/** Server: this connection's session ticket — see TicketInfo. */
|
|
380
|
+
getTicketInfo(): TicketInfo;
|
|
183
381
|
getSharedSecret(): Buffer | null;
|
|
184
382
|
getNegotiationResult(): NegotiationResult | null;
|
|
185
383
|
rekeySend(): void;
|
|
@@ -200,6 +398,13 @@ export class TLSSocket extends Duplex {
|
|
|
200
398
|
on(event: 'certificateRequest', listener: (msg: any) => void): this;
|
|
201
399
|
on(event: 'newSession', listener: (id: Buffer, data: Buffer, cb: () => void) => void): this;
|
|
202
400
|
on(event: 'resumeSession', listener: (id: Buffer, cb: (err: Error | null, data: Buffer | null) => void) => void): this;
|
|
401
|
+
/** Client: the server's stapled OCSP response (requestOCSP). */
|
|
402
|
+
on(event: 'OCSPResponse', listener: (response: Buffer) => void): this;
|
|
403
|
+
/** 0-RTT decided: whether the server took it. */
|
|
404
|
+
on(event: 'earlyData', listener: (accepted: boolean) => void): this;
|
|
405
|
+
on(event: 'alert', listener: (alert: { level: number; description: number }) => void): this;
|
|
406
|
+
on(event: 'secure', listener: () => void): this;
|
|
407
|
+
on(event: 'psk', listener: (identity: any, callback: (result: { psk: Buffer; cipher: number } | null) => void) => void): this;
|
|
203
408
|
on(event: 'error', listener: (err: Error) => void): this;
|
|
204
409
|
on(event: 'close', listener: () => void): this;
|
|
205
410
|
on(event: string, listener: (...args: any[]) => void): this;
|
|
@@ -218,8 +423,19 @@ export class Server extends EventEmitter {
|
|
|
218
423
|
close(callback?: (err?: Error) => void): this;
|
|
219
424
|
address(): { port: number; family: string; address: string } | string | null;
|
|
220
425
|
|
|
221
|
-
|
|
222
|
-
|
|
426
|
+
getConnections(callback: (err: Error | null, count: number) => void): void;
|
|
427
|
+
/** Node's documented injection of a Duplex into the server ('connection'), as a method. */
|
|
428
|
+
injectConnection(stream: Duplex): this;
|
|
429
|
+
ref(): this;
|
|
430
|
+
unref(): this;
|
|
431
|
+
|
|
432
|
+
/** Replace the server's key/cert at runtime (Let's Encrypt renewals, etc.). Throws on a bad one; the old stays. */
|
|
433
|
+
setSecureContext(options: SecureContextOptions | { secureContext: SecureContext }): void;
|
|
434
|
+
/** A certificate for an SNI name or wildcard ('*.example.com': one label). The latest wins; an exact name beats a wildcard. */
|
|
435
|
+
addContext(hostname: string, context: SecureContext | SecureContextOptions): this;
|
|
436
|
+
/** Encrypted Client Hello keys, rotated without a restart. */
|
|
437
|
+
getEchKeys(): EchServerOption | null;
|
|
438
|
+
setEchKeys(ech: EchServerOption | null): void;
|
|
223
439
|
/** Returns the 48-byte TLS session ticket encryption keys. */
|
|
224
440
|
getTicketKeys(): Buffer;
|
|
225
441
|
/** Sets the 48-byte TLS session ticket encryption keys (for clustered deployments). */
|
|
@@ -231,6 +447,11 @@ export class Server extends EventEmitter {
|
|
|
231
447
|
on(event: 'secureConnection', listener: (socket: TLSSocket) => void): this;
|
|
232
448
|
on(event: 'tlsClientError', listener: (err: Error, socket: TLSSocket | null) => void): this;
|
|
233
449
|
on(event: 'keylog', listener: (line: Buffer, socket: TLSSocket) => void): this;
|
|
450
|
+
/** Before the TLS handshake: the raw socket. */
|
|
451
|
+
on(event: 'connection', listener: (socket: Duplex) => void): this;
|
|
452
|
+
on(event: 'OCSPRequest', listener: (certificate: Buffer, issuer: Buffer | null, callback: (err: Error | null, response: Buffer | Uint8Array | null) => void) => void): this;
|
|
453
|
+
on(event: 'listening', listener: () => void): this;
|
|
454
|
+
on(event: 'drop', listener: (data: any) => void): this;
|
|
234
455
|
on(event: 'newSession', listener: (id: Buffer, data: Buffer, cb: () => void) => void): this;
|
|
235
456
|
on(event: 'resumeSession', listener: (id: Buffer, cb: (err: Error | null, data: Buffer | null) => void) => void): this;
|
|
236
457
|
on(event: 'error', listener: (err: Error) => void): this;
|
|
@@ -246,6 +467,8 @@ export interface TLSSessionOptions {
|
|
|
246
467
|
ALPNProtocols?: string[];
|
|
247
468
|
SNICallback?: (servername: string, cb: (err: Error | null, ctx: SecureContext | null) => void) => void;
|
|
248
469
|
ticketKeys?: Buffer;
|
|
470
|
+
/** See TLSSocketOptions.ticketData. */
|
|
471
|
+
ticketData?: (resumed: any) => any;
|
|
249
472
|
session?: Buffer | Uint8Array | SessionTicketData;
|
|
250
473
|
psk?: any;
|
|
251
474
|
rejectUnauthorized?: boolean;
|
|
@@ -295,6 +518,8 @@ export class TLSSession extends EventEmitter {
|
|
|
295
518
|
getCipher(): number | null;
|
|
296
519
|
getALPN(): string | null;
|
|
297
520
|
getPeerCertificate(): Array<{ cert: Buffer }> | null;
|
|
521
|
+
/** Our own certificate chain — the one this connection presents. */
|
|
522
|
+
getCertificate(): Array<{ cert: Buffer }> | null;
|
|
298
523
|
getTrafficSecrets(): TrafficSecrets;
|
|
299
524
|
getHandshakeSecrets(): HandshakeSecrets;
|
|
300
525
|
exportKeyingMaterial(length: number, label: string, context: Uint8Array): Uint8Array;
|
|
@@ -302,7 +527,11 @@ export class TLSSession extends EventEmitter {
|
|
|
302
527
|
getPeerFinished(): Buffer | null;
|
|
303
528
|
getSharedSecret(): Buffer | null;
|
|
304
529
|
getNegotiationResult(): NegotiationResult;
|
|
305
|
-
|
|
530
|
+
getJA4(opts?: FingerprintOptions): FingerprintResult | null;
|
|
531
|
+
getJA5(opts?: FingerprintOptions): FingerprintResult | null;
|
|
532
|
+
getJA4S(opts?: FingerprintOptions): FingerprintResult | null;
|
|
533
|
+
/** Server: this connection's session ticket — see TicketInfo. */
|
|
534
|
+
getTicketInfo(): TicketInfo;
|
|
306
535
|
requestKeyUpdate(requestPeer?: boolean): void;
|
|
307
536
|
|
|
308
537
|
// Events
|
|
@@ -325,19 +554,57 @@ export class TLSSession extends EventEmitter {
|
|
|
325
554
|
|
|
326
555
|
// ======================== Module Functions ========================
|
|
327
556
|
|
|
328
|
-
export function createSecureContext(options:
|
|
557
|
+
export function createSecureContext(options: SecureContextOptions): SecureContext;
|
|
329
558
|
|
|
330
|
-
export function connect(port: number, host: string, options?:
|
|
559
|
+
export function connect(port: number, host: string, options?: ConnectOptions, callback?: () => void): TLSSocket;
|
|
331
560
|
export function connect(port: number, host: string, callback?: () => void): TLSSocket;
|
|
332
|
-
export function connect(options:
|
|
333
|
-
|
|
334
|
-
export function createServer(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
561
|
+
export function connect(options: ConnectOptions, callback?: () => void): TLSSocket;
|
|
562
|
+
|
|
563
|
+
export function createServer(options: ServerOptions, connectionListener?: (socket: TLSSocket) => void): Server;
|
|
564
|
+
export function createServer(connectionListener?: (socket: TLSSocket) => void): Server;
|
|
565
|
+
|
|
566
|
+
// ---- node:tls's option strings → code lists (null when unset). They throw
|
|
567
|
+
// on a name they do not know, as Node's createServer does.
|
|
568
|
+
export function parseCiphers(ciphers: string | null | undefined): number[] | null;
|
|
569
|
+
export function parseEcdhCurve(ecdhCurve: string | null | undefined): number[] | null;
|
|
570
|
+
export function parseSigalgs(sigalgs: string | null | undefined): number[] | null;
|
|
571
|
+
export function parseTlsVersion(version: string | null | undefined, optionName?: string): number | null;
|
|
572
|
+
export function parseCertificateCompression(value: Array<'brotli' | 'zlib' | 'zstd'> | boolean | null | undefined): number[] | null;
|
|
573
|
+
|
|
574
|
+
/** A server's certificate chain compressed now, in the background (RFC 8879), so the first handshake does not wait for it. */
|
|
575
|
+
export function precompressCertificate(secureContext: SecureContext, algorithms?: Array<'brotli' | 'zlib' | 'zstd'> | boolean, callback?: () => void): void;
|
|
576
|
+
export function precompressCertificate(secureContext: SecureContext, callback?: () => void): void;
|
|
577
|
+
|
|
578
|
+
/** The key-exchange groups a server offers by default, in order (code points). */
|
|
579
|
+
export const DEFAULT_SERVER_GROUPS: readonly number[];
|
|
580
|
+
/** The signature schemes offered by default, in order (code points). */
|
|
581
|
+
export const DEFAULT_SIGNATURE_SCHEMES: readonly number[];
|
|
582
|
+
|
|
583
|
+
/** A saved session described without its secrets; null if it is not one. */
|
|
584
|
+
export function sessionInfo(session: Buffer | Uint8Array): SessionInfo | null;
|
|
585
|
+
/** A certificate (DER) as getPeerCertificate() describes it. */
|
|
586
|
+
export function certificateObject(der: Buffer | Uint8Array): PeerCertificate | { raw: Buffer | Uint8Array } | null;
|
|
587
|
+
|
|
588
|
+
/** A new ECH key for a server. publicName: the name the outer hello carries (the server holds a certificate for it). */
|
|
589
|
+
export function generateEchKey(options: { publicName: string; configId?: number; maxNameLength?: number; kem?: string | number }): EchKey;
|
|
590
|
+
/** Throws, saying what is wrong, if a server's `ech` option would not work. */
|
|
591
|
+
export function checkEchKeys(ech: EchServerOption | null | undefined): void;
|
|
338
592
|
|
|
339
593
|
export function getCiphers(): string[];
|
|
340
594
|
|
|
595
|
+
/** Node-compat: the shipped CA bundle (PEM strings) — tls.rootCertificates. */
|
|
596
|
+
export const rootCertificates: readonly string[];
|
|
597
|
+
/**
|
|
598
|
+
* Node-compat: tls.getCACertificates([type]). 'default' and 'bundled' return
|
|
599
|
+
* the CA set clients verify against when given no `ca` (the shipped bundle,
|
|
600
|
+
* or what setDefaultCACertificates put in its place); 'extra' and 'system'
|
|
601
|
+
* are not sourced here and return [].
|
|
602
|
+
*/
|
|
603
|
+
export function getCACertificates(type?: 'default' | 'bundled' | 'extra' | 'system'): string[];
|
|
604
|
+
/** Node-compat: tls.setDefaultCACertificates(certs) — replaces that set for
|
|
605
|
+
* every later connection that passes no `ca`. Throws if not an array. */
|
|
606
|
+
export function setDefaultCACertificates(certs: string[]): void;
|
|
607
|
+
|
|
341
608
|
/**
|
|
342
609
|
* Validates that the peer certificate matches the hostname (RFC 6125).
|
|
343
610
|
* Returns undefined on success, or an Error on mismatch.
|
|
@@ -363,6 +630,24 @@ export declare const crypto: {
|
|
|
363
630
|
hkdf_expand: (hash: string, prk: Uint8Array, info: Uint8Array, length: number) => Uint8Array;
|
|
364
631
|
hkdf_expand_label: (hash: string, secret: Uint8Array, label: string, context: Uint8Array, length: number) => Uint8Array;
|
|
365
632
|
hmac: (hash: string, key: Uint8Array, data: Uint8Array) => Uint8Array;
|
|
633
|
+
/** TLS 1.2 PRF (RFC 5246 §5). */
|
|
634
|
+
tls12_prf: (secret: Uint8Array, label: string, seed: Uint8Array, length: number, hash: string) => Uint8Array;
|
|
635
|
+
/** RFC 5705 exporter over a TLS 1.2 master secret. */
|
|
636
|
+
tls12_exporter: (hash: string, masterSecret: Uint8Array, label: string, clientRandom: Uint8Array,
|
|
637
|
+
serverRandom: Uint8Array, context: Uint8Array | null, length: number) => Uint8Array;
|
|
638
|
+
/** RFC 8446 §7.5 exporter; labelPrefix defaults to TLS 1.3's ('tls13 '). */
|
|
639
|
+
tls13_exporter: (hash: string, exporterMasterSecret: Uint8Array, label: string,
|
|
640
|
+
context: Uint8Array | null, length: number, labelPrefix?: string) => Uint8Array;
|
|
641
|
+
/** HPKE (RFC 9180) — what ECH encrypts with. */
|
|
642
|
+
hpke: {
|
|
643
|
+
suite: (...args: any[]) => any;
|
|
644
|
+
setupSender: (...args: any[]) => any;
|
|
645
|
+
setupRecipient: (...args: any[]) => any;
|
|
646
|
+
seal: (...args: any[]) => any;
|
|
647
|
+
open: (...args: any[]) => any;
|
|
648
|
+
generateKeyPair: (...args: any[]) => any;
|
|
649
|
+
deriveKeyPair: (...args: any[]) => any;
|
|
650
|
+
};
|
|
366
651
|
};
|
|
367
652
|
|
|
368
653
|
export declare const wire: any;
|
|
@@ -398,6 +683,9 @@ declare const _default: {
|
|
|
398
683
|
createServer: typeof createServer;
|
|
399
684
|
getCiphers: typeof getCiphers;
|
|
400
685
|
checkServerIdentity: typeof checkServerIdentity;
|
|
686
|
+
getCACertificates: typeof getCACertificates;
|
|
687
|
+
setDefaultCACertificates: typeof setDefaultCACertificates;
|
|
688
|
+
rootCertificates: typeof rootCertificates;
|
|
401
689
|
DEFAULT_MIN_VERSION: string;
|
|
402
690
|
DEFAULT_MAX_VERSION: string;
|
|
403
691
|
DEFAULT_CIPHERS: string;
|
|
@@ -405,10 +693,22 @@ declare const _default: {
|
|
|
405
693
|
crypto: typeof crypto;
|
|
406
694
|
wire: typeof wire;
|
|
407
695
|
record: typeof record;
|
|
696
|
+
parseCiphers: typeof parseCiphers;
|
|
697
|
+
parseEcdhCurve: typeof parseEcdhCurve;
|
|
698
|
+
parseSigalgs: typeof parseSigalgs;
|
|
699
|
+
parseTlsVersion: typeof parseTlsVersion;
|
|
700
|
+
parseCertificateCompression: typeof parseCertificateCompression;
|
|
701
|
+
precompressCertificate: typeof precompressCertificate;
|
|
702
|
+
DEFAULT_SERVER_GROUPS: typeof DEFAULT_SERVER_GROUPS;
|
|
703
|
+
DEFAULT_SIGNATURE_SCHEMES: typeof DEFAULT_SIGNATURE_SCHEMES;
|
|
704
|
+
sessionInfo: typeof sessionInfo;
|
|
705
|
+
certificateObject: typeof certificateObject;
|
|
706
|
+
generateEchKey: typeof generateEchKey;
|
|
707
|
+
checkEchKeys: typeof checkEchKeys;
|
|
408
708
|
DTLSSession: typeof DTLSSession;
|
|
409
709
|
DTLSSocket: typeof DTLSSocket;
|
|
410
710
|
createDTLSServer: typeof createDTLSServer;
|
|
411
711
|
connectDTLS: typeof connectDTLS;
|
|
412
712
|
};
|
|
413
713
|
|
|
414
|
-
export default _default;
|
|
714
|
+
export default _default;
|
package/index.js
CHANGED
|
@@ -21,11 +21,74 @@ import {
|
|
|
21
21
|
Server,
|
|
22
22
|
getCiphers,
|
|
23
23
|
checkServerIdentity,
|
|
24
|
+
getCACertificates,
|
|
25
|
+
setDefaultCACertificates,
|
|
24
26
|
DEFAULT_MIN_VERSION,
|
|
25
27
|
DEFAULT_MAX_VERSION,
|
|
26
28
|
DEFAULT_CIPHERS,
|
|
27
29
|
DEFAULT_ECDH_CURVE,
|
|
28
30
|
} from './src/compat.js';
|
|
31
|
+
import { parseCiphers, parseEcdhCurve, parseSigalgs, parseTlsVersion } from './src/node_options.js';
|
|
32
|
+
import { certificateObject } from './src/utils.js';
|
|
33
|
+
import { parse_cert_compression, precompress_certificate_body, supported_cert_compression } from './src/session/cert_compression.js';
|
|
34
|
+
import { build_tls_message } from './src/session/message.js';
|
|
35
|
+
import { client_session_info } from './src/session/ticket.js';
|
|
36
|
+
|
|
37
|
+
/** A saved session ('session' event's Buffer) described without its secrets:
|
|
38
|
+
* { version, servername, alpn, created, expiresAt, earlyData }, or null. */
|
|
39
|
+
function sessionInfo(blob) { return client_session_info(blob); }
|
|
40
|
+
import { SERVER_DEFAULT_GROUPS } from './src/session/ecdh.js';
|
|
41
|
+
import { default_signature_schemes } from './src/session/signing.js';
|
|
42
|
+
|
|
43
|
+
/** The signature schemes offered by default (signature_algorithms), in
|
|
44
|
+
* order, as code points: the modern ones, ML-DSA where this Node.js has it,
|
|
45
|
+
* then the legacy ones (accepted on certificates, never used in a TLS 1.3
|
|
46
|
+
* CertificateVerify). A copy. */
|
|
47
|
+
const DEFAULT_SIGNATURE_SCHEMES = Object.freeze(default_signature_schemes());
|
|
48
|
+
|
|
49
|
+
/** The key-exchange groups a server offers by default, in its preference:
|
|
50
|
+
* every post-quantum group this Node.js has (the hybrids, then ML-KEM
|
|
51
|
+
* alone), then X25519, P-256, P-384 — as code points. A copy. */
|
|
52
|
+
const DEFAULT_SERVER_GROUPS = Object.freeze(SERVER_DEFAULT_GROUPS.slice());
|
|
53
|
+
|
|
54
|
+
/** certificateCompression (RFC 8879): ['brotli', 'zlib', 'zstd'] in order,
|
|
55
|
+
* true (all this build has) or false (none) → algorithm codes; null when
|
|
56
|
+
* unset (the default). Throws on a name it does not know, so a server can
|
|
57
|
+
* check the option when it is made rather than at the first handshake. */
|
|
58
|
+
function parseCertificateCompression(value) { return parse_cert_compression(value); }
|
|
59
|
+
|
|
60
|
+
/** A server's certificate chain compressed now (RFC 8879), in the
|
|
61
|
+
* background, for each algorithm it may be sent in — `algorithms` as the
|
|
62
|
+
* certificateCompression option takes them (unset: all this build has) —
|
|
63
|
+
* so the first TLS 1.3 handshake to send it does not compress it while
|
|
64
|
+
* its client waits. `secureContext`: from createSecureContext(). Optional:
|
|
65
|
+
* without it the first handshake compresses, and caches, as before.
|
|
66
|
+
* callback() when done. */
|
|
67
|
+
function precompressCertificate(secureContext, algorithms, callback) {
|
|
68
|
+
if (typeof algorithms === 'function') { callback = algorithms; algorithms = undefined; }
|
|
69
|
+
const algs = parse_cert_compression(algorithms) || supported_cert_compression();
|
|
70
|
+
// Its chain, and those of its other key/certificate pairs (key/cert arrays).
|
|
71
|
+
const chains = secureContext ? [secureContext.certificateChain].concat((secureContext.alternates || []).map((a) => a.certificateChain)) : [];
|
|
72
|
+
let left = 1;
|
|
73
|
+
const done = () => { if (--left === 0 && callback) callback(); };
|
|
74
|
+
for (const chain of chains) {
|
|
75
|
+
if (!Array.isArray(chain) || chain.length === 0 || algs.length === 0) continue;
|
|
76
|
+
// The Certificate message's body as a TLS 1.3 server sends it: an empty
|
|
77
|
+
// request context, each entry without extensions (an OCSP response
|
|
78
|
+
// stapled to the leaf makes another body, compressed when it is sent).
|
|
79
|
+
const body = build_tls_message({ type: 'certificate', version: 0x0304, entries: chain, certificate_request_context: new Uint8Array(0) }).subarray(4);
|
|
80
|
+
left++;
|
|
81
|
+
precompress_certificate_body(body, algs, done);
|
|
82
|
+
}
|
|
83
|
+
process.nextTick(done);
|
|
84
|
+
}
|
|
85
|
+
import { generate_ech_key, prepare_ech_server } from './src/session/ech.js';
|
|
86
|
+
import { hpke_suite, setup_sender, setup_recipient, hpke_seal, hpke_open,
|
|
87
|
+
hpke_generate_key_pair, hpke_derive_key_pair } from './src/session/hpke.js';
|
|
88
|
+
import nodeTls from 'node:tls';
|
|
89
|
+
|
|
90
|
+
/** Node-compat: the shipped CA bundle, same array Node exposes. */
|
|
91
|
+
const rootCertificates = nodeTls.rootCertificates;
|
|
29
92
|
|
|
30
93
|
// DTLS
|
|
31
94
|
import DTLSSession from './src/dtls_session.js';
|
|
@@ -53,8 +116,40 @@ var crypto = {
|
|
|
53
116
|
tls12_prf,
|
|
54
117
|
tls12_exporter,
|
|
55
118
|
tls13_exporter,
|
|
119
|
+
// HPKE (RFC 9180) — what ECH encrypts with; also the base of Oblivious
|
|
120
|
+
// HTTP and MLS.
|
|
121
|
+
hpke: {
|
|
122
|
+
suite: hpke_suite,
|
|
123
|
+
setupSender: setup_sender,
|
|
124
|
+
setupRecipient: setup_recipient,
|
|
125
|
+
seal: hpke_seal,
|
|
126
|
+
open: hpke_open,
|
|
127
|
+
generateKeyPair: hpke_generate_key_pair,
|
|
128
|
+
deriveKeyPair: hpke_derive_key_pair,
|
|
129
|
+
},
|
|
56
130
|
};
|
|
57
131
|
|
|
132
|
+
/**
|
|
133
|
+
* A new Encrypted Client Hello key (RFC 9849) for a server:
|
|
134
|
+
* { config, privateKey, configList, configListBase64 }.
|
|
135
|
+
* Pass { keys: [{ config, privateKey }] } as the server's `ech` option and
|
|
136
|
+
* publish configListBase64 as `ech=` in the domain's HTTPS record.
|
|
137
|
+
*/
|
|
138
|
+
function generateEchKey(options) {
|
|
139
|
+
return generate_ech_key(options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check a server's `ech` option — { keys: [{ config, privateKey, retry }] }
|
|
144
|
+
* — and throw, saying what is wrong, if it would not work: a config that
|
|
145
|
+
* does not parse, a private key that is not its config's. For a server
|
|
146
|
+
* built on TLSSession directly, which would otherwise learn of it on the
|
|
147
|
+
* first connection. createServer checks by itself.
|
|
148
|
+
*/
|
|
149
|
+
function checkEchKeys(ech) {
|
|
150
|
+
prepare_ech_server(ech);
|
|
151
|
+
}
|
|
152
|
+
|
|
58
153
|
export {
|
|
59
154
|
TLSSocket,
|
|
60
155
|
TLSSession,
|
|
@@ -64,6 +159,9 @@ export {
|
|
|
64
159
|
Server,
|
|
65
160
|
getCiphers,
|
|
66
161
|
checkServerIdentity,
|
|
162
|
+
getCACertificates,
|
|
163
|
+
setDefaultCACertificates,
|
|
164
|
+
rootCertificates,
|
|
67
165
|
DEFAULT_MIN_VERSION,
|
|
68
166
|
DEFAULT_MAX_VERSION,
|
|
69
167
|
DEFAULT_CIPHERS,
|
|
@@ -71,6 +169,19 @@ export {
|
|
|
71
169
|
crypto,
|
|
72
170
|
wire,
|
|
73
171
|
record,
|
|
172
|
+
// node:tls option strings → code lists (node_options.js)
|
|
173
|
+
parseCiphers,
|
|
174
|
+
parseEcdhCurve,
|
|
175
|
+
parseSigalgs,
|
|
176
|
+
parseTlsVersion,
|
|
177
|
+
parseCertificateCompression,
|
|
178
|
+
precompressCertificate,
|
|
179
|
+
DEFAULT_SERVER_GROUPS,
|
|
180
|
+
DEFAULT_SIGNATURE_SCHEMES,
|
|
181
|
+
sessionInfo,
|
|
182
|
+
certificateObject,
|
|
183
|
+
generateEchKey,
|
|
184
|
+
checkEchKeys,
|
|
74
185
|
|
|
75
186
|
// DTLS
|
|
76
187
|
DTLSSession,
|
|
@@ -91,6 +202,9 @@ export default {
|
|
|
91
202
|
Server,
|
|
92
203
|
getCiphers,
|
|
93
204
|
checkServerIdentity,
|
|
205
|
+
getCACertificates,
|
|
206
|
+
setDefaultCACertificates,
|
|
207
|
+
rootCertificates,
|
|
94
208
|
DEFAULT_MIN_VERSION,
|
|
95
209
|
DEFAULT_MAX_VERSION,
|
|
96
210
|
DEFAULT_CIPHERS,
|
|
@@ -98,6 +212,18 @@ export default {
|
|
|
98
212
|
crypto,
|
|
99
213
|
wire,
|
|
100
214
|
record,
|
|
215
|
+
parseCiphers,
|
|
216
|
+
parseEcdhCurve,
|
|
217
|
+
parseSigalgs,
|
|
218
|
+
parseTlsVersion,
|
|
219
|
+
parseCertificateCompression,
|
|
220
|
+
precompressCertificate,
|
|
221
|
+
DEFAULT_SERVER_GROUPS,
|
|
222
|
+
DEFAULT_SIGNATURE_SCHEMES,
|
|
223
|
+
sessionInfo,
|
|
224
|
+
certificateObject,
|
|
225
|
+
generateEchKey,
|
|
226
|
+
checkEchKeys,
|
|
101
227
|
|
|
102
228
|
// DTLS
|
|
103
229
|
DTLSSession,
|