node-rtc-connection 1.0.12 → 1.0.15

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/src/STUNClient.js DELETED
@@ -1,222 +0,0 @@
1
- /**
2
- * STUN Client Implementation (RFC 5389)
3
- * Pure Node.js implementation using dgram
4
- */
5
-
6
- const dgram = require('dgram');
7
- const crypto = require('crypto');
8
-
9
- // STUN Message Types
10
- const STUN_BINDING_REQUEST = 0x0001;
11
- const STUN_BINDING_RESPONSE = 0x0101;
12
-
13
- // STUN Attributes
14
- const ATTR_MAPPED_ADDRESS = 0x0001;
15
- const ATTR_XOR_MAPPED_ADDRESS = 0x0020;
16
-
17
- // STUN Magic Cookie
18
- const MAGIC_COOKIE = 0x2112A442;
19
-
20
- class STUNClient {
21
- constructor() {
22
- this.socket = null;
23
- this.timeout = 5000;
24
- }
25
-
26
- /**
27
- * Get public IP and port by querying STUN server
28
- * @param {string} stunServer - STUN server address (e.g., 'stun.l.google.com:19302')
29
- * @returns {Promise<{ip: string, port: number, type: string}>}
30
- */
31
- async getReflexiveAddress(stunServer = 'stun.l.google.com:19302') {
32
- return new Promise((resolve, reject) => {
33
- const [host, portStr] = stunServer.split(':');
34
- const port = parseInt(portStr, 10);
35
-
36
- // Create UDP socket
37
- this.socket = dgram.createSocket('udp4');
38
-
39
- const transactionId = crypto.randomBytes(12);
40
- let resolved = false;
41
-
42
- const timeout = setTimeout(() => {
43
- if (!resolved) {
44
- resolved = true;
45
- this.socket.close();
46
- reject(new Error('STUN request timeout'));
47
- }
48
- }, this.timeout);
49
-
50
- this.socket.on('error', (err) => {
51
- if (!resolved) {
52
- resolved = true;
53
- clearTimeout(timeout);
54
- this.socket.close();
55
- reject(err);
56
- }
57
- });
58
-
59
- this.socket.on('message', (msg) => {
60
- if (resolved) return;
61
-
62
- try {
63
- const result = this._parseSTUNResponse(msg, transactionId);
64
- if (result) {
65
- resolved = true;
66
- clearTimeout(timeout);
67
- this.socket.close();
68
- resolve(result);
69
- }
70
- } catch (err) {
71
- resolved = true;
72
- clearTimeout(timeout);
73
- this.socket.close();
74
- reject(err);
75
- }
76
- });
77
-
78
- // Send STUN Binding Request
79
- const request = this._createBindingRequest(transactionId);
80
- this.socket.send(request, port, host, (err) => {
81
- if (err && !resolved) {
82
- resolved = true;
83
- clearTimeout(timeout);
84
- this.socket.close();
85
- reject(err);
86
- }
87
- });
88
- });
89
- }
90
-
91
- /**
92
- * Create STUN Binding Request
93
- * @private
94
- */
95
- _createBindingRequest(transactionId) {
96
- const buffer = Buffer.allocUnsafe(20);
97
-
98
- // Message Type (2 bytes)
99
- buffer.writeUInt16BE(STUN_BINDING_REQUEST, 0);
100
-
101
- // Message Length (2 bytes) - 0 for no attributes
102
- buffer.writeUInt16BE(0, 2);
103
-
104
- // Magic Cookie (4 bytes)
105
- buffer.writeUInt32BE(MAGIC_COOKIE, 4);
106
-
107
- // Transaction ID (12 bytes)
108
- transactionId.copy(buffer, 8);
109
-
110
- return buffer;
111
- }
112
-
113
- /**
114
- * Parse STUN Response
115
- * @private
116
- */
117
- _parseSTUNResponse(msg, expectedTransactionId) {
118
- if (msg.length < 20) {
119
- throw new Error('Invalid STUN response: too short');
120
- }
121
-
122
- const messageType = msg.readUInt16BE(0);
123
- const messageLength = msg.readUInt16BE(2);
124
- const magicCookie = msg.readUInt32BE(4);
125
- const transactionId = msg.slice(8, 20);
126
-
127
- // Verify this is a binding response
128
- if (messageType !== STUN_BINDING_RESPONSE) {
129
- return null;
130
- }
131
-
132
- // Verify magic cookie
133
- if (magicCookie !== MAGIC_COOKIE) {
134
- throw new Error('Invalid STUN response: bad magic cookie');
135
- }
136
-
137
- // Verify transaction ID
138
- if (!transactionId.equals(expectedTransactionId)) {
139
- return null;
140
- }
141
-
142
- // Parse attributes
143
- let offset = 20;
144
- while (offset < 20 + messageLength) {
145
- const attrType = msg.readUInt16BE(offset);
146
- const attrLength = msg.readUInt16BE(offset + 2);
147
- const attrValue = msg.slice(offset + 4, offset + 4 + attrLength);
148
-
149
- if (attrType === ATTR_XOR_MAPPED_ADDRESS) {
150
- return this._parseXorMappedAddress(attrValue, transactionId);
151
- } else if (attrType === ATTR_MAPPED_ADDRESS) {
152
- return this._parseMappedAddress(attrValue);
153
- }
154
-
155
- // Move to next attribute (with padding)
156
- offset += 4 + attrLength;
157
- if (attrLength % 4 !== 0) {
158
- offset += 4 - (attrLength % 4);
159
- }
160
- }
161
-
162
- throw new Error('No mapped address in STUN response');
163
- }
164
-
165
- /**
166
- * Parse XOR-MAPPED-ADDRESS attribute
167
- * @private
168
- */
169
- _parseXorMappedAddress(value, transactionId) {
170
- const family = value.readUInt8(1);
171
- const xPort = value.readUInt16BE(2);
172
- const xAddress = value.slice(4, 8);
173
-
174
- // XOR with magic cookie
175
- const port = xPort ^ (MAGIC_COOKIE >> 16);
176
-
177
- const addressBytes = Buffer.allocUnsafe(4);
178
- const magicBytes = Buffer.allocUnsafe(4);
179
- magicBytes.writeUInt32BE(MAGIC_COOKIE, 0);
180
-
181
- for (let i = 0; i < 4; i++) {
182
- addressBytes[i] = xAddress[i] ^ magicBytes[i];
183
- }
184
-
185
- const ip = Array.from(addressBytes).join('.');
186
-
187
- return {
188
- ip,
189
- port,
190
- type: 'srflx' // Server Reflexive
191
- };
192
- }
193
-
194
- /**
195
- * Parse MAPPED-ADDRESS attribute
196
- * @private
197
- */
198
- _parseMappedAddress(value) {
199
- const family = value.readUInt8(1);
200
- const port = value.readUInt16BE(2);
201
- const addressBytes = value.slice(4, 8);
202
- const ip = Array.from(addressBytes).join('.');
203
-
204
- return {
205
- ip,
206
- port,
207
- type: 'srflx'
208
- };
209
- }
210
-
211
- /**
212
- * Close the socket
213
- */
214
- close() {
215
- if (this.socket) {
216
- this.socket.close();
217
- this.socket = null;
218
- }
219
- }
220
- }
221
-
222
- module.exports = STUNClient;
@@ -1,298 +0,0 @@
1
- /**
2
- * Secure Connection Wrapper
3
- * Provides TLS/DTLS-like encryption using Node.js crypto and tls modules
4
- */
5
-
6
- const tls = require('tls');
7
- const crypto = require('crypto');
8
- const { EventEmitter } = require('events');
9
-
10
- class SecureConnection extends EventEmitter {
11
- constructor(socket, options = {}) {
12
- super();
13
-
14
- this.socket = socket;
15
- this.isServer = options.isServer || false;
16
- this.secureSocket = null;
17
- this.certificates = options.certificates || this._generateCertificates();
18
- this.ready = false;
19
- }
20
-
21
- /**
22
- * Generate self-signed certificates for DTLS-like encryption
23
- * @private
24
- */
25
- _generateCertificates() {
26
- // Use Node.js built-in pki module to generate self-signed cert
27
- const { exec } = require('child_process');
28
- const fs = require('fs');
29
- const path = require('path');
30
-
31
- // Create a simple self-signed certificate using openssl command
32
- // For simplicity, we'll use generateKeyPairSync for keys
33
- const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
34
- modulusLength: 2048,
35
- publicKeyEncoding: {
36
- type: 'spki',
37
- format: 'pem'
38
- },
39
- privateKeyEncoding: {
40
- type: 'pkcs8',
41
- format: 'pem',
42
- cipher: undefined,
43
- passphrase: undefined
44
- }
45
- });
46
-
47
- // Generate a basic self-signed certificate using Node's crypto
48
- // This is a simplified approach - in production, use proper PKI
49
- const cert = this._createMinimalCert(publicKey);
50
-
51
- return {
52
- key: privateKey,
53
- cert: cert
54
- };
55
- }
56
-
57
- /**
58
- * Create a minimal self-signed certificate
59
- * @private
60
- */
61
- _createMinimalCert(publicKey) {
62
- // Create a minimal X.509-like certificate structure
63
- // This is simplified - real certs are much more complex
64
- const certInfo = {
65
- version: 3,
66
- serialNumber: crypto.randomBytes(16).toString('hex'),
67
- subject: 'CN=NodeRTC',
68
- issuer: 'CN=NodeRTC',
69
- notBefore: new Date().toISOString(),
70
- notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
71
- publicKey: publicKey
72
- };
73
-
74
- // For Node.js TLS, we need a proper PEM certificate
75
- // Since we can't easily generate one without external tools,
76
- // we'll create a minimal valid PEM structure
77
- const certPem = `-----BEGIN CERTIFICATE-----
78
- MIICljCCAX4CCQCxMjQxNjA5MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCU5v
79
- ZGVSVENMQTAEFQ0yMzEyMjcwMDAwMDBaFw0yNDEyMjcwMDAwMDBaMBQxEjAQBgNV
80
- BAMMCU5vZGVSVEMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8kxdC
81
- g5xPf+GJ3LQnJpXMTq7Z2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5Jnl
82
- JXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2wKj
83
- 5JnlJXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK
84
- 2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2YLvnJCvXXvW
85
- 8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQ
86
- JJjpKKQh8FLG3wKj5JnlJXFG3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGxPPzk=
87
- -----END CERTIFICATE-----`;
88
-
89
- return certPem;
90
- }
91
-
92
- /**
93
- * Wrap socket with TLS encryption
94
- * @returns {Promise<void>}
95
- */
96
- async wrap() {
97
- return new Promise((resolve, reject) => {
98
- try {
99
- const tlsOptions = {
100
- socket: this.socket,
101
- rejectUnauthorized: false, // Accept self-signed certs
102
- requestCert: false,
103
- ...this.certificates
104
- };
105
-
106
- if (this.isServer) {
107
- this.secureSocket = new tls.TLSSocket(this.socket, {
108
- isServer: true,
109
- ...tlsOptions
110
- });
111
- } else {
112
- this.secureSocket = tls.connect({
113
- socket: this.socket,
114
- rejectUnauthorized: false
115
- });
116
- }
117
-
118
- this.secureSocket.on('secureConnect', () => {
119
- this.ready = true;
120
- this.emit('secure');
121
- resolve();
122
- });
123
-
124
- this.secureSocket.on('error', (err) => {
125
- this.emit('error', err);
126
- if (!this.ready) {
127
- reject(err);
128
- }
129
- });
130
-
131
- this.secureSocket.on('data', (data) => {
132
- this.emit('data', data);
133
- });
134
-
135
- this.secureSocket.on('close', () => {
136
- this.emit('close');
137
- });
138
-
139
- } catch (err) {
140
- reject(err);
141
- }
142
- });
143
- }
144
-
145
- /**
146
- * Send data over secure connection
147
- * @param {Buffer|string} data - Data to send
148
- * @returns {boolean}
149
- */
150
- write(data) {
151
- if (!this.secureSocket || !this.ready) {
152
- throw new Error('Secure connection not ready');
153
- }
154
- return this.secureSocket.write(data);
155
- }
156
-
157
- /**
158
- * Get certificate fingerprint for SDP
159
- * @returns {string}
160
- */
161
- getFingerprint() {
162
- // Calculate SHA-256 fingerprint of certificate
163
- const cert = this.certificates.cert;
164
- const hash = crypto.createHash('sha256').update(cert).digest('hex');
165
-
166
- // Format as XX:XX:XX:...
167
- return hash.match(/.{2}/g).join(':').toUpperCase();
168
- }
169
-
170
- /**
171
- * Close the secure connection
172
- */
173
- close() {
174
- if (this.secureSocket) {
175
- this.secureSocket.destroy();
176
- this.secureSocket = null;
177
- }
178
- this.ready = false;
179
- }
180
-
181
- /**
182
- * Check if connection is ready
183
- */
184
- isReady() {
185
- return this.ready;
186
- }
187
- }
188
-
189
- /**
190
- * Simplified DTLS-like encryption for UDP
191
- * Uses AES-256-GCM for packet encryption
192
- */
193
- class DTLSConnection extends EventEmitter {
194
- constructor(options = {}) {
195
- super();
196
-
197
- this.isServer = options.isServer || false;
198
- this.key = options.key || crypto.randomBytes(32); // 256-bit key
199
- this.remoteKey = null;
200
- this.ready = false;
201
- }
202
-
203
- /**
204
- * Exchange keys (simplified handshake)
205
- * In real DTLS, this would be a full handshake
206
- */
207
- async handshake(sendCallback) {
208
- return new Promise((resolve, reject) => {
209
- // Send our key
210
- sendCallback(this.key);
211
-
212
- // Wait for remote key
213
- const timeout = setTimeout(() => {
214
- reject(new Error('DTLS handshake timeout'));
215
- }, 5000);
216
-
217
- this.once('remoteKey', (key) => {
218
- clearTimeout(timeout);
219
- this.remoteKey = key;
220
- this.ready = true;
221
- this.emit('ready');
222
- resolve();
223
- });
224
- });
225
- }
226
-
227
- /**
228
- * Set remote key
229
- */
230
- setRemoteKey(key) {
231
- this.remoteKey = key;
232
- this.emit('remoteKey', key);
233
- }
234
-
235
- /**
236
- * Encrypt data for transmission
237
- * @param {Buffer} data - Plaintext data
238
- * @returns {Buffer} Encrypted data with IV and auth tag
239
- */
240
- encrypt(data) {
241
- if (!this.ready) {
242
- throw new Error('DTLS not ready');
243
- }
244
-
245
- const iv = crypto.randomBytes(12); // GCM IV
246
- const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
247
-
248
- const encrypted = Buffer.concat([
249
- cipher.update(data),
250
- cipher.final()
251
- ]);
252
-
253
- const authTag = cipher.getAuthTag();
254
-
255
- // Return: IV (12) + Auth Tag (16) + Encrypted Data
256
- return Buffer.concat([iv, authTag, encrypted]);
257
- }
258
-
259
- /**
260
- * Decrypt received data
261
- * @param {Buffer} data - Encrypted data with IV and auth tag
262
- * @returns {Buffer} Decrypted data
263
- */
264
- decrypt(data) {
265
- if (!this.ready || !this.remoteKey) {
266
- throw new Error('DTLS not ready');
267
- }
268
-
269
- if (data.length < 28) {
270
- throw new Error('Invalid encrypted packet');
271
- }
272
-
273
- const iv = data.slice(0, 12);
274
- const authTag = data.slice(12, 28);
275
- const encrypted = data.slice(28);
276
-
277
- const decipher = crypto.createDecipheriv('aes-256-gcm', this.remoteKey, iv);
278
- decipher.setAuthTag(authTag);
279
-
280
- return Buffer.concat([
281
- decipher.update(encrypted),
282
- decipher.final()
283
- ]);
284
- }
285
-
286
- /**
287
- * Get fingerprint of our key
288
- */
289
- getFingerprint() {
290
- const hash = crypto.createHash('sha256').update(this.key).digest('hex');
291
- return hash.match(/.{2}/g).join(':').toUpperCase();
292
- }
293
- }
294
-
295
- module.exports = {
296
- SecureConnection,
297
- DTLSConnection
298
- };