2021-10-02 00:16:34 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
import net from 'net';
|
|
|
|
import { EventEmitter, once } from 'events';
|
|
|
|
|
|
|
|
import l from 'log';
|
|
|
|
|
|
|
|
import settings from '../../settings.mjs';
|
2021-10-02 02:02:49 +00:00
|
|
|
import rekey from './rekey.mjs';
|
2021-10-02 14:56:34 +00:00
|
|
|
import FramedTransport from './FramedTransport.mjs';
|
2021-10-02 02:02:49 +00:00
|
|
|
import { write_safe } from '../io/StreamHelpers.mjs';
|
|
|
|
import { encrypt_bytes, decrypt_bytes } from '../crypto/secretbox.mjs';
|
2021-10-02 00:16:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a connection to a single endpoint.
|
|
|
|
*/
|
|
|
|
class Connection extends EventEmitter {
|
2021-10-02 16:00:24 +00:00
|
|
|
constructor(secret_join, socket) {
|
2021-10-02 00:16:34 +00:00
|
|
|
super();
|
2021-10-02 02:02:49 +00:00
|
|
|
|
2021-10-02 16:00:24 +00:00
|
|
|
this.socket = socket;
|
|
|
|
|
|
|
|
this.rekey_last = null;
|
|
|
|
this.rekey_interval = 30 * 60 * 1000; // 30 minutes
|
2021-10-02 02:02:49 +00:00
|
|
|
this.session_key = Buffer.from(secret_join, "base64");
|
2021-10-02 00:16:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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");
|
2021-10-02 16:00:24 +00:00
|
|
|
|
|
|
|
await this.init();
|
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
2021-10-02 00:16:34 +00:00
|
|
|
this.socket.setKeepAlive(true);
|
|
|
|
|
2021-10-02 16:00:24 +00:00
|
|
|
await this.rekey();
|
2021-10-02 00:16:34 +00:00
|
|
|
|
2021-10-02 14:56:34 +00:00
|
|
|
this.framer = new FramedTransport(this.socket);
|
|
|
|
this.framer.on("frame", this.handle_frame);
|
2021-10-02 00:16:34 +00:00
|
|
|
|
2021-10-02 02:02:49 +00:00
|
|
|
this.read_task = read_loop();
|
2021-10-02 00:16:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 16:00:24 +00:00
|
|
|
async rekey() {
|
|
|
|
this.rekey_last = new Date();
|
|
|
|
this.session_key = await rekey(this.socket, this.session_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
async destroy() {
|
|
|
|
await this.framer.destroy();
|
2021-10-02 00:16:34 +00:00
|
|
|
this.emit("destroy");
|
|
|
|
}
|
|
|
|
|
2021-10-02 14:56:34 +00:00
|
|
|
async handle_frame(bytes) {
|
2021-10-02 00:16:34 +00:00
|
|
|
try {
|
2021-10-02 14:56:34 +00:00
|
|
|
let decrypted = decrypt_bytes(this.session_key, bytes);
|
|
|
|
if(decrypted === null) return;
|
|
|
|
await handle_message(decrypted.toString("utf-8"));
|
2021-10-02 00:16:34 +00:00
|
|
|
}
|
|
|
|
catch(error) {
|
|
|
|
l.warn(`Warning: Killing connection to ${this.address}:${this.port} after error: ${settings.cli.verbose ? error : error.message}`);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-02 14:56:34 +00:00
|
|
|
async handle_message(msg_text) {
|
|
|
|
const msg = JSON.parse(msg_text);
|
|
|
|
this.emit("message", msg);
|
2021-10-02 00:16:34 +00:00
|
|
|
}
|
2021-10-02 02:02:49 +00:00
|
|
|
|
|
|
|
async send(message) {
|
2021-10-02 16:00:24 +00:00
|
|
|
if(new Date() - this.rekey_last > this.rekey_interval)
|
|
|
|
await this.rekey();
|
|
|
|
|
2021-10-02 02:03:49 +00:00
|
|
|
// TODO: Consider https://devdocs.io/node/crypto#crypto.createCipheriv() - which lets us use any openssl ciphers we like - e.g. ChaCha20-Poly1305
|
2021-10-02 02:02:49 +00:00
|
|
|
let payload = JSON.stringify(message);
|
|
|
|
payload = encrypt_bytes(this.session_key, payload);
|
|
|
|
|
2021-10-02 16:00:24 +00:00
|
|
|
|
|
|
|
await this.framer.write(payload);
|
2021-10-02 02:02:49 +00:00
|
|
|
}
|
2021-10-02 00:16:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 16:00:24 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-10-02 00:16:34 +00:00
|
|
|
export default Connection;
|