lemon-tls 0.2.2 → 0.3.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 -203
- package/index.d.ts +145 -14
- package/index.js +12 -0
- package/package.json +1 -1
- package/src/compat.js +290 -31
- package/src/crypto.js +127 -7
- package/src/record.js +408 -61
- package/src/session/message.js +27 -2
- package/src/session/ticket.js +185 -0
- package/src/tls_session.js +780 -94
- package/src/tls_socket.js +815 -249
- package/src/wire.js +25 -0
package/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Duplex } from 'node:stream';
|
|
4
4
|
import { EventEmitter } from 'node:events';
|
|
5
5
|
import type { Server as NetServer } from 'node:net';
|
|
6
|
+
import type { X509Certificate } from 'node:crypto';
|
|
6
7
|
|
|
7
8
|
// ======================== Types ========================
|
|
8
9
|
|
|
@@ -18,8 +19,8 @@ export interface CipherInfo {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface PeerCertificate {
|
|
21
|
-
subject:
|
|
22
|
-
issuer:
|
|
22
|
+
subject: any;
|
|
23
|
+
issuer: any;
|
|
23
24
|
subjectaltname?: string;
|
|
24
25
|
valid_from: string;
|
|
25
26
|
valid_to: string;
|
|
@@ -55,6 +56,10 @@ export interface NegotiationResult {
|
|
|
55
56
|
handshakeDuration: number | null;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Payload of the 'session' event on TLSSession (raw internal form).
|
|
61
|
+
* The TLSSocket-level 'session' event emits an opaque Buffer (Node-compatible).
|
|
62
|
+
*/
|
|
58
63
|
export interface SessionTicketData {
|
|
59
64
|
ticket: Buffer;
|
|
60
65
|
ticket_nonce: Buffer;
|
|
@@ -86,13 +91,19 @@ export interface TLSSocketOptions {
|
|
|
86
91
|
rejectUnauthorized?: boolean;
|
|
87
92
|
ca?: Buffer | string;
|
|
88
93
|
ticketKeys?: Buffer;
|
|
89
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Opaque serialized session from a previous 'session' event.
|
|
96
|
+
* Pass this back to resume a TLS 1.2/1.3 session.
|
|
97
|
+
*/
|
|
98
|
+
session?: Buffer | Uint8Array;
|
|
90
99
|
requestCert?: boolean;
|
|
91
100
|
cert?: Buffer | string;
|
|
92
101
|
key?: Buffer | string;
|
|
93
102
|
|
|
94
103
|
// LemonTLS-only options
|
|
95
104
|
noTickets?: boolean;
|
|
105
|
+
sessionTickets?: boolean;
|
|
106
|
+
ticketLifetime?: number;
|
|
96
107
|
signatureAlgorithms?: number[];
|
|
97
108
|
groups?: number[];
|
|
98
109
|
prioritizeChaCha?: boolean;
|
|
@@ -110,10 +121,16 @@ export interface TLSSocketOptions {
|
|
|
110
121
|
export class TLSSocket extends Duplex {
|
|
111
122
|
constructor(transport: Duplex | null, options: TLSSocketOptions);
|
|
112
123
|
|
|
113
|
-
// Node.js compatible
|
|
124
|
+
// ------- Node.js tls.TLSSocket compatible API -------
|
|
114
125
|
getProtocol(): string | null;
|
|
115
126
|
getCipher(): CipherInfo | null;
|
|
116
127
|
getPeerCertificate(): PeerCertificate | null;
|
|
128
|
+
/** Returns our LOCAL cert (what we presented). Empty object if none. */
|
|
129
|
+
getCertificate(): PeerCertificate | {};
|
|
130
|
+
/** Returns the peer cert as a native crypto.X509Certificate object (Node 15.9+). */
|
|
131
|
+
getPeerX509Certificate(): X509Certificate | undefined;
|
|
132
|
+
/** Returns our LOCAL cert as a native crypto.X509Certificate object. */
|
|
133
|
+
getX509Certificate(): X509Certificate | undefined;
|
|
117
134
|
isSessionReused(): boolean;
|
|
118
135
|
getFinished(): Buffer | null;
|
|
119
136
|
getPeerFinished(): Buffer | null;
|
|
@@ -121,16 +138,46 @@ export class TLSSocket extends Duplex {
|
|
|
121
138
|
getEphemeralKeyInfo(): EphemeralKeyInfo;
|
|
122
139
|
disableRenegotiation(): void;
|
|
123
140
|
setServername(name: string): void;
|
|
141
|
+
/** Returns the opaque serialized session Buffer, or undefined. */
|
|
142
|
+
getSession(): Buffer | undefined;
|
|
143
|
+
/** Returns the TLS 1.2 session ticket as a Buffer, or undefined (incl. for TLS 1.3). */
|
|
144
|
+
getTLSTicket(): Buffer | undefined;
|
|
145
|
+
/** Returns signature algorithm names shared between client and server (server side). */
|
|
146
|
+
getSharedSigalgs(): string[];
|
|
147
|
+
/** Caps outgoing plaintext fragment size. Must be in [512, 16384]. */
|
|
148
|
+
setMaxSendFragment(size: number): boolean;
|
|
149
|
+
/** Node-compat no-op (we don't wrap OpenSSL). Use 'keylog' / 'handshakeMessage' for insight. */
|
|
150
|
+
enableTrace(): void;
|
|
124
151
|
setSocket(transport: Duplex): void;
|
|
125
152
|
|
|
126
|
-
readonly isResumed: boolean;
|
|
127
153
|
readonly authorized: boolean;
|
|
128
154
|
readonly authorizationError: string | null;
|
|
155
|
+
/** The negotiated ALPN protocol string (e.g. 'h2'), or false. */
|
|
129
156
|
readonly alpnProtocol: string | false;
|
|
157
|
+
/** Always true for TLSSocket. */
|
|
130
158
|
readonly encrypted: boolean;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
/** SNI value — on server, the name the client sent; on client, the name we sent. */
|
|
160
|
+
readonly servername: string | false;
|
|
161
|
+
|
|
162
|
+
// net.Socket compat (delegated to underlying transport)
|
|
163
|
+
readonly remoteAddress: string | undefined;
|
|
164
|
+
readonly remotePort: number | undefined;
|
|
165
|
+
readonly remoteFamily: string | undefined;
|
|
166
|
+
readonly localAddress: string | undefined;
|
|
167
|
+
readonly localPort: number | undefined;
|
|
168
|
+
readonly localFamily: string | undefined;
|
|
169
|
+
readonly bytesRead: number;
|
|
170
|
+
readonly bytesWritten: number;
|
|
171
|
+
setNoDelay(noDelay?: boolean): this;
|
|
172
|
+
setKeepAlive(enable?: boolean, initialDelay?: number): this;
|
|
173
|
+
setTimeout(timeout: number, callback?: () => void): this;
|
|
174
|
+
ref(): this;
|
|
175
|
+
unref(): this;
|
|
176
|
+
|
|
177
|
+
// ------- LemonTLS-only extensions -------
|
|
178
|
+
readonly isResumed: boolean;
|
|
179
|
+
/** Returns the underlying TLSSession for low-level access. */
|
|
180
|
+
session: TLSSession;
|
|
134
181
|
readonly handshakeDuration: number | null;
|
|
135
182
|
getJA3(): JA3Result | null;
|
|
136
183
|
getSharedSecret(): Buffer | null;
|
|
@@ -138,15 +185,54 @@ export class TLSSocket extends Duplex {
|
|
|
138
185
|
rekeySend(): void;
|
|
139
186
|
rekeyBoth(): void;
|
|
140
187
|
|
|
141
|
-
// Events
|
|
188
|
+
// ------- Events -------
|
|
142
189
|
on(event: 'secureConnect', listener: () => void): this;
|
|
143
190
|
on(event: 'data', listener: (data: Buffer) => void): this;
|
|
144
|
-
|
|
145
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Emits an opaque Buffer (Node-compatible). Pass it back into tls.connect({ session })
|
|
193
|
+
* to resume. The Buffer is our internal encoded session blob.
|
|
194
|
+
*/
|
|
195
|
+
on(event: 'session', listener: (data: Buffer) => void): this;
|
|
146
196
|
on(event: 'keylog', listener: (line: Buffer) => void): this;
|
|
197
|
+
on(event: 'keyUpdate', listener: (direction: 'send' | 'receive') => void): this;
|
|
147
198
|
on(event: 'clienthello', listener: (raw: Buffer, parsed: any) => void): this;
|
|
148
199
|
on(event: 'handshakeMessage', listener: (type: string, raw: Buffer, parsed: any) => void): this;
|
|
149
200
|
on(event: 'certificateRequest', listener: (msg: any) => void): this;
|
|
201
|
+
on(event: 'newSession', listener: (id: Buffer, data: Buffer, cb: () => void) => void): this;
|
|
202
|
+
on(event: 'resumeSession', listener: (id: Buffer, cb: (err: Error | null, data: Buffer | null) => void) => void): this;
|
|
203
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
204
|
+
on(event: 'close', listener: () => void): this;
|
|
205
|
+
on(event: string, listener: (...args: any[]) => void): this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ======================== Server ========================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Node.js tls.Server compatible server. Returned by createServer().
|
|
212
|
+
* Extends EventEmitter and wraps a net.Server internally.
|
|
213
|
+
*/
|
|
214
|
+
export class Server extends EventEmitter {
|
|
215
|
+
listen(port: number, host?: string, callback?: () => void): this;
|
|
216
|
+
listen(port: number, callback?: () => void): this;
|
|
217
|
+
listen(options: { port?: number; host?: string }, callback?: () => void): this;
|
|
218
|
+
close(callback?: (err?: Error) => void): this;
|
|
219
|
+
address(): { port: number; family: string; address: string } | string | null;
|
|
220
|
+
|
|
221
|
+
/** Replace the server's key/cert at runtime (Let's Encrypt renewals, etc.). */
|
|
222
|
+
setSecureContext(options: { key: Buffer | string; cert: Buffer | string }): void;
|
|
223
|
+
/** Returns the 48-byte TLS session ticket encryption keys. */
|
|
224
|
+
getTicketKeys(): Buffer;
|
|
225
|
+
/** Sets the 48-byte TLS session ticket encryption keys (for clustered deployments). */
|
|
226
|
+
setTicketKeys(keys: Buffer): void;
|
|
227
|
+
|
|
228
|
+
readonly listening: boolean;
|
|
229
|
+
|
|
230
|
+
// Events
|
|
231
|
+
on(event: 'secureConnection', listener: (socket: TLSSocket) => void): this;
|
|
232
|
+
on(event: 'tlsClientError', listener: (err: Error, socket: TLSSocket | null) => void): this;
|
|
233
|
+
on(event: 'keylog', listener: (line: Buffer, socket: TLSSocket) => void): this;
|
|
234
|
+
on(event: 'newSession', listener: (id: Buffer, data: Buffer, cb: () => void) => void): this;
|
|
235
|
+
on(event: 'resumeSession', listener: (id: Buffer, cb: (err: Error | null, data: Buffer | null) => void) => void): this;
|
|
150
236
|
on(event: 'error', listener: (err: Error) => void): this;
|
|
151
237
|
on(event: 'close', listener: () => void): this;
|
|
152
238
|
on(event: string, listener: (...args: any[]) => void): this;
|
|
@@ -160,11 +246,12 @@ export interface TLSSessionOptions {
|
|
|
160
246
|
ALPNProtocols?: string[];
|
|
161
247
|
SNICallback?: (servername: string, cb: (err: Error | null, ctx: SecureContext | null) => void) => void;
|
|
162
248
|
ticketKeys?: Buffer;
|
|
163
|
-
session?: SessionTicketData;
|
|
249
|
+
session?: Buffer | Uint8Array | SessionTicketData;
|
|
164
250
|
psk?: any;
|
|
165
251
|
rejectUnauthorized?: boolean;
|
|
166
252
|
ca?: Buffer | string;
|
|
167
253
|
noTickets?: boolean;
|
|
254
|
+
sessionTickets?: boolean;
|
|
168
255
|
maxHandshakeSize?: number;
|
|
169
256
|
customExtensions?: Array<{ type: number; data: Uint8Array }>;
|
|
170
257
|
requestCert?: boolean;
|
|
@@ -222,12 +309,16 @@ export class TLSSession extends EventEmitter {
|
|
|
222
309
|
on(event: 'message', listener: (epoch: number, seq: number, type: string, data: Uint8Array) => void): this;
|
|
223
310
|
on(event: 'hello', listener: () => void): this;
|
|
224
311
|
on(event: 'secureConnect', listener: () => void): this;
|
|
225
|
-
|
|
312
|
+
/** Emits the raw internal session blob (Buffer/Uint8Array — encoded session state). */
|
|
313
|
+
on(event: 'session', listener: (data: Buffer) => void): this;
|
|
226
314
|
on(event: 'psk', listener: (identity: Buffer, callback: (result: { psk: Buffer; cipher: number } | null) => void) => void): this;
|
|
227
315
|
on(event: 'keyUpdate', listener: (info: { direction: 'send' | 'receive'; secret: Uint8Array }) => void): this;
|
|
316
|
+
on(event: 'keylog', listener: (line: Buffer) => void): this;
|
|
228
317
|
on(event: 'clienthello', listener: (raw: Buffer, parsed: any) => void): this;
|
|
229
318
|
on(event: 'handshakeMessage', listener: (type: string, raw: Buffer, parsed: any) => void): this;
|
|
230
319
|
on(event: 'certificateRequest', listener: (msg: any) => void): this;
|
|
320
|
+
on(event: 'newSession', listener: (id: Uint8Array, data: Uint8Array, cb: () => void) => void): this;
|
|
321
|
+
on(event: 'resumeSession', listener: (id: Uint8Array, cb: (err: Error | null, data: Uint8Array | null) => void) => void): this;
|
|
231
322
|
on(event: 'error', listener: (err: Error) => void): this;
|
|
232
323
|
on(event: string, listener: (...args: any[]) => void): this;
|
|
233
324
|
}
|
|
@@ -235,13 +326,32 @@ export class TLSSession extends EventEmitter {
|
|
|
235
326
|
// ======================== Module Functions ========================
|
|
236
327
|
|
|
237
328
|
export function createSecureContext(options: { key: Buffer | string; cert: Buffer | string }): SecureContext;
|
|
329
|
+
|
|
238
330
|
export function connect(port: number, host: string, options?: TLSSocketOptions, callback?: () => void): TLSSocket;
|
|
331
|
+
export function connect(port: number, host: string, callback?: () => void): TLSSocket;
|
|
239
332
|
export function connect(options: TLSSocketOptions & { port: number; host?: string }, callback?: () => void): TLSSocket;
|
|
240
|
-
|
|
333
|
+
|
|
334
|
+
export function createServer(
|
|
335
|
+
options: TLSSocketOptions & { key?: Buffer | string; cert?: Buffer | string },
|
|
336
|
+
connectionListener?: (socket: TLSSocket) => void
|
|
337
|
+
): Server;
|
|
338
|
+
|
|
241
339
|
export function getCiphers(): string[];
|
|
242
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Validates that the peer certificate matches the hostname (RFC 6125).
|
|
343
|
+
* Returns undefined on success, or an Error on mismatch.
|
|
344
|
+
* This is the default check used when rejectUnauthorized is true; apps can
|
|
345
|
+
* override it via the checkServerIdentity option in tls.connect.
|
|
346
|
+
*/
|
|
347
|
+
export function checkServerIdentity(hostname: string, cert: PeerCertificate): Error | undefined;
|
|
348
|
+
|
|
243
349
|
export const DEFAULT_MIN_VERSION: string;
|
|
244
350
|
export const DEFAULT_MAX_VERSION: string;
|
|
351
|
+
/** Node-compat: default colon-separated cipher string. */
|
|
352
|
+
export const DEFAULT_CIPHERS: string;
|
|
353
|
+
/** Node-compat: default ECDH curve selection policy ('auto'). */
|
|
354
|
+
export const DEFAULT_ECDH_CURVE: string;
|
|
245
355
|
|
|
246
356
|
// ======================== Submodules ========================
|
|
247
357
|
|
|
@@ -264,20 +374,41 @@ export declare const record: {
|
|
|
264
374
|
decryptRecord: (ciphertext: Uint8Array, key: Uint8Array, nonce: Uint8Array, algo: string) => Uint8Array;
|
|
265
375
|
};
|
|
266
376
|
|
|
377
|
+
// ======================== DTLS ========================
|
|
378
|
+
// DTLS bindings are exported from the package but typed loosely here —
|
|
379
|
+
// use the JS source directly for exact signatures.
|
|
380
|
+
|
|
381
|
+
export declare class DTLSSession extends EventEmitter {
|
|
382
|
+
constructor(options: any);
|
|
383
|
+
}
|
|
384
|
+
export declare class DTLSSocket extends EventEmitter {
|
|
385
|
+
constructor(options: any);
|
|
386
|
+
}
|
|
387
|
+
export declare function createDTLSServer(options: any, connectionListener?: (socket: DTLSSocket) => void): any;
|
|
388
|
+
export declare function connectDTLS(options: any, callback?: () => void): DTLSSocket;
|
|
389
|
+
|
|
267
390
|
// ======================== Default Export ========================
|
|
268
391
|
|
|
269
392
|
declare const _default: {
|
|
270
393
|
TLSSocket: typeof TLSSocket;
|
|
271
394
|
TLSSession: typeof TLSSession;
|
|
395
|
+
Server: typeof Server;
|
|
272
396
|
createSecureContext: typeof createSecureContext;
|
|
273
397
|
connect: typeof connect;
|
|
274
398
|
createServer: typeof createServer;
|
|
275
399
|
getCiphers: typeof getCiphers;
|
|
400
|
+
checkServerIdentity: typeof checkServerIdentity;
|
|
276
401
|
DEFAULT_MIN_VERSION: string;
|
|
277
402
|
DEFAULT_MAX_VERSION: string;
|
|
403
|
+
DEFAULT_CIPHERS: string;
|
|
404
|
+
DEFAULT_ECDH_CURVE: string;
|
|
278
405
|
crypto: typeof crypto;
|
|
279
406
|
wire: typeof wire;
|
|
280
407
|
record: typeof record;
|
|
408
|
+
DTLSSession: typeof DTLSSession;
|
|
409
|
+
DTLSSocket: typeof DTLSSocket;
|
|
410
|
+
createDTLSServer: typeof createDTLSServer;
|
|
411
|
+
connectDTLS: typeof connectDTLS;
|
|
281
412
|
};
|
|
282
413
|
|
|
283
414
|
export default _default;
|
package/index.js
CHANGED
|
@@ -15,9 +15,13 @@ import * as record from './src/record.js';
|
|
|
15
15
|
import {
|
|
16
16
|
connect,
|
|
17
17
|
createServer,
|
|
18
|
+
Server,
|
|
18
19
|
getCiphers,
|
|
20
|
+
checkServerIdentity,
|
|
19
21
|
DEFAULT_MIN_VERSION,
|
|
20
22
|
DEFAULT_MAX_VERSION,
|
|
23
|
+
DEFAULT_CIPHERS,
|
|
24
|
+
DEFAULT_ECDH_CURVE,
|
|
21
25
|
} from './src/compat.js';
|
|
22
26
|
|
|
23
27
|
// DTLS
|
|
@@ -47,9 +51,13 @@ export {
|
|
|
47
51
|
createSecureContext,
|
|
48
52
|
connect,
|
|
49
53
|
createServer,
|
|
54
|
+
Server,
|
|
50
55
|
getCiphers,
|
|
56
|
+
checkServerIdentity,
|
|
51
57
|
DEFAULT_MIN_VERSION,
|
|
52
58
|
DEFAULT_MAX_VERSION,
|
|
59
|
+
DEFAULT_CIPHERS,
|
|
60
|
+
DEFAULT_ECDH_CURVE,
|
|
53
61
|
crypto,
|
|
54
62
|
wire,
|
|
55
63
|
record,
|
|
@@ -70,9 +78,13 @@ export default {
|
|
|
70
78
|
createSecureContext,
|
|
71
79
|
connect,
|
|
72
80
|
createServer,
|
|
81
|
+
Server,
|
|
73
82
|
getCiphers,
|
|
83
|
+
checkServerIdentity,
|
|
74
84
|
DEFAULT_MIN_VERSION,
|
|
75
85
|
DEFAULT_MAX_VERSION,
|
|
86
|
+
DEFAULT_CIPHERS,
|
|
87
|
+
DEFAULT_ECDH_CURVE,
|
|
76
88
|
crypto,
|
|
77
89
|
wire,
|
|
78
90
|
record,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemon-tls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Zero-dependency 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",
|