@manch1kz/yanac 1.0.1

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/README.md ADDED
@@ -0,0 +1,33 @@
1
+ ## Installation
2
+ ```npm install -g @manch1kz/yanac```
3
+
4
+ ## Using
5
+ ```npm i``` before using if you using this from the source code
6
+
7
+ Starting the server:
8
+ ```
9
+ yanac start server [options]
10
+ ```
11
+
12
+ Starting the сlient:
13
+ ```
14
+ yanac start client [options]
15
+ ```
16
+
17
+ Options for server:
18
+ * ```--port <limit>``` - port (default: 3000)
19
+ * ```--limit <limit>``` - chat maximum size (default: 100)
20
+
21
+ Options for client:
22
+ * ```--port <limit>``` - port (default: 3000)
23
+ * ```--host <limit>``` - host (default: default)
24
+ * ```--name <limit>``` - port (default: random_string)
25
+
26
+ ## Communication demo
27
+ https://github.com/user-attachments/assets/37b0010c-6ea2-436c-96b0-f66d71bb3a7a
28
+
29
+
30
+
31
+
32
+
33
+
package/app.ts ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander"
4
+ import { Server } from "./server/serverInstance"
5
+ import { Client } from "./client/clientInstance"
6
+
7
+ const program = new Command()
8
+
9
+ program.command("start <type>")
10
+ .option("--port <port>", "Server port (default: 3000)")
11
+ .option("--host <host>", "Server host (default: localhost)")
12
+ .option("--name <name>", "Client name (default: random_str)")
13
+ .option("--limit <limit>", "Limit of chat storage (default: 100)")
14
+ .action((str, options) => {
15
+ switch (str) {
16
+ case "server":
17
+ const server = new Server(
18
+ Number(options.limit) ? Number(options.limit) : undefined,
19
+ Number(options.port) ? Number(options.port) : undefined
20
+ )
21
+ break;
22
+ case "client":
23
+ const client = new Client(options.name, options.host, Number(options.port) ? Number(options.port) : undefined)
24
+ break;
25
+ }
26
+ })
27
+
28
+ program.parse()
29
+
package/build/app.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const serverInstance_1 = require("./server/serverInstance");
6
+ const clientInstance_1 = require("./client/clientInstance");
7
+ const program = new commander_1.Command();
8
+ program.command("start <type>")
9
+ .option("--port <port>", "Server port (default: 3000)")
10
+ .option("--host <host>", "Server host (default: localhost)")
11
+ .option("--name <name>", "Client name (default: random_str)")
12
+ .option("--limit <limit>", "Limit of chat storage (default: 100)")
13
+ .action((str, options) => {
14
+ switch (str) {
15
+ case "server":
16
+ const server = new serverInstance_1.Server(Number(options.limit) ? Number(options.limit) : undefined, Number(options.port) ? Number(options.port) : undefined);
17
+ break;
18
+ case "client":
19
+ const client = new clientInstance_1.Client(options.name, options.host, Number(options.port) ? Number(options.port) : undefined);
20
+ break;
21
+ }
22
+ });
23
+ program.parse();
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Client = void 0;
7
+ const net_1 = __importDefault(require("net"));
8
+ const promises_1 = __importDefault(require("readline/promises"));
9
+ const sessionManager_1 = require("../security/sessionManager");
10
+ const processMessage_1 = require("./handlers/processMessage");
11
+ const mainInputHandler_1 = require("./handlers/mainInputHandler");
12
+ const genNameAlias_1 = require("../utils/genNameAlias");
13
+ class Client {
14
+ manager;
15
+ rl;
16
+ name;
17
+ server;
18
+ key_generated;
19
+ constructor(name = (0, genNameAlias_1.genName)(), host = "localhost", port = 3000) {
20
+ this.name = name;
21
+ this.server = net_1.default.connect({ host: host, port: port });
22
+ this.key_generated = false;
23
+ this.manager = new sessionManager_1.SessionManager();
24
+ this.rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout, prompt: `${this.name} >>> ` });
25
+ this.server.on("connect", () => {
26
+ this.server.write(JSON.stringify({ type: 'public_key_request', body: this.manager.getClient() }));
27
+ });
28
+ this.server.on("data", (data) => {
29
+ processMessage_1.processMessage.call(this, data);
30
+ });
31
+ this.rl.on("line", mainInputHandler_1.onLine.bind(this));
32
+ this.server.on("close", () => { this.server.end(); process.exit(); });
33
+ this.rl.on("SIGINT", () => { this.server.end(); process.exit(); });
34
+ }
35
+ }
36
+ exports.Client = Client;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.onLine = onLine;
4
+ const edUtils_1 = require("../../utils/edUtils");
5
+ function onLine(line) {
6
+ if (line.length > 0) {
7
+ process.stdout.moveCursor(0, -1);
8
+ process.stdout.write('\r\x1B[K');
9
+ console.log(`${this.name}: ${line}`);
10
+ if (line == "/exit") {
11
+ this.rl.emit("SIGINT");
12
+ }
13
+ this.server.write((0, edUtils_1.encrypt)(JSON.stringify({ type: 'msg', body: [this.name, line] }), this.manager.session_key));
14
+ this.rl.prompt();
15
+ }
16
+ else {
17
+ process.stdout.moveCursor(0, -1);
18
+ process.stdout.write('\r\x1B[K');
19
+ this.rl.prompt();
20
+ }
21
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processMessage = processMessage;
4
+ const edUtils_1 = require("../../utils/edUtils");
5
+ async function processMessage(data) {
6
+ const cur = this.rl.getCursorPos();
7
+ let processed;
8
+ if (this.key_generated) {
9
+ processed = JSON.parse((0, edUtils_1.decrypt)(data.toString(), this.manager.session_key));
10
+ }
11
+ else {
12
+ processed = JSON.parse(data.toString('utf-8'));
13
+ }
14
+ switch (processed.type) {
15
+ case 'public_key_response':
16
+ this.manager.setServer(processed.body[1]);
17
+ this.server.write(JSON.stringify({ type: 'master_key_response', body: [this.manager.getMaster(processed.body[0]), this.name] }));
18
+ break;
19
+ case 'finished':
20
+ this.manager.generateSession();
21
+ this.key_generated = true;
22
+ this.server.write((0, edUtils_1.encrypt)(JSON.stringify({ type: "finished", body: this.name }), this.manager.session_key));
23
+ this.rl.prompt();
24
+ break;
25
+ case 'history':
26
+ process.stdout.write('\r\x1B[K');
27
+ for (let i = 0; i < processed.body.length; i++) {
28
+ console.log(processed.body[i][0] + ':', processed.body[i][1]);
29
+ }
30
+ console.log('[SERVER]:', this.name, 'connected to server');
31
+ this.rl.prompt(true);
32
+ process.stdout.cursorTo(cur.cols);
33
+ break;
34
+ case 'msg':
35
+ process.stdout.write('\r\x1B[K');
36
+ console.log(processed.data[0] + ':', processed.data[1]);
37
+ this.rl.prompt(true);
38
+ process.stdout.cursorTo(cur.cols);
39
+ break;
40
+ }
41
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SessionManager = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ class SessionManager {
9
+ client_random;
10
+ server_random;
11
+ master_random;
12
+ session_key;
13
+ privateKey;
14
+ publicKey;
15
+ constructor() {
16
+ this.client_random = null;
17
+ this.server_random = null;
18
+ this.master_random = null;
19
+ this.session_key = null;
20
+ const { publicKey, privateKey } = crypto_1.default.generateKeyPairSync('rsa', {
21
+ modulusLength: 2048,
22
+ publicKeyEncoding: {
23
+ format: 'pem',
24
+ type: 'pkcs1'
25
+ },
26
+ privateKeyEncoding: {
27
+ format: 'pem',
28
+ type: 'pkcs1'
29
+ }
30
+ });
31
+ this.privateKey = privateKey;
32
+ this.publicKey = publicKey;
33
+ }
34
+ generateSession() {
35
+ if (this.client_random && this.server_random && this.master_random) {
36
+ const sum = this.client_random + this.server_random + Number(this.master_random);
37
+ this.session_key = crypto_1.default.createHash('sha256').update(String(sum)).digest().toString('hex');
38
+ }
39
+ }
40
+ setClient(client_random) {
41
+ this.client_random = client_random;
42
+ }
43
+ getClient() {
44
+ if (this.client_random == null) {
45
+ this.client_random = Math.floor(Math.random() * 5000);
46
+ }
47
+ return this.client_random;
48
+ }
49
+ getServer() {
50
+ if (this.server_random == null) {
51
+ this.server_random = Math.floor(Math.random() * 5000);
52
+ }
53
+ return this.server_random;
54
+ }
55
+ setServer(server_random) {
56
+ this.server_random = server_random;
57
+ }
58
+ getMaster(public_key) {
59
+ if (this.master_random == null) {
60
+ this.master_random = String(Math.floor(Math.random() * 5000));
61
+ }
62
+ return crypto_1.default.publicEncrypt({
63
+ key: public_key,
64
+ padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
65
+ }, this.master_random).toString('base64');
66
+ }
67
+ setMaster(master_random, private_key) {
68
+ this.master_random = crypto_1.default.privateDecrypt({
69
+ key: private_key,
70
+ padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
71
+ }, Buffer.from(master_random, 'base64')).toString();
72
+ }
73
+ }
74
+ exports.SessionManager = SessionManager;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processMessage = processMessage;
4
+ const edUtils_1 = require("../../utils/edUtils");
5
+ function processMessage(socket, data) {
6
+ const socket_item = this.connections.get(socket);
7
+ const socket_generator = socket_item.generator;
8
+ let message;
9
+ if (socket_item.key_generated) {
10
+ message = JSON.parse((0, edUtils_1.decrypt)(data.toString(), socket_item.generator.session_key));
11
+ }
12
+ else {
13
+ message = JSON.parse(data.toString());
14
+ }
15
+ switch (message.type) {
16
+ case 'msg':
17
+ this.localChatStore.push(message.body);
18
+ Array.from(this.connections.keys()).forEach((i) => {
19
+ if (i != socket) {
20
+ i.write((0, edUtils_1.encrypt)(JSON.stringify({ type: 'msg', data: message.body }), this.connections.get(i).generator.session_key));
21
+ }
22
+ });
23
+ break;
24
+ case 'public_key_request':
25
+ const server_random = this.manager.getServer();
26
+ socket_generator.setClient(message.body);
27
+ socket_generator.setServer(server_random);
28
+ socket.write(JSON.stringify({ type: 'public_key_response', body: [this.manager.publicKey, server_random] }));
29
+ break;
30
+ case 'master_key_response':
31
+ socket_generator.setMaster(message.body[0], this.manager.privateKey);
32
+ socket_generator.generateSession();
33
+ socket_item.name = message.body[1];
34
+ socket_item.key_generated = true;
35
+ socket.write(JSON.stringify({ type: 'finished' }));
36
+ break;
37
+ case 'finished':
38
+ socket_item.name = message.body;
39
+ socket.write((0, edUtils_1.encrypt)(JSON.stringify({ type: 'history', body: this.localChatStore.getChat() }), socket_generator.session_key));
40
+ this.localChatStore.push(['[SERVER]', message.body + ' connected to server']);
41
+ Array.from(this.connections.keys()).forEach((i) => {
42
+ if (i != socket) {
43
+ i.write((0, edUtils_1.encrypt)(JSON.stringify({ type: 'msg', data: ['[SERVER]', message.body + ' connected to server'] }), this.connections.get(i).generator.session_key));
44
+ }
45
+ });
46
+ break;
47
+ }
48
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupSocket = setupSocket;
4
+ const sessionManager_1 = require("../../security/sessionManager");
5
+ const edUtils_1 = require("../../utils/edUtils");
6
+ function setupSocket(socket, cb) {
7
+ this.connections.set(socket, { name: null, generator: new sessionManager_1.SessionManager(), key_generated: false });
8
+ socket.on('close', () => {
9
+ let body = ['[SERVER]', this.connections.get(socket).name + ' disconnected from server'];
10
+ this.localChatStore.push(body);
11
+ Array.from(this.connections.keys()).forEach((i) => {
12
+ i.write((0, edUtils_1.encrypt)(JSON.stringify({ type: 'msg', data: body }), this.connections.get(i).generator.session_key));
13
+ });
14
+ this.connections.delete(socket);
15
+ });
16
+ socket.on("error", () => { });
17
+ socket.on('data', (data) => {
18
+ cb(socket, data);
19
+ });
20
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Server = void 0;
7
+ const net_1 = __importDefault(require("net"));
8
+ const sessionManager_1 = require("../security/sessionManager");
9
+ const localChatStore_1 = require("../utils/localChatStore");
10
+ const processMessage_1 = require("./handlers/processMessage");
11
+ const setupSocket_1 = require("./handlers/setupSocket");
12
+ class Server {
13
+ localChatStore;
14
+ server;
15
+ manager;
16
+ connections;
17
+ constructor(limit = 100, port = 3000) {
18
+ this.localChatStore = new localChatStore_1.LocalChatStore(100);
19
+ this.manager = new sessionManager_1.SessionManager();
20
+ this.connections = new Map();
21
+ this.server = net_1.default.createServer();
22
+ this.server.on("connection", (socket) => {
23
+ setupSocket_1.setupSocket.call(this, socket, processMessage_1.processMessage.bind(this));
24
+ });
25
+ this.server.listen(port);
26
+ }
27
+ }
28
+ exports.Server = Server;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encrypt = encrypt;
7
+ exports.decrypt = decrypt;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ function formatKey(key) {
10
+ if (key.length > 32) {
11
+ key = key.slice(0, 32);
12
+ }
13
+ else if (key.length < 32) {
14
+ key += Array.from({ length: 32 - key.length }, () => 0).join('');
15
+ }
16
+ return key;
17
+ }
18
+ function encrypt(value, key) {
19
+ key = formatKey(key);
20
+ const iv = Buffer.alloc(16, 0);
21
+ const cipher = crypto_1.default.createCipheriv("aes-256-cbc", key, iv);
22
+ let encrypted = cipher.update(value, "utf-8", "hex");
23
+ encrypted += cipher.final("hex");
24
+ return encrypted;
25
+ }
26
+ function decrypt(value, key) {
27
+ key = formatKey(key);
28
+ const iv = Buffer.alloc(16, 0);
29
+ const decipher = crypto_1.default.createDecipheriv("aes-256-cbc", key, iv);
30
+ let decrypted = decipher.update(value, "hex", "utf-8");
31
+ decrypted += decipher.final("utf-8");
32
+ return decrypted;
33
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.genName = genName;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ function genName() {
9
+ return crypto_1.default.randomBytes(8).toString("hex");
10
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalChatStore = void 0;
4
+ class LocalChatStore {
5
+ chat;
6
+ limit;
7
+ constructor(limit) {
8
+ this.chat = [];
9
+ this.limit = limit;
10
+ }
11
+ push(message) {
12
+ if (this.chat.length >= this.limit) {
13
+ this.chat.splice(0, 1);
14
+ }
15
+ this.chat.push(message);
16
+ }
17
+ getChat() {
18
+ return this.chat;
19
+ }
20
+ }
21
+ exports.LocalChatStore = LocalChatStore;
@@ -0,0 +1,34 @@
1
+ import net from 'net'
2
+ import readline from 'readline/promises'
3
+ import { SessionManager } from '../security/sessionManager'
4
+ import { processMessage } from './handlers/processMessage'
5
+ import { onLine } from "./handlers/mainInputHandler"
6
+ import { genName } from '../utils/genNameAlias'
7
+
8
+ export class Client {
9
+ manager: SessionManager
10
+ rl: readline.Interface
11
+ name: string
12
+ server: net.Socket
13
+ key_generated: boolean
14
+
15
+ constructor(name=genName(), host="localhost", port=3000) {
16
+ this.name = name
17
+ this.server = net.connect({ host: host, port: port })
18
+ this.key_generated = false
19
+ this.manager = new SessionManager()
20
+ this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: `${this.name} >>> ` })
21
+
22
+ this.server.on("connect", () => {
23
+ this.server.write(JSON.stringify({type: 'public_key_request', body: this.manager.getClient()}))
24
+ })
25
+ this.server.on("data", (data) => {
26
+ processMessage.call(this, data)
27
+ })
28
+
29
+ this.rl.on("line", onLine.bind(this))
30
+
31
+ this.server.on("close", () => {this.server.end(); process.exit()})
32
+ this.rl.on("SIGINT", () => {this.server.end(); process.exit()})
33
+ }
34
+ }
@@ -0,0 +1,21 @@
1
+ import { Client } from "../clientInstance";
2
+ import { encrypt } from "../../utils/edUtils";
3
+
4
+ function onLine(this: Client, line: string) {
5
+ if (line.length > 0) {
6
+ process.stdout.moveCursor(0, -1)
7
+ process.stdout.write('\r\x1B[K');
8
+ console.log(`${this.name}: ${line}`)
9
+ if (line == "/exit") {
10
+ this.rl.emit("SIGINT")
11
+ }
12
+ this.server.write(encrypt(JSON.stringify({type: 'msg', body: [this.name, line]}), this.manager.session_key))
13
+ this.rl.prompt()
14
+ } else {
15
+ process.stdout.moveCursor(0, -1)
16
+ process.stdout.write('\r\x1B[K');
17
+ this.rl.prompt()
18
+ }
19
+ }
20
+
21
+ export { onLine }
@@ -0,0 +1,47 @@
1
+ import { decrypt, encrypt } from "../../utils/edUtils"
2
+ import { Client } from "../clientInstance"
3
+
4
+ async function processMessage(this: Client, data: any) {
5
+ const cur = this.rl.getCursorPos()
6
+ let processed
7
+
8
+ if (this.key_generated) {
9
+ processed = JSON.parse(decrypt(data.toString(), this.manager.session_key))
10
+ } else {
11
+ processed = JSON.parse(data.toString('utf-8'))
12
+ }
13
+
14
+ switch (processed.type) {
15
+ case 'public_key_response':
16
+ this.manager.setServer(processed.body[1])
17
+ this.server.write(JSON.stringify({type: 'master_key_response', body: [this.manager.getMaster(processed.body[0]), this.name]}))
18
+ break;
19
+ case 'finished':
20
+ this.manager.generateSession()
21
+ this.key_generated = true
22
+ this.server.write(encrypt(JSON.stringify({type: "finished", body: this.name}), this.manager.session_key))
23
+ this.rl.prompt()
24
+ break;
25
+ case 'history':
26
+ process.stdout.write('\r\x1B[K')
27
+
28
+ for (let i = 0; i < processed.body.length; i++) {
29
+ console.log(processed.body[i][0] + ':', processed.body[i][1])
30
+ }
31
+
32
+ console.log('[SERVER]:', this.name, 'connected to server')
33
+
34
+ this.rl.prompt(true)
35
+ process.stdout.cursorTo(cur.cols)
36
+ break;
37
+ case 'msg':
38
+ process.stdout.write('\r\x1B[K');
39
+ console.log(processed.data[0] + ':', processed.data[1])
40
+
41
+ this.rl.prompt(true)
42
+ process.stdout.cursorTo(cur.cols)
43
+ break;
44
+ }
45
+ }
46
+
47
+ export { processMessage }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@manch1kz/yanac",
3
+ "version": "1.0.1",
4
+ "description": "(YANAC) Yet another not another chat",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/manch1kz/YANAC"
8
+ },
9
+ "main": "./build/app.js",
10
+ "bin": "./build/app.js",
11
+ "scripts": {
12
+ "prepack": "npm run build",
13
+ "build": "tsc"
14
+ },
15
+ "keywords": [
16
+ "cryptography",
17
+ "handshake",
18
+ "tcp",
19
+ "educational",
20
+ "cli",
21
+ "security"
22
+ ],
23
+ "author": "manch1kz",
24
+ "license": "ISC",
25
+ "type": "commonjs",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "commander": "^14.0.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.0.8",
34
+ "typescript": "^5.0.0"
35
+ }
36
+ }
@@ -0,0 +1,78 @@
1
+ import crypto from "crypto"
2
+
3
+ export class SessionManager {
4
+ client_random: any
5
+ server_random: any
6
+ master_random: any
7
+ session_key: any
8
+ privateKey: any
9
+ publicKey: any
10
+
11
+ constructor() {
12
+ this.client_random = null
13
+ this.server_random = null
14
+ this.master_random = null
15
+ this.session_key = null
16
+
17
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
18
+ modulusLength: 2048,
19
+ publicKeyEncoding: {
20
+ format: 'pem',
21
+ type: 'pkcs1'
22
+ },
23
+ privateKeyEncoding: {
24
+ format: 'pem',
25
+ type: 'pkcs1'
26
+ }})
27
+
28
+ this.privateKey = privateKey
29
+ this.publicKey = publicKey
30
+ }
31
+
32
+ generateSession() {
33
+ if (this.client_random && this.server_random && this.master_random) {
34
+ const sum = this.client_random + this.server_random + Number(this.master_random)
35
+ this.session_key = crypto.createHash('sha256').update(String(sum)).digest().toString('hex')
36
+ }
37
+ }
38
+
39
+ setClient(client_random: any) {
40
+ this.client_random = client_random
41
+ }
42
+
43
+ getClient() {
44
+ if (this.client_random == null) {
45
+ this.client_random = Math.floor(Math.random() * 5000)
46
+ }
47
+ return this.client_random
48
+ }
49
+
50
+ getServer() {
51
+ if (this.server_random == null) {
52
+ this.server_random = Math.floor(Math.random() * 5000)
53
+ }
54
+ return this.server_random
55
+ }
56
+
57
+ setServer(server_random: any) {
58
+ this.server_random = server_random
59
+ }
60
+
61
+ getMaster(public_key: any) {
62
+ if (this.master_random == null) {
63
+ this.master_random = String(Math.floor(Math.random() * 5000))
64
+ }
65
+
66
+ return crypto.publicEncrypt({
67
+ key: public_key,
68
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
69
+ }, this.master_random).toString('base64')
70
+ }
71
+
72
+ setMaster(master_random: any, private_key: any) {
73
+ this.master_random = crypto.privateDecrypt({
74
+ key: private_key,
75
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
76
+ }, Buffer.from(master_random, 'base64')).toString()
77
+ }
78
+ }
@@ -0,0 +1,57 @@
1
+ import { encrypt, decrypt } from "../../utils/edUtils"
2
+ import { Server } from "../serverInstance"
3
+
4
+ function processMessage(this: Server, socket: any, data: any) {
5
+ const socket_item = this.connections.get(socket)
6
+ const socket_generator = socket_item.generator
7
+ let message
8
+
9
+ if (socket_item.key_generated) {
10
+ message = JSON.parse(decrypt(data.toString(), socket_item.generator.session_key))
11
+ } else {
12
+ message = JSON.parse(data.toString())
13
+ }
14
+
15
+ switch (message.type) {
16
+ case 'msg':
17
+ this.localChatStore.push(message.body)
18
+ Array.from(this.connections.keys()).forEach((i: any) => {
19
+ if (i != socket) {
20
+ i.write(encrypt(JSON.stringify({type: 'msg', data: message.body}), this.connections.get(i).generator.session_key))
21
+ }
22
+ })
23
+ break;
24
+ case 'public_key_request':
25
+ const server_random = this.manager.getServer()
26
+
27
+ socket_generator.setClient(message.body)
28
+ socket_generator.setServer(server_random)
29
+
30
+ socket.write(JSON.stringify({type: 'public_key_response', body: [this.manager.publicKey, server_random]}))
31
+ break;
32
+ case 'master_key_response':
33
+ socket_generator.setMaster(message.body[0], this.manager.privateKey)
34
+ socket_generator.generateSession()
35
+ socket_item.name = message.body[1]
36
+ socket_item.key_generated = true
37
+
38
+ socket.write(JSON.stringify({type: 'finished'}))
39
+ break;
40
+ case 'finished':
41
+ socket_item.name = message.body
42
+ socket.write(encrypt(JSON.stringify({type: 'history', body: this.localChatStore.getChat()}), socket_generator.session_key))
43
+ this.localChatStore.push(['[SERVER]', message.body + ' connected to server'])
44
+
45
+ Array.from(this.connections.keys()).forEach((i: any) => {
46
+ if (i != socket) {
47
+ i.write(
48
+ encrypt(JSON.stringify({type: 'msg', data: ['[SERVER]', message.body + ' connected to server']}),
49
+ this.connections.get(i).generator.session_key)
50
+ )
51
+ }
52
+ })
53
+ break;
54
+ }
55
+ }
56
+
57
+ export { processMessage }
@@ -0,0 +1,26 @@
1
+ import { SessionManager } from "../../security/sessionManager"
2
+ import { encrypt, decrypt } from "../../utils/edUtils"
3
+ import { Server } from "../serverInstance"
4
+ import net from 'net'
5
+
6
+ function setupSocket(this: Server, socket: net.Socket, cb: Function) {
7
+ this.connections.set(socket, {name: null, generator: new SessionManager(), key_generated: false})
8
+
9
+ socket.on('close', () => {
10
+ let body = ['[SERVER]', this.connections.get(socket).name + ' disconnected from server']
11
+ this.localChatStore.push(body)
12
+
13
+ Array.from(this.connections.keys()).forEach((i: any) => {
14
+ i.write(encrypt(JSON.stringify({type: 'msg', data: body}), this.connections.get(i).generator.session_key))
15
+ })
16
+ this.connections.delete(socket)
17
+ })
18
+
19
+ socket.on("error", () => {})
20
+
21
+ socket.on('data', (data: any) => {
22
+ cb(socket, data)
23
+ })
24
+ }
25
+
26
+ export { setupSocket }
@@ -0,0 +1,25 @@
1
+ import net from 'net'
2
+ import { SessionManager } from '../security/sessionManager'
3
+ import { LocalChatStore } from '../utils/localChatStore'
4
+ import { processMessage } from "./handlers/processMessage"
5
+ import { setupSocket } from "./handlers/setupSocket"
6
+
7
+ export class Server {
8
+ localChatStore: LocalChatStore
9
+ server: net.Server
10
+ manager: SessionManager
11
+ connections: Map<any, any>
12
+
13
+ constructor(limit=100, port=3000) {
14
+ this.localChatStore = new LocalChatStore(100)
15
+ this.manager = new SessionManager()
16
+ this.connections = new Map()
17
+ this.server = net.createServer()
18
+
19
+ this.server.on("connection", (socket) => {
20
+ setupSocket.call(this, socket, processMessage.bind(this))
21
+ })
22
+
23
+ this.server.listen(port)
24
+ }
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "commonjs",
5
+ "rootDir": "./",
6
+ "outDir": "./build",
7
+ "sourceMap": false,
8
+ "declaration": false,
9
+ "declarationMap": false,
10
+ "moduleResolution": "node",
11
+ "esModuleInterop": true,
12
+ },
13
+ "exclude": ["node_modules", "build"]
14
+ }
@@ -0,0 +1,37 @@
1
+ import crypto from "crypto"
2
+
3
+ function formatKey(key: string) {
4
+ if (key.length > 32) {
5
+ key = key.slice(0, 32)
6
+ } else if (key.length < 32) {
7
+ key += Array.from({length: 32 - key.length}, () => 0).join('')
8
+ }
9
+
10
+ return key
11
+ }
12
+
13
+ function encrypt(value: string, key: string) {
14
+ key = formatKey(key)
15
+
16
+ const iv = Buffer.alloc(16, 0);
17
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv)
18
+
19
+ let encrypted = cipher.update(value, "utf-8", "hex")
20
+ encrypted += cipher.final("hex")
21
+
22
+ return encrypted
23
+ }
24
+
25
+ function decrypt(value: string, key: string) {
26
+ key = formatKey(key)
27
+
28
+ const iv = Buffer.alloc(16, 0);
29
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv)
30
+
31
+ let decrypted = decipher.update(value, "hex", "utf-8")
32
+ decrypted += decipher.final("utf-8")
33
+
34
+ return decrypted
35
+ }
36
+
37
+ export { encrypt, decrypt }
@@ -0,0 +1,7 @@
1
+ import crypto from "crypto"
2
+
3
+ function genName() {
4
+ return crypto.randomBytes(8).toString("hex")
5
+ }
6
+
7
+ export { genName }
@@ -0,0 +1,20 @@
1
+ export class LocalChatStore {
2
+ chat: any
3
+ limit: number
4
+
5
+ constructor(limit: number) {
6
+ this.chat = []
7
+ this.limit = limit
8
+ }
9
+
10
+ push(message: any) {
11
+ if (this.chat.length >= this.limit) {
12
+ this.chat.splice(0, 1)
13
+ }
14
+ this.chat.push(message)
15
+ }
16
+
17
+ getChat() {
18
+ return this.chat
19
+ }
20
+ }