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/README.md +258 -203
- package/index.d.ts +145 -14
- package/index.js +33 -6
- package/package.json +3 -10
- package/src/compat.js +290 -31
- package/src/crypto.js +139 -8
- package/src/dtls_session.js +865 -0
- package/src/dtls_socket.js +263 -0
- package/src/record.js +894 -65
- package/src/session/message.js +33 -5
- package/src/session/ticket.js +185 -0
- package/src/tls_session.js +945 -150
- package/src/tls_socket.js +815 -249
- package/src/wire.js +167 -11
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dtls_socket.js — UDP transport wrapper for DTLS.
|
|
3
|
+
*
|
|
4
|
+
* DTLSSocket: wraps a single DTLS connection over a UDP socket.
|
|
5
|
+
* createDTLSServer: listens on a UDP port, manages multiple DTLS sessions.
|
|
6
|
+
* connectDTLS: client convenience — creates UDP socket + DTLSSocket.
|
|
7
|
+
*
|
|
8
|
+
* Usage (client):
|
|
9
|
+
* let socket = connectDTLS({ host: 'example.com', port: 4433 });
|
|
10
|
+
* socket.on('connect', () => socket.send('hello'));
|
|
11
|
+
* socket.on('data', (buf) => console.log(buf));
|
|
12
|
+
*
|
|
13
|
+
* Usage (server):
|
|
14
|
+
* let server = createDTLSServer({ key: KEY, cert: CERT }, (socket) => {
|
|
15
|
+
* socket.on('data', (buf) => socket.send(buf)); // echo
|
|
16
|
+
* });
|
|
17
|
+
* server.listen(4433);
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import dgram from 'node:dgram';
|
|
21
|
+
import { EventEmitter } from 'node:events';
|
|
22
|
+
import DTLSSession from './dtls_session.js';
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// ============================================================
|
|
26
|
+
// DTLSSocket — single DTLS connection over UDP
|
|
27
|
+
// ============================================================
|
|
28
|
+
|
|
29
|
+
function DTLSSocket(udpSocket, options) {
|
|
30
|
+
if (!(this instanceof DTLSSocket)) return new DTLSSocket(udpSocket, options);
|
|
31
|
+
options = options || {};
|
|
32
|
+
|
|
33
|
+
let self = this;
|
|
34
|
+
let ev = new EventEmitter();
|
|
35
|
+
|
|
36
|
+
let remoteAddress = options.remoteAddress || null;
|
|
37
|
+
let remotePort = options.remotePort || null;
|
|
38
|
+
|
|
39
|
+
// Create DTLSSession
|
|
40
|
+
let session = new DTLSSession({
|
|
41
|
+
isServer: !!options.isServer,
|
|
42
|
+
servername: options.servername,
|
|
43
|
+
SNICallback: options.SNICallback,
|
|
44
|
+
cert: options.cert,
|
|
45
|
+
key: options.key,
|
|
46
|
+
rejectUnauthorized: options.rejectUnauthorized,
|
|
47
|
+
ca: options.ca,
|
|
48
|
+
alpnProtocols: options.alpnProtocols,
|
|
49
|
+
minVersion: options.minVersion,
|
|
50
|
+
maxVersion: options.maxVersion,
|
|
51
|
+
mtu: options.mtu,
|
|
52
|
+
ticketKeys: options.ticketKeys,
|
|
53
|
+
useCookies: options.useCookies,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Wire session → UDP
|
|
57
|
+
session.on('packet', function(data) {
|
|
58
|
+
if (!remoteAddress || !remotePort) return;
|
|
59
|
+
udpSocket.send(data, 0, data.length, remotePort, remoteAddress);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
session.on('connect', function() {
|
|
63
|
+
ev.emit('connect');
|
|
64
|
+
ev.emit('secureConnect');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
session.on('data', function(data) {
|
|
68
|
+
ev.emit('data', data);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
session.on('error', function(err) {
|
|
72
|
+
ev.emit('error', err);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
session.on('close', function() {
|
|
76
|
+
ev.emit('close');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
session.on('session', function(ticket) {
|
|
80
|
+
ev.emit('session', ticket);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Feed an incoming UDP datagram (filtered by remote address/port).
|
|
85
|
+
* Called by the UDP socket's message handler or by DTLSServer.
|
|
86
|
+
*/
|
|
87
|
+
function feedDatagram(data, rinfo) {
|
|
88
|
+
// Update remote address on first datagram (server side)
|
|
89
|
+
if (!remoteAddress && rinfo) {
|
|
90
|
+
remoteAddress = rinfo.address;
|
|
91
|
+
remotePort = rinfo.port;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
session.feedDatagram(data);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If not server, bind to UDP messages directly
|
|
98
|
+
if (!options.isServer && udpSocket) {
|
|
99
|
+
udpSocket.on('message', function(msg, rinfo) {
|
|
100
|
+
if (remoteAddress && (rinfo.address !== remoteAddress || rinfo.port !== remotePort)) return;
|
|
101
|
+
feedDatagram(msg, rinfo);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- Public API ----
|
|
106
|
+
|
|
107
|
+
self.send = function(data) { session.send(data); };
|
|
108
|
+
self.close = function() { session.close(); };
|
|
109
|
+
self.feedDatagram = feedDatagram;
|
|
110
|
+
|
|
111
|
+
self.on = function(name, fn) { ev.on(name, fn); };
|
|
112
|
+
self.off = function(name, fn) { ev.off(name, fn); };
|
|
113
|
+
|
|
114
|
+
Object.defineProperty(self, 'connected', { get: function() { return session.connected; } });
|
|
115
|
+
Object.defineProperty(self, 'state', { get: function() { return session.state; } });
|
|
116
|
+
Object.defineProperty(self, 'version', { get: function() { return session.version; } });
|
|
117
|
+
Object.defineProperty(self, 'remoteAddress', { get: function() { return remoteAddress; } });
|
|
118
|
+
Object.defineProperty(self, 'remotePort', { get: function() { return remotePort; } });
|
|
119
|
+
|
|
120
|
+
self.getNegotiationResult = function() { return session.getNegotiationResult(); };
|
|
121
|
+
self.getALPN = function() { return session.getALPN(); };
|
|
122
|
+
self.getPeerCertificate = function() { return session.getPeerCertificate(); };
|
|
123
|
+
|
|
124
|
+
/** Access to internal DTLSSession (for advanced use). */
|
|
125
|
+
self.session = session;
|
|
126
|
+
|
|
127
|
+
return self;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// createDTLSServer — manages multiple DTLS sessions on one UDP port
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
function createDTLSServer(options, connectionListener) {
|
|
136
|
+
if (typeof options === 'function') {
|
|
137
|
+
connectionListener = options;
|
|
138
|
+
options = {};
|
|
139
|
+
}
|
|
140
|
+
options = options || {};
|
|
141
|
+
|
|
142
|
+
let ev = new EventEmitter();
|
|
143
|
+
let udpSocket = null;
|
|
144
|
+
let connections = {}; // 'addr:port' → DTLSSocket
|
|
145
|
+
|
|
146
|
+
function listen(port, address, callback) {
|
|
147
|
+
if (typeof address === 'function') { callback = address; address = undefined; }
|
|
148
|
+
|
|
149
|
+
udpSocket = dgram.createSocket('udp4');
|
|
150
|
+
|
|
151
|
+
udpSocket.on('message', function(msg, rinfo) {
|
|
152
|
+
let key = rinfo.address + ':' + rinfo.port;
|
|
153
|
+
|
|
154
|
+
if (!(key in connections)) {
|
|
155
|
+
// New client
|
|
156
|
+
let socket = new DTLSSocket(udpSocket, {
|
|
157
|
+
isServer: true,
|
|
158
|
+
remoteAddress: rinfo.address,
|
|
159
|
+
remotePort: rinfo.port,
|
|
160
|
+
cert: options.cert,
|
|
161
|
+
key: options.key,
|
|
162
|
+
SNICallback: options.SNICallback,
|
|
163
|
+
alpnProtocols: options.alpnProtocols,
|
|
164
|
+
minVersion: options.minVersion,
|
|
165
|
+
maxVersion: options.maxVersion,
|
|
166
|
+
mtu: options.mtu,
|
|
167
|
+
ticketKeys: options.ticketKeys,
|
|
168
|
+
useCookies: options.useCookies,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
connections[key] = socket;
|
|
172
|
+
|
|
173
|
+
socket.on('close', function() {
|
|
174
|
+
delete connections[key];
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
socket.on('connect', function() {
|
|
178
|
+
if (connectionListener) connectionListener(socket);
|
|
179
|
+
ev.emit('connection', socket);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
socket.on('error', function(err) {
|
|
183
|
+
ev.emit('clientError', err, socket);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
connections[key].feedDatagram(msg, rinfo);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
udpSocket.on('error', function(err) {
|
|
191
|
+
ev.emit('error', err);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
udpSocket.bind(port, address, function() {
|
|
195
|
+
if (callback) callback();
|
|
196
|
+
ev.emit('listening');
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function close(callback) {
|
|
201
|
+
for (let key in connections) {
|
|
202
|
+
connections[key].close();
|
|
203
|
+
}
|
|
204
|
+
connections = {};
|
|
205
|
+
if (udpSocket) {
|
|
206
|
+
udpSocket.close(callback);
|
|
207
|
+
udpSocket = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function address() {
|
|
212
|
+
return udpSocket ? udpSocket.address() : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
listen: listen,
|
|
217
|
+
close: close,
|
|
218
|
+
address: address,
|
|
219
|
+
on: function(name, fn) { ev.on(name, fn); },
|
|
220
|
+
off: function(name, fn) { ev.off(name, fn); },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
// ============================================================
|
|
226
|
+
// connectDTLS — client convenience
|
|
227
|
+
// ============================================================
|
|
228
|
+
|
|
229
|
+
function connectDTLS(options, callback) {
|
|
230
|
+
options = options || {};
|
|
231
|
+
let host = options.host || options.hostname || '127.0.0.1';
|
|
232
|
+
let port = options.port || 4433;
|
|
233
|
+
|
|
234
|
+
let udp = dgram.createSocket('udp4');
|
|
235
|
+
|
|
236
|
+
let socket = new DTLSSocket(udp, {
|
|
237
|
+
isServer: false,
|
|
238
|
+
remoteAddress: host,
|
|
239
|
+
remotePort: port,
|
|
240
|
+
servername: options.servername || host,
|
|
241
|
+
rejectUnauthorized: options.rejectUnauthorized,
|
|
242
|
+
ca: options.ca,
|
|
243
|
+
alpnProtocols: options.alpnProtocols,
|
|
244
|
+
minVersion: options.minVersion,
|
|
245
|
+
maxVersion: options.maxVersion,
|
|
246
|
+
mtu: options.mtu,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (callback) socket.on('connect', callback);
|
|
250
|
+
|
|
251
|
+
socket.on('close', function() {
|
|
252
|
+
try { udp.close(); } catch(e) {}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return socket;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
// ============================================================
|
|
260
|
+
// Exports
|
|
261
|
+
// ============================================================
|
|
262
|
+
|
|
263
|
+
export { DTLSSocket, createDTLSServer, connectDTLS };
|