lemon-tls 0.2.1 → 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/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: string;
22
- issuer: string;
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
- session?: SessionTicketData;
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
- // LemonTLS-only
133
- getSession(): TLSSession;
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
- on(event: 'session', listener: (data: SessionTicketData) => void): this;
145
- on(event: 'keyUpdate', listener: (direction: 'send' | 'receive') => void): this;
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
- on(event: 'session', listener: (data: SessionTicketData) => void): this;
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
- export function createServer(options: TLSSocketOptions & { key?: Buffer | string; cert?: Buffer | string }, connectionListener?: (socket: TLSSocket) => void): NetServer;
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,11 +15,23 @@ 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
 
27
+ // DTLS
28
+ import DTLSSession from './src/dtls_session.js';
29
+ import {
30
+ DTLSSocket,
31
+ createDTLSServer,
32
+ connectDTLS,
33
+ } from './src/dtls_socket.js';
34
+
23
35
  /**
24
36
  * Crypto primitives for QUIC and custom transport consumers.
25
37
  */
@@ -39,21 +51,26 @@ export {
39
51
  createSecureContext,
40
52
  connect,
41
53
  createServer,
54
+ Server,
42
55
  getCiphers,
56
+ checkServerIdentity,
43
57
  DEFAULT_MIN_VERSION,
44
58
  DEFAULT_MAX_VERSION,
59
+ DEFAULT_CIPHERS,
60
+ DEFAULT_ECDH_CURVE,
45
61
  crypto,
46
62
  wire,
47
63
  record,
64
+
65
+ // DTLS
66
+ DTLSSession,
67
+ DTLSSocket,
68
+ createDTLSServer,
69
+ connectDTLS,
48
70
  };
49
71
 
50
72
  /**
51
- * Default export — Node.js tls API compatible.
52
- *
53
- * Usage:
54
- * import tls from 'lemon-tls';
55
- * tls.connect(443, 'example.com', { ... });
56
- * tls.createServer({ key, cert }, (socket) => { ... });
73
+ * Default export — Node.js tls API compatible + DTLS.
57
74
  */
58
75
  export default {
59
76
  TLSSocket,
@@ -61,10 +78,20 @@ export default {
61
78
  createSecureContext,
62
79
  connect,
63
80
  createServer,
81
+ Server,
64
82
  getCiphers,
83
+ checkServerIdentity,
65
84
  DEFAULT_MIN_VERSION,
66
85
  DEFAULT_MAX_VERSION,
86
+ DEFAULT_CIPHERS,
87
+ DEFAULT_ECDH_CURVE,
67
88
  crypto,
68
89
  wire,
69
90
  record,
91
+
92
+ // DTLS
93
+ DTLSSession,
94
+ DTLSSocket,
95
+ createDTLSServer,
96
+ connectDTLS,
70
97
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lemon-tls",
3
- "version": "0.2.1",
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.",
3
+ "version": "0.3.0",
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",
7
7
  "types": "index.d.ts",
@@ -17,12 +17,6 @@
17
17
  "./record": "./src/record.js",
18
18
  "./session": "./src/tls_session.js"
19
19
  },
20
- "scripts": {
21
- "test": "node tests/test_all.js",
22
- "test:https": "node tests/test_https.js",
23
- "test:compat": "node tests/test_compat.js",
24
- "test:all": "node tests/test_all.js && node tests/test_compat.js"
25
- },
26
20
  "files": [
27
21
  "index.js",
28
22
  "index.cjs",
@@ -115,6 +109,5 @@
115
109
  "type": "buymeacoffee",
116
110
  "url": "https://buymeacoffee.com/colocohen"
117
111
  }
118
- ],
119
- "dependencies": {}
112
+ ]
120
113
  }