Skip to content
This repository was archived by the owner on Oct 11, 2025. It is now read-only.

Commit df06b39

Browse files
committed
Enhance Telnet client with ECDH key exchange and encryption support; refactor command sending logic and improve input handling
Signed-off-by: Hammer1279 <hammer@ht-dev.de>
1 parent a67fa0e commit df06b39

File tree

1 file changed

+79
-30
lines changed

1 file changed

+79
-30
lines changed

index.js

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
11
const fs = require("fs");
22
const path = require("path");
33
const { createConnection } = require('net');
4-
const { createDiffieHellman, createDiffieHellmanGroup } = require('crypto');
4+
const { createHash, createECDH } = require('crypto');
5+
const { Transform } = require('stream');
56

7+
// Application settings
68
const port = process.argv[3] || 23;
79
const hostname = process.argv[2] || (process.pkg ? "dom.ht-dev.de" : "localhost");
8-
const delayMs = process.argv[4] || 100; // Command delay in milliseconds
10+
let delayMs = process.argv[4] || 100; // Command delay in milliseconds
11+
12+
// Diffie-Hellman parameters
13+
const keyCurve = "prime256v1"; // key exchange curve, make this negotiable in the future
14+
15+
class MemoryStream extends Transform {
16+
constructor(options = {}) {
17+
super(options);
18+
}
19+
20+
_transform(chunk, encoding, callback) {
21+
this.push(chunk);
22+
callback();
23+
}
24+
}
25+
26+
const writer = new MemoryStream();
27+
let encrypted = false; // Encryption status, do not modify directly, runtime only
28+
let privateKey; // Private key, do not modify directly, runtime only
29+
const key = createECDH(keyCurve);
30+
31+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
32+
33+
const send = async (command) => {
34+
await delay(delayMs);
35+
writer.write(command); // Command
36+
await delay(delayMs);
37+
writer.write("\r\n"); // Line Feed
38+
};
939

1040
// Numeric Buffer values
1141
const NUL = Buffer.from([0x00]); // Null
@@ -65,13 +95,15 @@ if (process.pkg) {
6595
}
6696

6797
if (process.pkg) {
68-
console.debug = () => {}; // Disable debug logging when run as an executable
98+
console.debug = () => { }; // Disable debug logging when run as an executable
6999
fs.writeFileSync(path.join(project_folder, "README.md"), fs.readFileSync(path.join(__dirname, "README.md"))); // copy newest readme to folder
70100
fs.writeFileSync(path.join(project_folder, "LICENSE"), fs.readFileSync(path.join(__dirname, "LICENSE"))); // copy newest license to folder
71101
}
72102

73103
const socket = createConnection(port, hostname);
74104

105+
writer.pipe(socket);
106+
75107
let hold = false; // Hold input
76108

77109
process.stdin.setEncoding("ascii");
@@ -123,26 +155,46 @@ process.stdin.on('data', async (key) => {
123155
});
124156

125157
socket.on("data", (data) => {
126-
if (data.equals(PAUSE)) {
127-
process.stdin.pause();
128-
} else if (data.equals(RESUME)) {
129-
process.stdin.resume();
130-
} else if (data.equals(IP)) {
131-
process.exit();
132-
} else if (data.equals(Buffer.concat([IAC, DO, CUSTOM_CLIENT_INIT]))) {
133-
// server supports custom client features
134-
socket.write(Buffer.concat([IAC, WILL, KEY_EXCHANGE])); // Start Key Exchange
135-
} else if (data.includes(Buffer.concat([IAC, DO, KEY_EXCHANGE]))) {
136-
// generate keys
137-
} else if (data.equals(Buffer.concat([IAC, SB, KEY_EXCHANGE, ONE /* value required */, IAC, SE]))) {
138-
// send key to server
139-
} else {
140-
process.stdout.write(data);
158+
if (!encrypted) {
159+
if (data.equals(PAUSE)) {
160+
process.stdin.pause();
161+
} else if (data.equals(RESUME)) {
162+
process.stdin.resume();
163+
} else if (data.equals(IP)) {
164+
process.exit();
165+
} else if (data.equals(Buffer.concat([IAC, DO, CUSTOM_CLIENT_INIT]))) {
166+
// server supports custom client features
167+
socket.write(Buffer.concat([IAC, WILL, KEY_EXCHANGE])); // Start Key Exchange
168+
} else if (data.includes(Buffer.concat([IAC, DO, KEY_EXCHANGE]))) {
169+
// generate keys
170+
const publicKey = key.generateKeys();
171+
console.debug("Generated Key: " + publicKey.toString("hex"));
172+
} else if (data.equals(Buffer.concat([IAC, SB, KEY_EXCHANGE, ONE /* value required */, IAC, SE]))) {
173+
socket.write(Buffer.concat([IAC, SB, KEY_EXCHANGE, NUL, key.getPublicKey(), IAC, SE]));
174+
// send key to server
175+
} else if (data.includes(Buffer.concat([IAC, SB, KEY_EXCHANGE, NUL /* value provided */]))) {
176+
// server sent its key, generate secret
177+
console.debug("Key exchange received");
178+
const offsetBegin = data.indexOf(SB) + 2;
179+
const offsetEnd = data.lastIndexOf(SE) - 1;
180+
const keyData = data.subarray(offsetBegin + 1, offsetEnd); // client public key
181+
console.log("Extracted key:", keyData.toString("hex"));
182+
privateKey = key.computeSecret(keyData);
183+
socket.write(Buffer.concat([IAC, WILL, ENCRYPTION])); // Enable Encryption
184+
} else if (data.equals(Buffer.concat([IAC, DO, ENCRYPTION]))) {
185+
// enable encryption
186+
encrypted = true;
187+
console.debug("Encryption enabled");
188+
console.debug("Private Key: " + privateKey.toString("hex"));
189+
} else {
190+
process.stdout.write(data);
191+
}
141192
}
142193
});
143194

144195
socket.on("connect", async () => {
145196
console.log("Connected to server");
197+
process.stdin.pause(); // Pause input
146198

147199
// initialization
148200
socket.write(Buffer.concat([IAC, WILL, NAWS, SB, NAWS, Buffer.from([/* 24x80 */ 0x00, 0x18, 0x00, 0x50]), SE])); // Negotiate About Window Size
@@ -152,14 +204,20 @@ socket.on("connect", async () => {
152204
socket.write(Buffer.concat([IAC, DO, ECHO])); // Echo
153205
await delay(delayMs);
154206
socket.write(Buffer.concat([IAC, WILL, CUSTOM_CLIENT_INIT])); // Custom Client Initialization
155-
await delay(delayMs * 10);
156-
socket.write(Buffer.from([0x0d, 0x0a])); // Line Feed
207+
await delay(delayMs);
208+
// socket.write(Buffer.from([0x0d, 0x0a])); // Line Feed
209+
if (encrypted) {
210+
// increase delay for encryption
211+
delayMs += 500;
212+
}
213+
// from here on encryption is enabled, do not use socket.write() directly anymore
157214
await delay(delayMs);
158215
// initialization complete
159216

160-
await send("help");
217+
// await send("help");
161218
await delay(500);
162219
process.stdout.write("\rCtrl+X for client side commands\r\nCtrl+C to exit, Ctrl+D to force close\r\n> ");
220+
process.stdin.resume(); // Resume input
163221
// more commands can be added here
164222

165223

@@ -170,15 +228,6 @@ socket.on("end", () => {
170228
process.exit();
171229
});
172230

173-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
174-
175-
const send = async (command) => {
176-
await delay(delayMs);
177-
socket.write(command); // Command
178-
await delay(delayMs);
179-
socket.write("\r\n"); // Line Feed
180-
};
181-
182231
module.exports = {
183232
delay,
184233
send,

0 commit comments

Comments
 (0)