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>
This commit is contained in:
parent
a67fa0eead
commit
df06b39e88
109
index.js
109
index.js
@ -1,11 +1,41 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { createConnection } = require('net');
|
const { createConnection } = require('net');
|
||||||
const { createDiffieHellman, createDiffieHellmanGroup } = require('crypto');
|
const { createHash, createECDH } = require('crypto');
|
||||||
|
const { Transform } = require('stream');
|
||||||
|
|
||||||
|
// Application settings
|
||||||
const port = process.argv[3] || 23;
|
const port = process.argv[3] || 23;
|
||||||
const hostname = process.argv[2] || (process.pkg ? "dom.ht-dev.de" : "localhost");
|
const hostname = process.argv[2] || (process.pkg ? "dom.ht-dev.de" : "localhost");
|
||||||
const delayMs = process.argv[4] || 100; // Command delay in milliseconds
|
let delayMs = process.argv[4] || 100; // Command delay in milliseconds
|
||||||
|
|
||||||
|
// Diffie-Hellman parameters
|
||||||
|
const keyCurve = "prime256v1"; // key exchange curve, make this negotiable in the future
|
||||||
|
|
||||||
|
class MemoryStream extends Transform {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(chunk, encoding, callback) {
|
||||||
|
this.push(chunk);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writer = new MemoryStream();
|
||||||
|
let encrypted = false; // Encryption status, do not modify directly, runtime only
|
||||||
|
let privateKey; // Private key, do not modify directly, runtime only
|
||||||
|
const key = createECDH(keyCurve);
|
||||||
|
|
||||||
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
const send = async (command) => {
|
||||||
|
await delay(delayMs);
|
||||||
|
writer.write(command); // Command
|
||||||
|
await delay(delayMs);
|
||||||
|
writer.write("\r\n"); // Line Feed
|
||||||
|
};
|
||||||
|
|
||||||
// Numeric Buffer values
|
// Numeric Buffer values
|
||||||
const NUL = Buffer.from([0x00]); // Null
|
const NUL = Buffer.from([0x00]); // Null
|
||||||
@ -65,13 +95,15 @@ if (process.pkg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.pkg) {
|
if (process.pkg) {
|
||||||
console.debug = () => {}; // Disable debug logging when run as an executable
|
console.debug = () => { }; // Disable debug logging when run as an executable
|
||||||
fs.writeFileSync(path.join(project_folder, "README.md"), fs.readFileSync(path.join(__dirname, "README.md"))); // copy newest readme to folder
|
fs.writeFileSync(path.join(project_folder, "README.md"), fs.readFileSync(path.join(__dirname, "README.md"))); // copy newest readme to folder
|
||||||
fs.writeFileSync(path.join(project_folder, "LICENSE"), fs.readFileSync(path.join(__dirname, "LICENSE"))); // copy newest license to folder
|
fs.writeFileSync(path.join(project_folder, "LICENSE"), fs.readFileSync(path.join(__dirname, "LICENSE"))); // copy newest license to folder
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket = createConnection(port, hostname);
|
const socket = createConnection(port, hostname);
|
||||||
|
|
||||||
|
writer.pipe(socket);
|
||||||
|
|
||||||
let hold = false; // Hold input
|
let hold = false; // Hold input
|
||||||
|
|
||||||
process.stdin.setEncoding("ascii");
|
process.stdin.setEncoding("ascii");
|
||||||
@ -123,26 +155,46 @@ process.stdin.on('data', async (key) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("data", (data) => {
|
socket.on("data", (data) => {
|
||||||
if (data.equals(PAUSE)) {
|
if (!encrypted) {
|
||||||
process.stdin.pause();
|
if (data.equals(PAUSE)) {
|
||||||
} else if (data.equals(RESUME)) {
|
process.stdin.pause();
|
||||||
process.stdin.resume();
|
} else if (data.equals(RESUME)) {
|
||||||
} else if (data.equals(IP)) {
|
process.stdin.resume();
|
||||||
process.exit();
|
} else if (data.equals(IP)) {
|
||||||
} else if (data.equals(Buffer.concat([IAC, DO, CUSTOM_CLIENT_INIT]))) {
|
process.exit();
|
||||||
// server supports custom client features
|
} else if (data.equals(Buffer.concat([IAC, DO, CUSTOM_CLIENT_INIT]))) {
|
||||||
socket.write(Buffer.concat([IAC, WILL, KEY_EXCHANGE])); // Start Key Exchange
|
// server supports custom client features
|
||||||
} else if (data.includes(Buffer.concat([IAC, DO, KEY_EXCHANGE]))) {
|
socket.write(Buffer.concat([IAC, WILL, KEY_EXCHANGE])); // Start Key Exchange
|
||||||
// generate keys
|
} else if (data.includes(Buffer.concat([IAC, DO, KEY_EXCHANGE]))) {
|
||||||
} else if (data.equals(Buffer.concat([IAC, SB, KEY_EXCHANGE, ONE /* value required */, IAC, SE]))) {
|
// generate keys
|
||||||
// send key to server
|
const publicKey = key.generateKeys();
|
||||||
} else {
|
console.debug("Generated Key: " + publicKey.toString("hex"));
|
||||||
process.stdout.write(data);
|
} else if (data.equals(Buffer.concat([IAC, SB, KEY_EXCHANGE, ONE /* value required */, IAC, SE]))) {
|
||||||
|
socket.write(Buffer.concat([IAC, SB, KEY_EXCHANGE, NUL, key.getPublicKey(), IAC, SE]));
|
||||||
|
// send key to server
|
||||||
|
} else if (data.includes(Buffer.concat([IAC, SB, KEY_EXCHANGE, NUL /* value provided */]))) {
|
||||||
|
// server sent its key, generate secret
|
||||||
|
console.debug("Key exchange received");
|
||||||
|
const offsetBegin = data.indexOf(SB) + 2;
|
||||||
|
const offsetEnd = data.lastIndexOf(SE) - 1;
|
||||||
|
const keyData = data.subarray(offsetBegin + 1, offsetEnd); // client public key
|
||||||
|
console.log("Extracted key:", keyData.toString("hex"));
|
||||||
|
privateKey = key.computeSecret(keyData);
|
||||||
|
socket.write(Buffer.concat([IAC, WILL, ENCRYPTION])); // Enable Encryption
|
||||||
|
} else if (data.equals(Buffer.concat([IAC, DO, ENCRYPTION]))) {
|
||||||
|
// enable encryption
|
||||||
|
encrypted = true;
|
||||||
|
console.debug("Encryption enabled");
|
||||||
|
console.debug("Private Key: " + privateKey.toString("hex"));
|
||||||
|
} else {
|
||||||
|
process.stdout.write(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("connect", async () => {
|
socket.on("connect", async () => {
|
||||||
console.log("Connected to server");
|
console.log("Connected to server");
|
||||||
|
process.stdin.pause(); // Pause input
|
||||||
|
|
||||||
// initialization
|
// initialization
|
||||||
socket.write(Buffer.concat([IAC, WILL, NAWS, SB, NAWS, Buffer.from([/* 24x80 */ 0x00, 0x18, 0x00, 0x50]), SE])); // Negotiate About Window Size
|
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 () => {
|
|||||||
socket.write(Buffer.concat([IAC, DO, ECHO])); // Echo
|
socket.write(Buffer.concat([IAC, DO, ECHO])); // Echo
|
||||||
await delay(delayMs);
|
await delay(delayMs);
|
||||||
socket.write(Buffer.concat([IAC, WILL, CUSTOM_CLIENT_INIT])); // Custom Client Initialization
|
socket.write(Buffer.concat([IAC, WILL, CUSTOM_CLIENT_INIT])); // Custom Client Initialization
|
||||||
await delay(delayMs * 10);
|
await delay(delayMs);
|
||||||
socket.write(Buffer.from([0x0d, 0x0a])); // Line Feed
|
// socket.write(Buffer.from([0x0d, 0x0a])); // Line Feed
|
||||||
|
if (encrypted) {
|
||||||
|
// increase delay for encryption
|
||||||
|
delayMs += 500;
|
||||||
|
}
|
||||||
|
// from here on encryption is enabled, do not use socket.write() directly anymore
|
||||||
await delay(delayMs);
|
await delay(delayMs);
|
||||||
// initialization complete
|
// initialization complete
|
||||||
|
|
||||||
await send("help");
|
// await send("help");
|
||||||
await delay(500);
|
await delay(500);
|
||||||
process.stdout.write("\rCtrl+X for client side commands\r\nCtrl+C to exit, Ctrl+D to force close\r\n> ");
|
process.stdout.write("\rCtrl+X for client side commands\r\nCtrl+C to exit, Ctrl+D to force close\r\n> ");
|
||||||
|
process.stdin.resume(); // Resume input
|
||||||
// more commands can be added here
|
// more commands can be added here
|
||||||
|
|
||||||
|
|
||||||
@ -170,15 +228,6 @@ socket.on("end", () => {
|
|||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
const send = async (command) => {
|
|
||||||
await delay(delayMs);
|
|
||||||
socket.write(command); // Command
|
|
||||||
await delay(delayMs);
|
|
||||||
socket.write("\r\n"); // Line Feed
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
delay,
|
delay,
|
||||||
send,
|
send,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user