From 4aa755f9ac49fbf9284e27b013182487a2eb18bc Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 13 Nov 2020 10:19:44 -0500 Subject: [PATCH] end to end encryption --- lib/Identity.js | 89 +++++++++++------------- lib/STP.js | 180 +++++++++++++++++++++++++++++++----------------- relay/index.js | 83 +++++++++++++--------- 3 files changed, 211 insertions(+), 141 deletions(-) diff --git a/lib/Identity.js b/lib/Identity.js index 3e465f5..811f079 100644 --- a/lib/Identity.js +++ b/lib/Identity.js @@ -10,64 +10,59 @@ module.exports.Identity = class Identity { key; name; + /// ASYNC CONSTRUCTOR constructor(module, id) { - const kv = new Keyv({ - store: new KeyvFile({ - filename: `${os.tmpdir()}/valnet/${module}/${id}.json` - }) + return new Promise(async (res, rej) => { + const kv = new Keyv({ + store: new KeyvFile({ + filename: `${os.tmpdir()}/valnet/${module}/${id}.json` + }) + }); + + log = log.scope(`Identity(${module}/${id})`); + + this.key = await new Promise(async (res) => { + log.info(`Searching for identity`); + + if(! await kv.get('private-key') + || ! await kv.get('public-key') + || ! await kv.get('name')) { + + log.warn(`no keypair found, generating...`); + const name = hri.random(); + const key = new NodeRSA({b: 512}); + key.generateKeyPair(); + await kv.set('name', name); + await kv.set('private-key', key.exportKey('pkcs8-private-pem')); + await kv.set('public-key', key.exportKey('pkcs8-public-pem')); + log.success(`done!`); + } + + const identity = new NodeRSA(); + identity.importKey(await kv.get('private-key'), 'pkcs8-private-pem'); + identity.importKey(await kv.get('public-key'), 'pkcs8-public-pem'); + log.info(`Identity imported.`); + + this.name = await kv.get('name'); + res(identity); + }); + + res(this); }); + } - log = log.scope(`Identity(${module}/${id})`); - - this.key = new Promise(async (res) => { - log.info(`Searching for identity`); - - if(! await kv.get('private-key') - || ! await kv.get('public-key') - || ! await kv.get('name')) { - - log.warn(`no keypair found, generating...`); - const name = hri.random(); - const key = new NodeRSA({b: 512}); - key.generateKeyPair(); - await kv.set('name', name); - await kv.set('private-key', key.exportKey('pkcs8-private-pem')); - await kv.set('public-key', key.exportKey('pkcs8-public-pem')); - log.success(`done!`); - } - - const identity = new NodeRSA(); - identity.importKey(await kv.get('private-key'), 'pkcs8-private-pem'); - identity.importKey(await kv.get('public-key'), 'pkcs8-public-pem'); - log.info(`Identity imported.`); - - this.name = await kv.get('name'); - res(identity); - }); + get publicKey() { + return this.key.exportKey('pkcs8-public-pem'); } async name() { - await this.key; return this.name; } - async encryptPublic(...args) { - await this.key; + async encrypt(...args) { return this.key.encrypt(...args); } - - async encryptPrivate(...args) { - await this.key; - return this.key.encryptPrivate(...args); - } - - async decryptPublic(...args) { - await this.key; - return this.key.decryptPublic(...args); - } - - async decryptPrivate(...args) { - await this.key; + async decrypt(...args) { return this.key.decrypt(...args); } diff --git a/lib/STP.js b/lib/STP.js index ab7a587..03284a2 100644 --- a/lib/STP.js +++ b/lib/STP.js @@ -1,87 +1,143 @@ const net = require('net'); const EventEmitter = require('events'); const NodeRSA = require('node-rsa'); -const log = require('signale'); +const log = require('signale').scope('stp'); module.exports.createServer = function(keys, onConnect) { const server = new Server(keys); - server.on('connect', onConnect); + server.on('connection', onConnect); return server; // return 5; } -module.exports.connect = function(port, ip) { - const client = new Client(port, ip); -} - -class Client extends EventEmitter { - port; - ip; - tcpClient - - constructor(port, ip) { - super(); - this.ip = ip; - this.port = port; - this.tcpClient = net.createConnection(port, ip); - this.tcpClient.on('connect', this.connect.bind(this)); - this.tcpClient.on('error', this.error.bind(this)); - } - - connect(e) { - // log.debug('connect', e); - this.emit('connect'); - } - - error(e) { - // log.debug('error', e); - this.emit('error'); - } +module.exports.connect = function(identity, port, ip) { + const client = net.connect(port, ip); + return new STPSocket(client, identity); } class Server extends EventEmitter { tcpServer; - key; - publicKey; - privateKey; + identity; - Client = class Client extends EventEmitter { - tcpClient; - - get remoteAddress() { return this.tcpClient.remoteAddress }; - - constructor(tcpClient) { - super(); - this.tcpClient = tcpClient; - this.tcpClient.on('error', (e) => { - this.emit('error', e) - }); - } - - write(...args) { - this.tcpClient.write(...args); - } - }; - - constructor(keys) { + constructor(identity) { super(); + this.identity = identity; this.tcpServer = net.createServer(this.tcpConnectClient.bind(this)); - this.key = new NodeRSA(); - this.key.importKey(keys.privateKey, 'pkcs8-private-pem'); - this.key.importKey(keys.publicKey, 'pkcs8-public-pem'); - this.publicKey = keys.publicKey; - this.privateKey = keys.privateKey; } - tcpConnectClient(tcpClient) { - const client = new this.Client(tcpClient); - client.write(this.publicKey); - this.emit('connect', client); - // return client; + tcpConnectClient(tcpSocket) { + const socket = new STPSocket(tcpSocket, this.identity); + socket.on('ready', () => { + this.emit('connection', socket); + }) } listen(...args) { - // log.success('STP Server created on port', args[0]); this.tcpServer.listen(...args); } } + +class STPSocket extends EventEmitter { + tcpSocket; + readyState = 0; + buffer = ''; + externalIdentity; + + get remoteAddress() { + return this.tcpSocket.remoteAddress; + } + + get remoteIdentity() { + return this.externalIdentity.exportKey('pkcs8-public-pem'); + } + + constructor(tcpSocket, identity) { + super(); + this.tcpSocket = tcpSocket; + this.identity = identity; + if(tcpSocket.readyState === 'open') this.handshake(); + else this.tcpSocket.on('connect', this.handshake.bind(this)); + + this.tcpSocket.on('data', this.data.bind(this)); + } + + data(evt) { + // log.debug(evt.toString()); + this.buffer += evt.toString(); + this.processBuffer(); + } + + processBuffer() { + const parts = this.buffer.split(/(\x02[^\x02\x03]*\x03)/g); + this.buffer = ''; + for(const message of parts) { + if(message.endsWith('\x03')) { + const obj = JSON.parse(message.substr(1, message.length - 2)); + this.processMessage(obj); + } else { + this.buffer += message; + } + } + } + + processMessage(obj) { + switch(obj.cmd) { + case 'KEY': { + if(this.readyState === 0) { + // log.debug('registering external key'); + this.externalIdentity = new NodeRSA(); + this.externalIdentity.importKey(obj.data.key, 'pkcs8-public-pem'); + this.tcpSocket.write(new AckPacket().toBuffer()); + this.readyState = 1; + } + break; + } + case 'ACK': { + if(this.readyState === 1) { + this.readyState = 2; + // log.debug('socket ready!'); + this.emit('ready'); + } + break; + } + } + } + + handshake() { + // log.debug('begin handshake on socket'); + const pk = this.identity.publicKey; + const packet = new KeyExchangePacket(pk); + const buffer = packet.toBuffer(); + this.tcpSocket.write(buffer); + } +} + +class STPPacket { + cmd = 'NOOP'; + data = {}; + meta = {}; + + toBuffer() { + return Buffer.from(`\x02${JSON.stringify({ + cmd: this.cmd, + data: this.data, + meta: this.meta + })}\x03`); + } +} + +class KeyExchangePacket extends STPPacket { + constructor(key, type = 'pkcs8-pem') { + super(); + this.cmd = 'KEY'; + this.data.key = key; + this.meta.type = type; + } +} + +class AckPacket extends STPPacket { + constructor() { + super(); + this.cmd = 'ACK'; + } +} \ No newline at end of file diff --git a/relay/index.js b/relay/index.js index 613462d..64aea1c 100644 --- a/relay/index.js +++ b/relay/index.js @@ -1,47 +1,51 @@ +(async () => { const { title } = require('../lib/title'); const net = require('net'); const log = require('signale').scope('relay'); -const { config } = require('./../package.json'); +const { config } = require('../package.json'); const { Identity } = require('../lib/Identity'); const stp = require('../lib/STP'); title('relay', false); -const identity = new Identity('relay', 'default'); +const identity = await new Identity('relay', 'default'); -// let connection = null; - -// (function tryConnect() { -// connection = net.createConnection(config.ports.ns, 'valnet.xyz', () => { -// log.success('Connected to name server'); -// }); -// connection.on('error', () => { -// log.error('Could not connect to name server') -// connection = null; -// tryConnect(); -// }); -// connection.on('data', (data) => { -// log.debug(data.toString()); -// }) -// })(); - -(async () => { - await identity.key; - - const server = stp.createServer({ - publicKey: (await identity.key).exportKey('pkcs8-public-pem'), - privateKey: (await identity.key).exportKey('pkcs8-private-pem') - }, (client) => { - log.info(`incomming connection from ${client.remoteAddress}`); +// ==================================== [STP CLIENT] +let client = null; +(function tryConnect() { + client = stp.connect(identity, config.ports.relay, config.addresses.relay); + client.on('ready', () => { + log.success(`connected to ${config.addresses.relay}`); + }) + client.on('error', () => { + log.error(`connection error on ${config.addresses.relay}`) + client = null; + setTimeout(tryConnect, 1000); }); - - server.listen(config.ports.relay); - log.success(`STP server listening on ${config.ports.relay}`); - - stp.connect(config.ports.relay, config.addresses.relay); + client.on('data', (data) => { + log.debug(data.toString()); + }) })(); +// stp.connect(config.ports.relay, config.addresses.relay); + +// ==================================== [STP SERVER] +const server = stp.createServer(identity, (client) => { + log.info(`incomming connection from ${client.remoteAddress} + ${client.remoteIdentity}`); +}); + +server.listen(config.ports.relay); +log.success(`STP server listening on ${config.ports.relay}`); + + + + + + + +// ==================================== [EXPRESS] const express = require('express'); const app = express(); @@ -49,4 +53,19 @@ app.get('/', (req, res) => { res.end(Date.now().toString()); }) -app.listen(9999); \ No newline at end of file +app.listen(9999); + + + + + + + + + + + + + + +})(); \ No newline at end of file