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.
@@ -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 };