@balaji003/lantransfer 1.0.8 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balaji003/lantransfer",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "LAN File Transfer — peer-to-peer file sharing over local network. Zero dependencies.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -586,11 +586,13 @@
586
586
  extra += '</div>';
587
587
  }
588
588
 
589
+ const encrypted = (t.status === 'in_progress' || t.status === 'completed') ? ' \uD83D\uDD12' : '';
590
+
589
591
  return '<div class="transfer">'
590
592
  + '<div class="transfer-top">'
591
593
  + '<div class="transfer-info">'
592
594
  + '<span class="transfer-icon">' + icon + '</span>'
593
- + '<span>' + esc(t.filename) + ' ' + arrow + ' ' + esc(peer) + '</span>'
595
+ + '<span>' + esc(t.filename) + ' ' + arrow + ' ' + esc(peer) + encrypted + '</span>'
594
596
  + '</div>'
595
597
  + '<span class="badge ' + badgeClass + '">' + badgeText + '</span>'
596
598
  + '</div>'
package/server.js CHANGED
@@ -4,10 +4,9 @@
4
4
  * server.js — LAN File Transfer (Node.js rewrite)
5
5
  *
6
6
  * Zero external dependencies. Uses built-in modules only.
7
- * Protocol-compatible with the original Rust version:
8
- * - UDP discovery on port 34254
7
+ * - HTTP + UDP discovery
9
8
  * - TCP file transfer on port 34255
10
- * - XOR obfuscation with key "LAN-XFER-KEY-2024"
9
+ * - E2E encryption: ECDH key exchange + AES-256-CTR
11
10
  *
12
11
  * Run: node server.js
13
12
  * Then open http://localhost:3000 in a browser.
@@ -16,6 +15,7 @@
16
15
  const http = require('http');
17
16
  const fs = require('fs');
18
17
  const path = require('path');
18
+ const crypto = require('crypto');
19
19
  const dgram = require('dgram');
20
20
  const net = require('net');
21
21
  const os = require('os');
@@ -27,9 +27,9 @@ const { exec } = require('child_process');
27
27
  const DISCOVERY_PORT = 34254;
28
28
  const TRANSFER_PORT = 34255;
29
29
  const HTTP_PORT = 3000;
30
- const XOR_KEY = Buffer.from('LAN-XFER-KEY-2024');
31
30
  const PEER_TIMEOUT = 10_000; // ms
32
31
  const BROADCAST_INTERVAL = 3_000; // ms
32
+ const ECDH_CURVE = 'prime256v1'; // NIST P-256
33
33
 
34
34
  // ──────────────────────────────────────────────────────────────────────────────
35
35
  // Persistent config (device name)
@@ -128,14 +128,18 @@ function broadcast() {
128
128
  }
129
129
 
130
130
  // ──────────────────────────────────────────────────────────────────────────────
131
- // Helpers
131
+ // Helpers — E2E encryption (ECDH + AES-256-CTR)
132
132
  // ──────────────────────────────────────────────────────────────────────────────
133
- function xorCrypt(buf, offset) {
134
- const out = Buffer.allocUnsafe(buf.length);
135
- for (let i = 0; i < buf.length; i++) {
136
- out[i] = buf[i] ^ XOR_KEY[(offset + i) % XOR_KEY.length];
137
- }
138
- return out;
133
+ function createKeyPair() {
134
+ const ecdh = crypto.createECDH(ECDH_CURVE);
135
+ ecdh.generateKeys();
136
+ return ecdh;
137
+ }
138
+
139
+ function deriveKey(ecdh, peerPubKey) {
140
+ const shared = ecdh.computeSecret(peerPubKey);
141
+ // SHA-256 the shared secret to get a 32-byte AES key
142
+ return crypto.createHash('sha256').update(shared).digest();
139
143
  }
140
144
 
141
145
  function readBody(req) {
@@ -536,12 +540,32 @@ async function handleIncoming(socket) {
536
540
  t.savePath = savePath;
537
541
  console.log(` [recv] Saving to: ${savePath}`);
538
542
 
543
+ // ── E2E key exchange: receive sender's public key, send ours ──
544
+ const pubKeyLenBuf = await readExact(socket, 2);
545
+ const pubKeyLen = pubKeyLenBuf.readUInt16BE(0);
546
+ const senderPubKey = await readExact(socket, pubKeyLen);
547
+ console.log(` [recv] Got sender public key (${pubKeyLen} bytes)`);
548
+
549
+ const ecdh = createKeyPair();
550
+ const myPubKey = ecdh.getPublicKey();
551
+ const keyLenBuf = Buffer.alloc(2);
552
+ keyLenBuf.writeUInt16BE(myPubKey.length);
553
+ socket.write(keyLenBuf);
554
+ socket.write(myPubKey);
555
+ console.log(` [recv] Sent our public key (${myPubKey.length} bytes)`);
556
+
557
+ // Derive AES key + receive IV
558
+ const aesKey = deriveKey(ecdh, senderPubKey);
559
+ const iv = await readExact(socket, 16);
560
+ console.log(` [recv] AES key derived, IV received — decrypting with AES-256-CTR`);
561
+
562
+ const decipher = crypto.createDecipheriv('aes-256-ctr', aesKey, iv);
539
563
  const ws = fs.createWriteStream(savePath);
540
564
  let received = 0;
541
565
  let lastBc = 0;
542
566
 
543
567
  socket.on('data', chunk => {
544
- const decrypted = xorCrypt(chunk, received);
568
+ const decrypted = decipher.update(chunk);
545
569
  ws.write(decrypted);
546
570
  received += chunk.length;
547
571
 
@@ -558,6 +582,8 @@ async function handleIncoming(socket) {
558
582
 
559
583
  await new Promise((resolve, reject) => {
560
584
  socket.on('end', () => {
585
+ const final = decipher.final();
586
+ if (final.length > 0) ws.write(final);
561
587
  ws.end();
562
588
  t.status = 'completed';
563
589
  t.progress = 100;
@@ -595,6 +621,7 @@ async function sendFile(peer, file) {
595
621
  socket.connect(peer.tcp_port, peer.ip, res);
596
622
  socket.once('error', rej);
597
623
  });
624
+ console.log(` [send] Connected to ${peer.ip}:${peer.tcp_port}`);
598
625
 
599
626
  // Send header
600
627
  const headerJSON = JSON.stringify({
@@ -613,20 +640,44 @@ async function sendFile(peer, file) {
613
640
  t.error = 'Rejected by recipient';
614
641
  broadcast();
615
642
  socket.end();
643
+ console.log(` [send] Rejected by ${peer.device_name}`);
616
644
  return;
617
645
  }
618
646
 
647
+ console.log(` [send] Accepted — starting E2E key exchange`);
648
+
649
+ // ── E2E key exchange: send our public key, receive receiver's ──
650
+ const ecdh = createKeyPair();
651
+ const myPubKey = ecdh.getPublicKey();
652
+ const keyLenBuf = Buffer.alloc(2);
653
+ keyLenBuf.writeUInt16BE(myPubKey.length);
654
+ socket.write(keyLenBuf);
655
+ socket.write(myPubKey);
656
+ console.log(` [send] Sent our public key (${myPubKey.length} bytes)`);
657
+
658
+ const recvKeyLenBuf = await readExact(socket, 2);
659
+ const recvKeyLen = recvKeyLenBuf.readUInt16BE(0);
660
+ const recvPubKey = await readExact(socket, recvKeyLen);
661
+ console.log(` [send] Got receiver public key (${recvKeyLen} bytes)`);
662
+
663
+ // Derive AES key, generate IV, send IV
664
+ const aesKey = deriveKey(ecdh, recvPubKey);
665
+ const iv = crypto.randomBytes(16);
666
+ socket.write(iv);
667
+ console.log(` [send] AES key derived, IV sent — encrypting with AES-256-CTR`);
668
+
619
669
  t.status = 'in_progress';
620
670
  t._start = Date.now();
621
671
  broadcast();
622
672
 
623
- // Stream file with XOR encryption
673
+ // Stream file with AES-256-CTR encryption
674
+ const cipher = crypto.createCipheriv('aes-256-ctr', aesKey, iv);
624
675
  const rs = fs.createReadStream(file.path, { highWaterMark: 65536 });
625
676
  let sent = 0;
626
677
  let lastBc = 0;
627
678
 
628
679
  for await (const chunk of rs) {
629
- const encrypted = xorCrypt(Buffer.from(chunk), sent);
680
+ const encrypted = cipher.update(Buffer.from(chunk));
630
681
  const ok = socket.write(encrypted);
631
682
  sent += chunk.length;
632
683
 
@@ -649,16 +700,20 @@ async function sendFile(peer, file) {
649
700
  }
650
701
  }
651
702
 
703
+ // Write final cipher block
704
+ const final = cipher.final();
705
+ if (final.length > 0) socket.write(final);
706
+
652
707
  socket.end();
653
708
  t.status = 'completed';
654
709
  t.progress = 100;
655
710
  broadcast();
656
- console.log(` Sent: ${file.name} → ${peer.device_name}`);
711
+ console.log(` [send] Complete: ${file.name} → ${peer.device_name}`);
657
712
  } catch (e) {
658
713
  t.status = 'failed';
659
714
  t.error = e.message;
660
715
  broadcast();
661
- console.error(` Send error: ${e.message}`);
716
+ console.error(` [send] Error: ${e.message}`);
662
717
  }
663
718
  }
664
719
 
@@ -770,6 +825,7 @@ server.listen(HTTP_PORT, () => {
770
825
  console.log(` Discovery HTTP scan (no firewall needed)`);
771
826
  console.log(` Discovery UDP :${DISCOVERY_PORT} (fallback)`);
772
827
  console.log(` Transfer TCP :${TRANSFER_PORT}`);
828
+ console.log(` Encryption ECDH + AES-256-CTR (E2E)`);
773
829
  console.log(` UI: http://localhost:${HTTP_PORT}`);
774
830
  console.log('');
775
831