node-rtc-connection 1.0.11 → 1.0.14
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 +355 -289
- package/dist/index.cjs +4318 -3095
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +4318 -3095
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/datachannel/RTCDataChannel.js +354 -0
- package/src/dtls/RTCCertificate.js +310 -0
- package/src/dtls/RTCDtlsTransport.js +247 -0
- package/src/foundation/ByteBufferQueue.js +235 -0
- package/src/foundation/RTCError.js +226 -0
- package/src/ice/RTCIceCandidate.js +301 -0
- package/src/ice/RTCIceTransport.js +956 -0
- package/src/index.d.ts +316 -145
- package/src/index.js +78 -45
- package/src/network/network-transport.js +478 -0
- package/src/peerconnection/RTCPeerConnection.js +847 -0
- package/src/sctp/RTCSctpTransport.js +253 -0
- package/src/sdp/RTCSessionDescription.js +102 -0
- package/src/sdp/sdp-utils.js +224 -0
- package/src/stun/stun-client.js +643 -0
- package/src/ICEGatherer.js +0 -341
- package/src/NativePeerConnectionFactory.js +0 -1044
- package/src/RTCDataChannel.js +0 -346
- package/src/RTCDataChannelEvent.js +0 -50
- package/src/RTCError.js +0 -66
- package/src/RTCIceCandidate.js +0 -184
- package/src/RTCPeerConnection.js +0 -505
- package/src/RTCPeerConnectionIceEvent.js +0 -58
- package/src/RTCSessionDescription.js +0 -62
- package/src/STUNClient.js +0 -222
- package/src/SecureConnection.js +0 -298
- package/src/TURNClient.js +0 -561
- package/src/UDPTransport.js +0 -236
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;
|
package/src/SecureConnection.js
DELETED
|
@@ -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
|
-
};
|