@omindu/yaksha 1.0.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/LICENSE +21 -0
- package/bin/yaksha.js +329 -0
- package/package.json +28 -0
- package/src/bypass/firewall-evasion.js +354 -0
- package/src/client/index.js +544 -0
- package/src/core/connection.js +393 -0
- package/src/core/encryption.js +299 -0
- package/src/core/protocol.js +268 -0
- package/src/features/dns-override.js +403 -0
- package/src/features/multi-path.js +394 -0
- package/src/features/sni-spoof.js +355 -0
- package/src/features/tls-camouflage.js +369 -0
- package/src/features/traffic-obfuscation.js +338 -0
- package/src/index.js +106 -0
- package/src/security/auth.js +441 -0
- package/src/security/levels.js +316 -0
- package/src/server/index.js +551 -0
- package/src/utils/buffer-pool.js +150 -0
- package/src/utils/config.js +205 -0
- package/src/utils/logger.js +105 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Yaksha Connection Manager
|
|
5
|
+
* Handles TCP/UDP connections with pooling and automatic reconnection
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const net = require('net');
|
|
9
|
+
const dgram = require('dgram');
|
|
10
|
+
const EventEmitter = require('events');
|
|
11
|
+
const Logger = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
// Connection states
|
|
14
|
+
const CONNECTION_STATES = {
|
|
15
|
+
DISCONNECTED: 'disconnected',
|
|
16
|
+
CONNECTING: 'connecting',
|
|
17
|
+
AUTHENTICATING: 'authenticating',
|
|
18
|
+
CONNECTED: 'connected',
|
|
19
|
+
DISCONNECTING: 'disconnecting'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
class Connection extends EventEmitter {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this.host = options.host || 'localhost';
|
|
27
|
+
this.port = options.port || 8443;
|
|
28
|
+
this.useUDP = options.useUDP !== false;
|
|
29
|
+
this.useTCP = options.useTCP !== false;
|
|
30
|
+
this.timeout = options.timeout || 60000;
|
|
31
|
+
this.keepaliveInterval = options.keepaliveInterval || 30000;
|
|
32
|
+
this.autoReconnect = options.autoReconnect !== false;
|
|
33
|
+
this.reconnectDelay = options.reconnectDelay || 1000;
|
|
34
|
+
this.maxReconnectDelay = options.maxReconnectDelay || 30000;
|
|
35
|
+
this.reconnectBackoff = options.reconnectBackoff || 2.0;
|
|
36
|
+
|
|
37
|
+
this.state = CONNECTION_STATES.DISCONNECTED;
|
|
38
|
+
this.tcpSocket = null;
|
|
39
|
+
this.udpSocket = null;
|
|
40
|
+
this.keepaliveTimer = null;
|
|
41
|
+
this.timeoutTimer = null;
|
|
42
|
+
this.reconnectTimer = null;
|
|
43
|
+
this.currentReconnectDelay = this.reconnectDelay;
|
|
44
|
+
this.reconnectAttempts = 0;
|
|
45
|
+
|
|
46
|
+
this.logger = new Logger({ prefix: 'Connection' });
|
|
47
|
+
|
|
48
|
+
this.stats = {
|
|
49
|
+
bytesReceived: 0,
|
|
50
|
+
bytesSent: 0,
|
|
51
|
+
packetsReceived: 0,
|
|
52
|
+
packetsSent: 0,
|
|
53
|
+
errors: 0,
|
|
54
|
+
reconnects: 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Connect to server
|
|
60
|
+
*/
|
|
61
|
+
async connect() {
|
|
62
|
+
if (this.state !== CONNECTION_STATES.DISCONNECTED) {
|
|
63
|
+
throw new Error(`Cannot connect from state: ${this.state}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.state = CONNECTION_STATES.CONNECTING;
|
|
67
|
+
this.emit('connecting');
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Connect TCP control channel
|
|
71
|
+
if (this.useTCP) {
|
|
72
|
+
await this._connectTCP();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Connect UDP data channel
|
|
76
|
+
if (this.useUDP) {
|
|
77
|
+
await this._connectUDP();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.state = CONNECTION_STATES.CONNECTED;
|
|
81
|
+
this.currentReconnectDelay = this.reconnectDelay;
|
|
82
|
+
this.reconnectAttempts = 0;
|
|
83
|
+
|
|
84
|
+
this._startKeepalive();
|
|
85
|
+
this._startTimeout();
|
|
86
|
+
|
|
87
|
+
this.emit('connected');
|
|
88
|
+
this.logger.info(`Connected to ${this.host}:${this.port}`);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.state = CONNECTION_STATES.DISCONNECTED;
|
|
91
|
+
this.stats.errors++;
|
|
92
|
+
this.emit('error', error);
|
|
93
|
+
|
|
94
|
+
if (this.autoReconnect) {
|
|
95
|
+
this._scheduleReconnect();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Connect TCP socket
|
|
104
|
+
*/
|
|
105
|
+
_connectTCP() {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
this.tcpSocket = new net.Socket();
|
|
108
|
+
|
|
109
|
+
this.tcpSocket.on('data', (data) => {
|
|
110
|
+
this.stats.bytesReceived += data.length;
|
|
111
|
+
this.stats.packetsReceived++;
|
|
112
|
+
this._resetTimeout();
|
|
113
|
+
this.emit('tcp:data', data);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
this.tcpSocket.on('close', () => {
|
|
117
|
+
this.logger.debug('TCP connection closed');
|
|
118
|
+
this._handleDisconnection();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.tcpSocket.on('error', (error) => {
|
|
122
|
+
this.stats.errors++;
|
|
123
|
+
this.logger.error('TCP error:', error.message);
|
|
124
|
+
this.emit('error', error);
|
|
125
|
+
reject(error);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
this.tcpSocket.connect(this.port, this.host, () => {
|
|
129
|
+
this.logger.debug('TCP connected');
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Set timeout for connection
|
|
134
|
+
this.tcpSocket.setTimeout(this.timeout);
|
|
135
|
+
this.tcpSocket.on('timeout', () => {
|
|
136
|
+
this.logger.warn('TCP connection timeout');
|
|
137
|
+
this.tcpSocket.destroy();
|
|
138
|
+
reject(new Error('Connection timeout'));
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Connect UDP socket
|
|
145
|
+
*/
|
|
146
|
+
_connectUDP() {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
this.udpSocket = dgram.createSocket('udp4');
|
|
149
|
+
|
|
150
|
+
this.udpSocket.on('message', (data, rinfo) => {
|
|
151
|
+
this.stats.bytesReceived += data.length;
|
|
152
|
+
this.stats.packetsReceived++;
|
|
153
|
+
this._resetTimeout();
|
|
154
|
+
this.emit('udp:data', data, rinfo);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
this.udpSocket.on('error', (error) => {
|
|
158
|
+
this.stats.errors++;
|
|
159
|
+
this.logger.error('UDP error:', error.message);
|
|
160
|
+
this.emit('error', error);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.udpSocket.bind(() => {
|
|
164
|
+
this.logger.debug(`UDP socket bound to port ${this.udpSocket.address().port}`);
|
|
165
|
+
resolve();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Send data via TCP
|
|
172
|
+
*/
|
|
173
|
+
sendTCP(data) {
|
|
174
|
+
if (!this.tcpSocket || this.state !== CONNECTION_STATES.CONNECTED) {
|
|
175
|
+
throw new Error('TCP socket not connected');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
this.tcpSocket.write(data, (error) => {
|
|
180
|
+
if (error) {
|
|
181
|
+
this.stats.errors++;
|
|
182
|
+
reject(error);
|
|
183
|
+
} else {
|
|
184
|
+
this.stats.bytesSent += data.length;
|
|
185
|
+
this.stats.packetsSent++;
|
|
186
|
+
resolve();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Send data via UDP
|
|
194
|
+
*/
|
|
195
|
+
sendUDP(data, port, host) {
|
|
196
|
+
if (!this.udpSocket) {
|
|
197
|
+
throw new Error('UDP socket not created');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
port = port || this.port;
|
|
201
|
+
host = host || this.host;
|
|
202
|
+
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
this.udpSocket.send(data, port, host, (error) => {
|
|
205
|
+
if (error) {
|
|
206
|
+
this.stats.errors++;
|
|
207
|
+
reject(error);
|
|
208
|
+
} else {
|
|
209
|
+
this.stats.bytesSent += data.length;
|
|
210
|
+
this.stats.packetsSent++;
|
|
211
|
+
resolve();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Start keepalive mechanism
|
|
219
|
+
*/
|
|
220
|
+
_startKeepalive() {
|
|
221
|
+
this._stopKeepalive();
|
|
222
|
+
|
|
223
|
+
this.keepaliveTimer = setInterval(() => {
|
|
224
|
+
this.emit('keepalive');
|
|
225
|
+
}, this.keepaliveInterval);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Stop keepalive mechanism
|
|
230
|
+
*/
|
|
231
|
+
_stopKeepalive() {
|
|
232
|
+
if (this.keepaliveTimer) {
|
|
233
|
+
clearInterval(this.keepaliveTimer);
|
|
234
|
+
this.keepaliveTimer = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Start connection timeout timer
|
|
240
|
+
*/
|
|
241
|
+
_startTimeout() {
|
|
242
|
+
this._resetTimeout();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reset connection timeout timer
|
|
247
|
+
*/
|
|
248
|
+
_resetTimeout() {
|
|
249
|
+
if (this.timeoutTimer) {
|
|
250
|
+
clearTimeout(this.timeoutTimer);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.timeoutTimer = setTimeout(() => {
|
|
254
|
+
this.logger.warn('Connection timeout - no data received');
|
|
255
|
+
this.disconnect();
|
|
256
|
+
}, this.timeout);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Handle disconnection
|
|
261
|
+
*/
|
|
262
|
+
_handleDisconnection() {
|
|
263
|
+
if (this.state === CONNECTION_STATES.DISCONNECTING ||
|
|
264
|
+
this.state === CONNECTION_STATES.DISCONNECTED) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.logger.info('Connection lost');
|
|
269
|
+
this.state = CONNECTION_STATES.DISCONNECTED;
|
|
270
|
+
this._cleanup();
|
|
271
|
+
this.emit('disconnected');
|
|
272
|
+
|
|
273
|
+
if (this.autoReconnect) {
|
|
274
|
+
this._scheduleReconnect();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Schedule reconnection attempt
|
|
280
|
+
*/
|
|
281
|
+
_scheduleReconnect() {
|
|
282
|
+
if (this.reconnectTimer) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.reconnectAttempts++;
|
|
287
|
+
this.stats.reconnects++;
|
|
288
|
+
|
|
289
|
+
this.logger.info(`Reconnecting in ${this.currentReconnectDelay}ms (attempt ${this.reconnectAttempts})`);
|
|
290
|
+
this.emit('reconnecting', this.reconnectAttempts, this.currentReconnectDelay);
|
|
291
|
+
|
|
292
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
293
|
+
this.reconnectTimer = null;
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
await this.connect();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
// connect() will schedule next reconnect automatically
|
|
299
|
+
}
|
|
300
|
+
}, this.currentReconnectDelay);
|
|
301
|
+
|
|
302
|
+
// Exponential backoff
|
|
303
|
+
this.currentReconnectDelay = Math.min(
|
|
304
|
+
this.currentReconnectDelay * this.reconnectBackoff,
|
|
305
|
+
this.maxReconnectDelay
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Disconnect
|
|
311
|
+
*/
|
|
312
|
+
disconnect() {
|
|
313
|
+
if (this.state === CONNECTION_STATES.DISCONNECTED) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.state = CONNECTION_STATES.DISCONNECTING;
|
|
318
|
+
this.emit('disconnecting');
|
|
319
|
+
|
|
320
|
+
this._cleanup();
|
|
321
|
+
|
|
322
|
+
this.state = CONNECTION_STATES.DISCONNECTED;
|
|
323
|
+
this.emit('disconnected');
|
|
324
|
+
this.logger.info('Disconnected');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Cleanup resources
|
|
329
|
+
*/
|
|
330
|
+
_cleanup() {
|
|
331
|
+
this._stopKeepalive();
|
|
332
|
+
|
|
333
|
+
if (this.timeoutTimer) {
|
|
334
|
+
clearTimeout(this.timeoutTimer);
|
|
335
|
+
this.timeoutTimer = null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this.reconnectTimer) {
|
|
339
|
+
clearTimeout(this.reconnectTimer);
|
|
340
|
+
this.reconnectTimer = null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this.tcpSocket) {
|
|
344
|
+
this.tcpSocket.removeAllListeners();
|
|
345
|
+
this.tcpSocket.destroy();
|
|
346
|
+
this.tcpSocket = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (this.udpSocket) {
|
|
350
|
+
this.udpSocket.removeAllListeners();
|
|
351
|
+
this.udpSocket.close();
|
|
352
|
+
this.udpSocket = null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get connection state
|
|
358
|
+
*/
|
|
359
|
+
getState() {
|
|
360
|
+
return this.state;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Check if connected
|
|
365
|
+
*/
|
|
366
|
+
isConnected() {
|
|
367
|
+
return this.state === CONNECTION_STATES.CONNECTED;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get connection statistics
|
|
372
|
+
*/
|
|
373
|
+
getStats() {
|
|
374
|
+
return { ...this.stats };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Reset statistics
|
|
379
|
+
*/
|
|
380
|
+
resetStats() {
|
|
381
|
+
this.stats = {
|
|
382
|
+
bytesReceived: 0,
|
|
383
|
+
bytesSent: 0,
|
|
384
|
+
packetsReceived: 0,
|
|
385
|
+
packetsSent: 0,
|
|
386
|
+
errors: 0,
|
|
387
|
+
reconnects: 0
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
module.exports = Connection;
|
|
393
|
+
module.exports.CONNECTION_STATES = CONNECTION_STATES;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Yaksha Encryption Engine
|
|
5
|
+
* Supports multiple encryption algorithms for different security levels
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const nacl = require('tweetnacl');
|
|
10
|
+
|
|
11
|
+
// Encryption algorithms
|
|
12
|
+
const ALGORITHMS = {
|
|
13
|
+
CHACHA20_POLY1305: 'chacha20-poly1305',
|
|
14
|
+
AES_256_GCM: 'aes-256-gcm',
|
|
15
|
+
DOUBLE: 'double'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
class Encryption {
|
|
19
|
+
constructor(securityLevel = 'medium', options = {}) {
|
|
20
|
+
this.securityLevel = securityLevel;
|
|
21
|
+
this.customConfig = options.customConfig || null;
|
|
22
|
+
|
|
23
|
+
// Handle custom security level
|
|
24
|
+
if (securityLevel === 'custom' && this.customConfig) {
|
|
25
|
+
this.algorithm = this.customConfig.encryption || 'aes-256-gcm';
|
|
26
|
+
this.keyRotationInterval = this.customConfig.keyRotationInterval || null;
|
|
27
|
+
} else {
|
|
28
|
+
this.algorithm = this._selectAlgorithm(securityLevel);
|
|
29
|
+
this.keyRotationInterval = options.keyRotationInterval || (securityLevel === 'high' ? 1000 : null);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.packetCount = 0;
|
|
33
|
+
this.keys = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Select encryption algorithm based on security level
|
|
38
|
+
*/
|
|
39
|
+
_selectAlgorithm(level) {
|
|
40
|
+
switch (level) {
|
|
41
|
+
case 'low':
|
|
42
|
+
return ALGORITHMS.CHACHA20_POLY1305;
|
|
43
|
+
case 'medium':
|
|
44
|
+
return ALGORITHMS.AES_256_GCM;
|
|
45
|
+
case 'high':
|
|
46
|
+
return ALGORITHMS.DOUBLE;
|
|
47
|
+
case 'custom':
|
|
48
|
+
// For custom, return medium default (can be overridden)
|
|
49
|
+
return ALGORITHMS.AES_256_GCM;
|
|
50
|
+
default:
|
|
51
|
+
return ALGORITHMS.AES_256_GCM;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate encryption keys
|
|
57
|
+
*/
|
|
58
|
+
generateKeys() {
|
|
59
|
+
const keys = {
|
|
60
|
+
primary: crypto.randomBytes(32), // 256-bit key
|
|
61
|
+
secondary: null,
|
|
62
|
+
publicKey: null,
|
|
63
|
+
privateKey: null
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// For high security, generate key pair for ECDH
|
|
67
|
+
if (this.securityLevel === 'high' || this.securityLevel === 'medium') {
|
|
68
|
+
const keyPair = nacl.box.keyPair();
|
|
69
|
+
keys.publicKey = Buffer.from(keyPair.publicKey);
|
|
70
|
+
keys.privateKey = Buffer.from(keyPair.secretKey);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For double encryption, generate secondary key
|
|
74
|
+
if (this.algorithm === ALGORITHMS.DOUBLE) {
|
|
75
|
+
keys.secondary = crypto.randomBytes(32);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.keys = keys;
|
|
79
|
+
return keys;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Perform key exchange using X25519 (Curve25519)
|
|
84
|
+
*/
|
|
85
|
+
keyExchange(theirPublicKey) {
|
|
86
|
+
if (!this.keys || !this.keys.privateKey) {
|
|
87
|
+
throw new Error('Keys not generated. Call generateKeys() first.');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Compute shared secret using X25519
|
|
91
|
+
const sharedSecret = nacl.box.before(
|
|
92
|
+
Uint8Array.from(theirPublicKey),
|
|
93
|
+
Uint8Array.from(this.keys.privateKey)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Derive encryption key from shared secret using HKDF
|
|
97
|
+
const salt = crypto.randomBytes(32);
|
|
98
|
+
const info = Buffer.from('yaksha-v1');
|
|
99
|
+
const derivedKey = crypto.hkdfSync('sha256', Buffer.from(sharedSecret), salt, info, 32);
|
|
100
|
+
|
|
101
|
+
this.keys.primary = derivedKey;
|
|
102
|
+
return { sharedKey: derivedKey, salt };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set pre-shared key (for low security mode)
|
|
107
|
+
*/
|
|
108
|
+
setPreSharedKey(key) {
|
|
109
|
+
if (key.length !== 32) {
|
|
110
|
+
throw new Error('Key must be 32 bytes (256 bits)');
|
|
111
|
+
}
|
|
112
|
+
this.keys = { primary: key };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Encrypt data using ChaCha20-Poly1305
|
|
117
|
+
*/
|
|
118
|
+
_encryptChaCha20(data, key) {
|
|
119
|
+
const nonce = crypto.randomBytes(12); // 96-bit nonce
|
|
120
|
+
const cipher = crypto.createCipheriv('chacha20-poly1305', key, nonce, {
|
|
121
|
+
authTagLength: 16
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
125
|
+
const authTag = cipher.getAuthTag();
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
encrypted,
|
|
129
|
+
nonce,
|
|
130
|
+
authTag
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Decrypt data using ChaCha20-Poly1305
|
|
136
|
+
*/
|
|
137
|
+
_decryptChaCha20(encrypted, key, nonce, authTag) {
|
|
138
|
+
const decipher = crypto.createDecipheriv('chacha20-poly1305', key, nonce, {
|
|
139
|
+
authTagLength: 16
|
|
140
|
+
});
|
|
141
|
+
decipher.setAuthTag(authTag);
|
|
142
|
+
|
|
143
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Encrypt data using AES-256-GCM
|
|
148
|
+
*/
|
|
149
|
+
_encryptAES(data, key) {
|
|
150
|
+
const nonce = crypto.randomBytes(12); // 96-bit nonce
|
|
151
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
|
|
152
|
+
|
|
153
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
154
|
+
const authTag = cipher.getAuthTag();
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
encrypted,
|
|
158
|
+
nonce,
|
|
159
|
+
authTag
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Decrypt data using AES-256-GCM
|
|
165
|
+
*/
|
|
166
|
+
_decryptAES(encrypted, key, nonce, authTag) {
|
|
167
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
168
|
+
decipher.setAuthTag(authTag);
|
|
169
|
+
|
|
170
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Encrypt data
|
|
175
|
+
*/
|
|
176
|
+
encrypt(data) {
|
|
177
|
+
if (!this.keys || !this.keys.primary) {
|
|
178
|
+
throw new Error('Encryption keys not set');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.packetCount++;
|
|
182
|
+
|
|
183
|
+
// Check if key rotation is needed
|
|
184
|
+
if (this.keyRotationInterval && this.packetCount % this.keyRotationInterval === 0) {
|
|
185
|
+
// In production, trigger key rotation event
|
|
186
|
+
// For now, just reset counter
|
|
187
|
+
this.packetCount = 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let result;
|
|
191
|
+
|
|
192
|
+
switch (this.algorithm) {
|
|
193
|
+
case ALGORITHMS.CHACHA20_POLY1305:
|
|
194
|
+
result = this._encryptChaCha20(data, this.keys.primary);
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case ALGORITHMS.AES_256_GCM:
|
|
198
|
+
result = this._encryptAES(data, this.keys.primary);
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case ALGORITHMS.DOUBLE:
|
|
202
|
+
// First layer: AES-256-GCM
|
|
203
|
+
const firstLayer = this._encryptAES(data, this.keys.primary);
|
|
204
|
+
const firstLayerData = Buffer.concat([
|
|
205
|
+
firstLayer.nonce,
|
|
206
|
+
firstLayer.authTag,
|
|
207
|
+
firstLayer.encrypted
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
// Second layer: ChaCha20-Poly1305
|
|
211
|
+
result = this._encryptChaCha20(firstLayerData, this.keys.secondary);
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
default:
|
|
215
|
+
throw new Error(`Unknown algorithm: ${this.algorithm}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Return encrypted data with metadata
|
|
219
|
+
return {
|
|
220
|
+
data: result.encrypted,
|
|
221
|
+
nonce: result.nonce,
|
|
222
|
+
authTag: result.authTag,
|
|
223
|
+
algorithm: this.algorithm
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Decrypt data
|
|
229
|
+
*/
|
|
230
|
+
decrypt(encryptedData, nonce, authTag, algorithm) {
|
|
231
|
+
if (!this.keys || !this.keys.primary) {
|
|
232
|
+
throw new Error('Encryption keys not set');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
algorithm = algorithm || this.algorithm;
|
|
236
|
+
|
|
237
|
+
let decrypted;
|
|
238
|
+
|
|
239
|
+
switch (algorithm) {
|
|
240
|
+
case ALGORITHMS.CHACHA20_POLY1305:
|
|
241
|
+
decrypted = this._decryptChaCha20(encryptedData, this.keys.primary, nonce, authTag);
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
case ALGORITHMS.AES_256_GCM:
|
|
245
|
+
decrypted = this._decryptAES(encryptedData, this.keys.primary, nonce, authTag);
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
case ALGORITHMS.DOUBLE:
|
|
249
|
+
// First layer: ChaCha20-Poly1305
|
|
250
|
+
const firstDecrypted = this._decryptChaCha20(encryptedData, this.keys.secondary, nonce, authTag);
|
|
251
|
+
|
|
252
|
+
// Extract metadata from first layer
|
|
253
|
+
const innerNonce = firstDecrypted.slice(0, 12);
|
|
254
|
+
const innerAuthTag = firstDecrypted.slice(12, 28);
|
|
255
|
+
const innerEncrypted = firstDecrypted.slice(28);
|
|
256
|
+
|
|
257
|
+
// Second layer: AES-256-GCM
|
|
258
|
+
decrypted = this._decryptAES(innerEncrypted, this.keys.primary, innerNonce, innerAuthTag);
|
|
259
|
+
break;
|
|
260
|
+
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unknown algorithm: ${algorithm}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return decrypted;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate HMAC for authentication
|
|
270
|
+
*/
|
|
271
|
+
hmac(data) {
|
|
272
|
+
if (!this.keys || !this.keys.primary) {
|
|
273
|
+
throw new Error('Encryption keys not set');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const hmac = crypto.createHmac('sha256', this.keys.primary);
|
|
277
|
+
hmac.update(data);
|
|
278
|
+
return hmac.digest();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Verify HMAC (constant-time comparison)
|
|
283
|
+
*/
|
|
284
|
+
verifyHmac(data, expectedHmac) {
|
|
285
|
+
const computedHmac = this.hmac(data);
|
|
286
|
+
return crypto.timingSafeEqual(computedHmac, expectedHmac);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Rotate encryption keys
|
|
291
|
+
*/
|
|
292
|
+
rotateKeys() {
|
|
293
|
+
this.generateKeys();
|
|
294
|
+
this.packetCount = 0;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports = Encryption;
|
|
299
|
+
module.exports.ALGORITHMS = ALGORITHMS;
|