@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 +1 -1
- package/public/index.html +3 -1
- package/server.js +72 -16
package/package.json
CHANGED
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
|
-
*
|
|
8
|
-
* - UDP discovery on port 34254
|
|
7
|
+
* - HTTP + UDP discovery
|
|
9
8
|
* - TCP file transfer on port 34255
|
|
10
|
-
* -
|
|
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
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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(`
|
|
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(`
|
|
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
|
|