"use strict"; import net from 'net'; import { EventEmitter, once } from 'events'; import l from 'log'; import settings from '../../settings.mjs'; import rekey from './rekey.mjs'; import FramedTransport from './FramedTransport.mjs'; import { write_safe } from '../io/StreamHelpers.mjs'; import { encrypt_bytes, decrypt_bytes } from '../crypto/secretbox.mjs'; /** * Represents a connection to a single endpoint. */ class Connection extends EventEmitter { constructor(secret_join, socket) { super(); this.socket = socket; this.rekey_last = null; this.rekey_interval = 30 * 60 * 1000; // 30 minutes this.session_key = Buffer.from(secret_join, "base64"); } /** * Connects to a peer and initialises a secure TCP connection thereto. * @param {string} address The address to connect to. * @param {string} port The TCP port to connect to. * @return {net.Socket} A socket setup for secure communication. */ async connect(address, port) { this.address = address; this.port = port; this.socket = new new.Socket(); this.socket.connect({ address, port }); await once(this.socket, "connect"); await this.init(); } async init() { this.socket.setKeepAlive(true); await this.rekey(); this.framer = new FramedTransport(this.socket); this.framer.on("frame", this.handle_frame); this.read_task = read_loop(); } async rekey() { this.rekey_last = new Date(); this.session_key = await rekey(this.socket, this.session_key); } async destroy() { await this.framer.destroy(); this.emit("destroy"); } async handle_frame(bytes) { try { let decrypted = decrypt_bytes(this.session_key, bytes); if(decrypted === null) return; await handle_message(decrypted.toString("utf-8")); } catch(error) { l.warn(`Warning: Killing connection to ${this.address}:${this.port} after error: ${settings.cli.verbose ? error : error.message}`); } finally { this.destroy(); } } async handle_message(msg_text) { const msg = JSON.parse(msg_text); this.emit("message", msg); } async send(message) { if(new Date() - this.rekey_last > this.rekey_interval) await this.rekey(); // TODO: Consider https://devdocs.io/node/crypto#crypto.createCipheriv() - which lets us use any openssl ciphers we like - e.g. ChaCha20-Poly1305 let payload = JSON.stringify(message); payload = encrypt_bytes(this.session_key, payload); await this.framer.write(payload); } } Connection.Wrap = async function(secret_join, socket) { const socket = new Connection(secret_join, socket); await socket.init(); return socket; } Connection.Create = async function(secret_join, address, port) { const socket = new Connection(secret_join); socket.connect(address, port); } export default Connection;