From 5fa84e31b90d77bf1cabc28479341d7645a43959 Mon Sep 17 00:00:00 2001 From: OfficialDakari Date: Mon, 29 Apr 2024 19:42:07 +0500 Subject: [PATCH] initial commit --- .gitignore | 7 +++ README.md | 11 ++++ client.yaml | 10 ++++ client/index.js | 82 ++++++++++++++++++++++++++++++ config.js | 15 ++++++ createUser.js | 21 ++++++++ encryption.js | 45 +++++++++++++++++ genkey.js | 10 ++++ index.js | 14 ++++++ package.json | 19 +++++++ server.yaml | 14 ++++++ server/index.js | 130 ++++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 378 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client.yaml create mode 100644 client/index.js create mode 100644 config.js create mode 100644 createUser.js create mode 100644 encryption.js create mode 100644 genkey.js create mode 100644 index.js create mode 100644 package.json create mode 100644 server.yaml create mode 100644 server/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c02654 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +./node_modules/ +node_modules/* +node_modules/** +./node_modules/* +./node_modules/** +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..32621a1 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# FearlessVPN-NG +Новый FearlessVPN (старый скоро будет доступен) работает по UDP (старый работал по HTTPS, хоть через Cloudflare). + +# Как настроить +Мне лень всё расписывать, вроде всё понятно. + +# Как перенаправить весь трафик +На Linux надо делать что-то с `ip route`. Не шарю + +# Завершение +Самый понятный README. Потом переделаю. \ No newline at end of file diff --git a/client.yaml b/client.yaml new file mode 100644 index 0000000..2797e96 --- /dev/null +++ b/client.yaml @@ -0,0 +1,10 @@ +type: client +port: 5454 +endpoint: 192.168.0.158 +mtu: 1400 +subnet: 10.3.8.0 +len: 24 +iv: aiD6UuDsVPJyKudkc_SZBUlH-MRzLaZROiQwD11irtI +globalKey: mcCOmjwTUF6jNFQkbTWHrwyOMA6KV7oRFrK8lIawT5U +algorithm: aes-256-cbc +privateKey: Wf7U1DzfrkkFs9OugXHauYqmgfyWVLwxXoskrOebObg \ No newline at end of file diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..810619e --- /dev/null +++ b/client/index.js @@ -0,0 +1,82 @@ +// 13 b4 37 XX XX XX XX > (pk) - Authenticated +// 13 37 > (pk) - I am <...> +// e7 5a 3d ed > (gk) - Authenticate yourself +// 55 44 e9 37 < (pk) - Forget me +// e7 5a 3d ea > (pk) - Forget me. OK + +const dgram = require('dgram'); +const net = require('net'); +const { Tun } = require('tuntap2'); +const { encrypt, decrypt } = require('../encryption'); +const IP = require('ip-packet'); +const sock = dgram.createSocket('udp4'); + +const tun = new Tun(); + +var established = false; + +sock.on('message', (msg, info) => { + const pd = decrypt(msg, config.privateKey, config.iv, config.algorithm); + const gd = decrypt(msg, config.globalKey, config.iv, config.algorithm); + const dec = pd.length == 0 ? gd : pd; + if (dec.length == 4 && dec[0] == 0xe7 && dec[1] == 0x5a && dec[2] == 0x3d && dec[3] == 0xed) { + console.log(`Server asks to authenticate. Authenticating...`); + authenticateAndCheck(); + return; + } + if (dec.length == 7 && dec[0] == 0x13 && dec[1] == 0xb4 && dec[2] == 0x37) { + const ip = dec.slice(3); + const strIp = ip.map(s => s.toString()).join('.'); + console.log(`My IP: ${strIp}`); + tun.mtu = config.mtu; + tun.ipv4 = `${strIp}/${config.len}`; + tun.isUp = true; + tun.on('data', (d) => { + sock.send( + encrypt( + d, + config.privateKey, + config.iv, + config.algorithm + ), + config.port, + config.endpoint + ); + }); + established = true; + } + if (established && (dec[0] >> 4 == 4)) { + tun.write(dec); + } +}); + +function authenticate() { + sock.send( + encrypt( + Buffer.from([ + 0x13, 0x37 + ]), + config.privateKey, + config.iv, + config.algorithm + ), + config.port, + config.endpoint + ); +} + +function authenticateAndCheck() { + authenticate(); + + var i = setInterval(() => { + if (!established) { + console.log(`Connection not established. Authenticating again...`); + authenticate(); + } else { + clearInterval(i); + console.log(`Authenticated.`); + } + }, 5000); +} + +authenticateAndCheck(); \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..896501a --- /dev/null +++ b/config.js @@ -0,0 +1,15 @@ +const fs = require('fs'); +const YAML = require('yaml'); + +function getConfig(file) { + return YAML.parse(fs.readFileSync(file, 'utf-8')); +} + +function saveConfig(file, conf) { + fs.writeFileSync(file, YAML.stringify(conf)); +} + +module.exports = { + getConfig, + saveConfig +}; \ No newline at end of file diff --git a/createUser.js b/createUser.js new file mode 100644 index 0000000..f8dda0b --- /dev/null +++ b/createUser.js @@ -0,0 +1,21 @@ +const { getConfig, saveConfig } = require("./config"); +const genkey = require("./genkey"); + +const configFile = process.argv[2]; + +const config = getConfig(configFile); + +if (config.type != 'server') { + console.error('Not server'); + process.exit(-1); +} + +const [ username, addr ] = process.argv.slice(3); +const user = { + username, + addr, + key: genkey.genkey() +}; + +config.users[username] = user; +saveConfig(configFile, config); \ No newline at end of file diff --git a/encryption.js b/encryption.js new file mode 100644 index 0000000..9190aee --- /dev/null +++ b/encryption.js @@ -0,0 +1,45 @@ +const crypto = require('crypto'); + +function encrypt(payload, key, secret_iv, algorithm) { + try { + const iv = crypto + .createHash('sha512') + .update(secret_iv) + .digest('hex') + .substring(0, 16) + const cipher = crypto.createCipheriv(algorithm, crypto + .createHash('sha512') + .update(key) + .digest('hex') + .substring(0, 32), iv); + let encrypted = cipher.update(payload, 'binary', 'binary'); + encrypted += cipher.final('binary'); + return Buffer.from(encrypted, 'binary'); + } catch (error) { + return Buffer.from(''); + } +} + +function decrypt(payload, key, secret_iv, algorithm) { + try { + const iv = crypto + .createHash('sha512') + .update(secret_iv) + .digest('hex') + .substring(0, 16) + const decipher = crypto.createDecipheriv(algorithm, crypto + .createHash('sha512') + .update(key) + .digest('hex') + .substring(0, 32), iv); + let decrypted = decipher.update(payload, 'binary', 'binary'); + decrypted += decipher.final('binary'); + return Buffer.from(decrypted, 'binary'); + } catch (error) { + return Buffer.from(''); + } +} + +module.exports = { + encrypt, decrypt +}; \ No newline at end of file diff --git a/genkey.js b/genkey.js new file mode 100644 index 0000000..c568978 --- /dev/null +++ b/genkey.js @@ -0,0 +1,10 @@ +const md5 = require('md5'); +const { v4 } = require('uuid'); + +console.log(Buffer.from(md5(v4()) + md5(v4()), 'hex').toString('base64url')); + +module.exports = { + genkey: () => { + return Buffer.from(md5(v4()) + md5(v4()), 'hex').toString('base64url'); + } +}; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..d786fc6 --- /dev/null +++ b/index.js @@ -0,0 +1,14 @@ +const { getConfig } = require("./config"); + +const [ nodejs, indexjs, configFile ] = process.argv; + +const conf = getConfig(configFile); + +globalThis.configFile = configFile; +globalThis.config = conf; + +if (conf.type == 'server') { + require('./server/'); +} else if (conf.type == 'client') { + require('./client/'); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5596dfe --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "fearlessvpn_ng", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "OfficialDakari", + "license": "MIT", + "dependencies": { + "crystals-kyber": "^5.1.0", + "ip-packet": "^1.1.0", + "md5": "^2.3.0", + "tuntap2": "^0.9.1", + "uuid": "^9.0.1", + "yaml": "^2.4.2" + } +} diff --git a/server.yaml b/server.yaml new file mode 100644 index 0000000..2100b73 --- /dev/null +++ b/server.yaml @@ -0,0 +1,14 @@ +type: server +port: 5454 +mtu: 1400 +subnet: 10.3.8.0 +len: 24 +addr: 10.3.8.1 +iv: aiD6UuDsVPJyKudkc_SZBUlH-MRzLaZROiQwD11irtI +globalKey: mcCOmjwTUF6jNFQkbTWHrwyOMA6KV7oRFrK8lIawT5U +algorithm: aes-256-cbc +users: + officialdakari: + username: officialdakari + addr: 10.3.8.2 + key: Wf7U1DzfrkkFs9OugXHauYqmgfyWVLwxXoskrOebObg diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..38d18c6 --- /dev/null +++ b/server/index.js @@ -0,0 +1,130 @@ +// 13 b4 37 XX XX XX XX > (pk) - Authenticated +// 13 37 > (pk) - I am <...> +// e7 5a 3d ed > (gk) - Authenticate yourself +// 55 44 e9 37 < (pk) - Forget me +// e7 5a 3d ea > (pk) - Forget me. OK + +const dgram = require('dgram'); +const net = require('net'); +const { Tun } = require('tuntap2'); +const { encrypt, decrypt } = require('../encryption'); +const IP = require('ip-packet'); + +const conns = {}; +const ips = {}; +const ports = {}; + +const sock = dgram.createSocket('udp4'); + +const l = new net.BlockList(); +l.addSubnet(config.subnet, config.len); + +const tun = new Tun(); + +tun.mtu = config.mtu; +tun.ipv4 = `${config.addr}/${config.len}`; +tun.isUp = true; + +tun.on('data', (buff) => { + if (buff[0] >> 4 !== 4) return; + const p = IP.decode(buff); + //(p); + const user = Object.values(config.users).find(x => x.addr == p.destinationIp); + //(user); + if (!user) return; + const targetIp = ips[user.username]; + const targetPort = ports[user.username]; + //(targetIp, targetPort); + if (!targetIp || !targetPort) return; + sock.send(encrypt(IP.encode(p), user.key, config.iv, config.algorithm), targetPort, targetIp); +}) + +sock.on('listening', () => { + const a = sock.address(); + console.log(`Listening on ${a.address}:${a.port}`); +}); + +sock.on('message', (msg, info) => { + if (!conns[info.address]) { + for (const uname in config.users) { + const u = config.users[uname]; + const dec = decrypt(msg, u.key, config.iv, config.algorithm); + if (dec.length == 2 && dec[0] == 0x13 && dec[1] == 0x37) { + conns[info.address] = uname; + ips[uname] = info.address; + const spl = u.addr.split('.').map(s => parseInt(s)); + const buff = Buffer.from([ + 0x13, + 0xb4, + 0x37, + ...spl + ]); + const enc = encrypt(buff, u.key, config.iv, config.algorithm); + sock.send(enc, info.port, info.address); + return; + } + } + const buff = Buffer.from([ + 0xe7, + 0x5a, + 0x3d, + 0xed + ]); + const enc = encrypt(buff, config.globalKey, config.iv, config.algorithm); + sock.send(enc, info.port, info.address); + return; + } else { + const uname = conns[info.address]; + ports[uname] = info.port; + ips[uname] = info.address; + const acc = config.users[uname]; + const dec = decrypt(msg, acc.key, config.iv, config.algorithm); + //(dec); + if (dec.length == 0) { + console.error(`Empty or malformed packet from ${uname}`); + return; + } + if (dec.length == 2 && dec[0] == 0x13 && dec[1] == 0x37) { + const spl = acc.addr.split('.').map(s => parseInt(s)); + const buff = Buffer.from([ + 0x13, + 0xb4, + 0x37, + ...spl + ]); + const enc = encrypt(buff, acc.key, config.iv, config.algorithm); + sock.send(enc, info.port, info.address); + return; + } + if (dec.length == 4 && + dec[0] == 0x55 && dec[1] == 0x44 && dec[2] == 0xe9 && dec[3] == 0x37) { + const buff = Buffer.from([ + 0xe7, + 0x5a, + 0x3d, + 0xea + ]); + const enc = encrypt(buff, acc.key, config.iv, config.algorithm); + sock.send(enc, info.port, info.address); + delete conns[info.address]; + return; + } + + if ((dec[0] >> 4) !== 4) return; + const packet = IP.decode(dec); + //(packet); + packet.sourceIp = acc.addr; + if ((config.allow_outbound && !l.check(packet.destinationIp)) || packet.destinationIp == config.addr) { + tun.write(dec); + return; + } + const user = Object.values(config.users).find(x => x.addr == packet.destinationIp); + if (!user) return; + const targetIp = conns[user.username]; + const targetPort = ports[user.username]; + if (!targetIp || !targetPort) return; + sock.send(encrypt(dec, user.key, config.iv, config.algorithm), targetPort, targetIp); + } +}); + +sock.bind(config.port); \ No newline at end of file