lemon-tls 0.1.0 → 0.2.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 +432 -122
- package/index.cjs +28 -0
- package/index.d.ts +283 -0
- package/index.js +70 -15
- package/lemontls.svg +1 -1
- package/package.json +120 -62
- package/src/compat.js +235 -0
- package/src/crypto.js +580 -0
- package/src/record.js +232 -0
- package/{secure_context.js → src/secure_context.js} +196 -196
- package/src/session/ecdh.js +61 -0
- package/src/session/message.js +203 -0
- package/src/session/signing.js +129 -0
- package/src/tls_session.js +2204 -0
- package/src/tls_socket.js +877 -0
- package/{utils.js → src/utils.js} +100 -87
- package/{wire.js → src/wire.js} +1499 -1672
- package/crypto.js +0 -383
- package/tls_server.js +0 -0
- package/tls_session.js +0 -1441
- package/tls_socket.js +0 -456
package/src/compat.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compat.js — Node.js tls module API compatibility layer.
|
|
3
|
+
*
|
|
4
|
+
* Provides tls.connect(), tls.createServer(), and additional
|
|
5
|
+
* TLSSocket methods to match Node.js tls API conventions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import net from 'node:net';
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import { TLS_CIPHER_SUITES } from './crypto.js';
|
|
11
|
+
import TLSSession from './tls_session.js';
|
|
12
|
+
import TLSSocket from './tls_socket.js';
|
|
13
|
+
import createSecureContext from './secure_context.js';
|
|
14
|
+
|
|
15
|
+
// ===================== Constants =====================
|
|
16
|
+
|
|
17
|
+
const DEFAULT_MIN_VERSION = 'TLSv1.2';
|
|
18
|
+
const DEFAULT_MAX_VERSION = 'TLSv1.3';
|
|
19
|
+
|
|
20
|
+
// ===================== tls.getCiphers() =====================
|
|
21
|
+
|
|
22
|
+
/** Returns array of supported cipher names (lowercase, OpenSSL-style). */
|
|
23
|
+
function getCiphers() {
|
|
24
|
+
let out = [];
|
|
25
|
+
for (let code in TLS_CIPHER_SUITES) {
|
|
26
|
+
let info = TLS_CIPHER_SUITES[code];
|
|
27
|
+
if (info.name) out.push(info.name.toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ===================== tls.connect() =====================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Node.js compatible tls.connect().
|
|
36
|
+
*
|
|
37
|
+
* Usage:
|
|
38
|
+
* tls.connect(port, host, options, callback)
|
|
39
|
+
* tls.connect(port, host, callback)
|
|
40
|
+
* tls.connect(port, options, callback)
|
|
41
|
+
* tls.connect(options, callback)
|
|
42
|
+
*/
|
|
43
|
+
function connect(/* ...args */) {
|
|
44
|
+
let port, host, options, connectListener;
|
|
45
|
+
|
|
46
|
+
// Parse overloaded arguments
|
|
47
|
+
let args = Array.from(arguments);
|
|
48
|
+
|
|
49
|
+
if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
|
|
50
|
+
options = args[0];
|
|
51
|
+
connectListener = typeof args[1] === 'function' ? args[1] : null;
|
|
52
|
+
port = options.port;
|
|
53
|
+
host = options.host || 'localhost';
|
|
54
|
+
} else {
|
|
55
|
+
port = args[0];
|
|
56
|
+
if (typeof args[1] === 'string') {
|
|
57
|
+
host = args[1];
|
|
58
|
+
if (typeof args[2] === 'object') {
|
|
59
|
+
options = args[2];
|
|
60
|
+
connectListener = typeof args[3] === 'function' ? args[3] : null;
|
|
61
|
+
} else {
|
|
62
|
+
options = {};
|
|
63
|
+
connectListener = typeof args[2] === 'function' ? args[2] : null;
|
|
64
|
+
}
|
|
65
|
+
} else if (typeof args[1] === 'object') {
|
|
66
|
+
options = args[1];
|
|
67
|
+
host = options.host || 'localhost';
|
|
68
|
+
connectListener = typeof args[2] === 'function' ? args[2] : null;
|
|
69
|
+
} else {
|
|
70
|
+
options = {};
|
|
71
|
+
host = 'localhost';
|
|
72
|
+
connectListener = typeof args[1] === 'function' ? args[1] : null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
options = options || {};
|
|
77
|
+
|
|
78
|
+
let socket = new TLSSocket(null, {
|
|
79
|
+
isServer: false,
|
|
80
|
+
servername: options.servername || host,
|
|
81
|
+
rejectUnauthorized: options.rejectUnauthorized,
|
|
82
|
+
ca: options.ca,
|
|
83
|
+
session: options.session,
|
|
84
|
+
ALPNProtocols: options.ALPNProtocols,
|
|
85
|
+
minVersion: options.minVersion,
|
|
86
|
+
maxVersion: options.maxVersion,
|
|
87
|
+
signatureAlgorithms: options.signatureAlgorithms,
|
|
88
|
+
groups: options.groups,
|
|
89
|
+
prioritizeChaCha: options.prioritizeChaCha,
|
|
90
|
+
maxRecordSize: options.maxRecordSize,
|
|
91
|
+
noTickets: options.noTickets,
|
|
92
|
+
cert: options.cert,
|
|
93
|
+
key: options.key,
|
|
94
|
+
maxHandshakeSize: options.maxHandshakeSize,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
addCompatMethods(socket);
|
|
98
|
+
|
|
99
|
+
if (connectListener) {
|
|
100
|
+
socket.on('secureConnect', connectListener);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let tcp = net.connect(port, host, function() {
|
|
104
|
+
socket.setSocket(tcp);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
tcp.on('error', function(e) {
|
|
108
|
+
socket.emit('error', e);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return socket;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ===================== tls.createServer() =====================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Node.js compatible tls.createServer().
|
|
118
|
+
*
|
|
119
|
+
* Usage:
|
|
120
|
+
* tls.createServer(options, connectionListener)
|
|
121
|
+
*/
|
|
122
|
+
function createServer(options, connectionListener) {
|
|
123
|
+
if (typeof options === 'function') {
|
|
124
|
+
connectionListener = options;
|
|
125
|
+
options = {};
|
|
126
|
+
}
|
|
127
|
+
options = options || {};
|
|
128
|
+
|
|
129
|
+
let ctx = null;
|
|
130
|
+
if (options.key && options.cert) {
|
|
131
|
+
ctx = createSecureContext({ key: options.key, cert: options.cert });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Shared ticketKeys for all connections (enables PSK across connections)
|
|
135
|
+
let sharedTicketKeys = options.ticketKeys || crypto.randomBytes(48);
|
|
136
|
+
|
|
137
|
+
let server = net.createServer(function(tcp) {
|
|
138
|
+
let socketOpts = {
|
|
139
|
+
isServer: true,
|
|
140
|
+
ticketKeys: sharedTicketKeys,
|
|
141
|
+
ALPNProtocols: options.ALPNProtocols,
|
|
142
|
+
minVersion: options.minVersion || DEFAULT_MIN_VERSION,
|
|
143
|
+
maxVersion: options.maxVersion || DEFAULT_MAX_VERSION,
|
|
144
|
+
signatureAlgorithms: options.signatureAlgorithms,
|
|
145
|
+
groups: options.groups,
|
|
146
|
+
prioritizeChaCha: options.prioritizeChaCha,
|
|
147
|
+
maxRecordSize: options.maxRecordSize,
|
|
148
|
+
noTickets: options.noTickets,
|
|
149
|
+
requestCert: options.requestCert,
|
|
150
|
+
maxHandshakeSize: options.maxHandshakeSize,
|
|
151
|
+
allowedCipherSuites: options.allowedCipherSuites,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (options.SNICallback) {
|
|
155
|
+
socketOpts.SNICallback = options.SNICallback;
|
|
156
|
+
} else if (ctx) {
|
|
157
|
+
socketOpts.SNICallback = function(servername, cb) {
|
|
158
|
+
cb(null, ctx);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let socket = new TLSSocket(tcp, socketOpts);
|
|
163
|
+
addCompatMethods(socket);
|
|
164
|
+
|
|
165
|
+
if (connectionListener) {
|
|
166
|
+
socket.on('secureConnect', function() {
|
|
167
|
+
connectionListener(socket);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
server.emit('secureConnection', socket);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return server;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ===================== Compat methods for TLSSocket =====================
|
|
178
|
+
|
|
179
|
+
function addCompatMethods(socket) {
|
|
180
|
+
let session = socket.getSession();
|
|
181
|
+
|
|
182
|
+
/** Node.js compat: isSessionReused() */
|
|
183
|
+
socket.isSessionReused = function() {
|
|
184
|
+
return socket.isResumed;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** Node.js compat: getFinished() */
|
|
188
|
+
socket.getFinished = function() {
|
|
189
|
+
return session.getFinished ? session.getFinished() : null;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Node.js compat: getPeerFinished() */
|
|
193
|
+
socket.getPeerFinished = function() {
|
|
194
|
+
return session.getPeerFinished ? session.getPeerFinished() : null;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** Node.js compat: exportKeyingMaterial(length, label, context) */
|
|
198
|
+
socket.exportKeyingMaterial = function(length, label, context) {
|
|
199
|
+
return session.exportKeyingMaterial ? session.exportKeyingMaterial(length, label, context) : new Uint8Array(0);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** Node.js compat: getEphemeralKeyInfo() */
|
|
203
|
+
socket.getEphemeralKeyInfo = function() {
|
|
204
|
+
let group = session.context.selected_group;
|
|
205
|
+
if (group === 0x001d) return { type: 'X25519', size: 253 };
|
|
206
|
+
if (group === 0x0017) return { type: 'ECDH', name: 'prime256v1', size: 256 };
|
|
207
|
+
return {};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Node.js compat: setServername(name) */
|
|
211
|
+
socket.setServername = function(name) {
|
|
212
|
+
session.set_context({ local_sni: name });
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** Node.js compat: disableRenegotiation() — no-op (renegotiation not supported) */
|
|
216
|
+
socket.disableRenegotiation = function() {};
|
|
217
|
+
|
|
218
|
+
/** Node.js compat: address() — delegates to underlying transport */
|
|
219
|
+
socket.address = function() {
|
|
220
|
+
try { return session.context && session.context.transport ? session.context.transport.address() : {}; }
|
|
221
|
+
catch(e) { return {}; }
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ===================== Exports =====================
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
connect,
|
|
229
|
+
createServer,
|
|
230
|
+
createSecureContext,
|
|
231
|
+
getCiphers,
|
|
232
|
+
addCompatMethods,
|
|
233
|
+
DEFAULT_MIN_VERSION,
|
|
234
|
+
DEFAULT_MAX_VERSION,
|
|
235
|
+
};
|