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