hey, we have basic starttls! Though it's not wired up yet.

This commit is contained in:
Starbeamrainbowlabs 2021-10-02 01:16:34 +01:00
parent 90f1a69e2e
commit a2d2b58b38
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
9 changed files with 426 additions and 0 deletions

143
package-lock.json generated
View file

@ -12,6 +12,8 @@
"applause-cli": "^1.7.0",
"log": "^6.2.0",
"make-cert": "^1.2.1",
"nexline": "^1.2.2",
"systeminformation": "^5.9.4",
"tweetnacl": "^1.0.3"
}
},
@ -89,6 +91,43 @@
"type": "^2.5.0"
}
},
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
},
"node_modules/iconv-lite": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/log": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/log/-/log-6.2.0.tgz",
@ -113,6 +152,18 @@
"make-cert": "make-cert.js"
}
},
"node_modules/nexline": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/nexline/-/nexline-1.2.2.tgz",
"integrity": "sha512-YLX5uoqNP7XVsXk889i8ZQcuMkukA4My4JD9wqTRLT+4dFo6QEEn+hU26J5H89m+mzW9BfhDgriGdbMEP06eeQ==",
"dependencies": {
"fs-extra": "^8.1.0",
"iconv-lite": "^0.5.0"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@ -126,6 +177,11 @@
"node": ">= 6.0.0"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sprintf-kit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sprintf-kit/-/sprintf-kit-2.0.1.tgz",
@ -134,6 +190,30 @@
"es5-ext": "^0.10.53"
}
},
"node_modules/systeminformation": {
"version": "5.9.4",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.9.4.tgz",
"integrity": "sha512-FOsiTn0CyJZoj9kIhla11ndsMzbbwwuriul81wpqIBt9IpbxHZ6P/oZCphIFgJrwqjTnme0Qp1HDzIkUD9Xr/g==",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=4.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
@ -143,6 +223,14 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
"integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw=="
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"engines": {
"node": ">= 4.0.0"
}
}
},
"dependencies": {
@ -222,6 +310,37 @@
"type": "^2.5.0"
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
},
"iconv-lite": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"log": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/log/-/log-6.2.0.tgz",
@ -243,6 +362,15 @@
"node-forge": "^0.10.0"
}
},
"nexline": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/nexline/-/nexline-1.2.2.tgz",
"integrity": "sha512-YLX5uoqNP7XVsXk889i8ZQcuMkukA4My4JD9wqTRLT+4dFo6QEEn+hU26J5H89m+mzW9BfhDgriGdbMEP06eeQ==",
"requires": {
"fs-extra": "^8.1.0",
"iconv-lite": "^0.5.0"
}
},
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@ -253,6 +381,11 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sprintf-kit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sprintf-kit/-/sprintf-kit-2.0.1.tgz",
@ -261,6 +394,11 @@
"es5-ext": "^0.10.53"
}
},
"systeminformation": {
"version": "5.9.4",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.9.4.tgz",
"integrity": "sha512-FOsiTn0CyJZoj9kIhla11ndsMzbbwwuriul81wpqIBt9IpbxHZ6P/oZCphIFgJrwqjTnme0Qp1HDzIkUD9Xr/g=="
},
"tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
@ -270,6 +408,11 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
"integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw=="
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
}
}
}

View file

@ -20,6 +20,8 @@
"applause-cli": "^1.7.0",
"log": "^6.2.0",
"make-cert": "^1.2.1",
"nexline": "^1.2.2",
"systeminformation": "^5.9.4",
"tweetnacl": "^1.0.3"
}
}

30
src/lib/agent/Agent.mjs Normal file
View file

@ -0,0 +1,30 @@
"use strict";
import os from 'os';
import make_cert from 'make-cert';
import systeminfo from 'systeminformation';
import hash from '../crypto/hash.mjs';
class Agent {
constructor() {
}
async init(secret_join) {
this.secret_join = secret_join;
/** Our peer id - calculated automatically from the system's uuid */
this.peer_id = hash("sha256", "base64", await systeminfo.system().serial)
.replace(/[+/=]/g, "");
this.peer_name = os.hostname();
// Properties: key, cert
this.cert = make_cert(`${our_id}.systemquery-peer.localhost`);
}
}
export default Agent;

View file

@ -0,0 +1,9 @@
"use strict";
class PeerServer {
constructor() {
}
}
export default PeerServer;

9
src/lib/crypto/hash.mjs Normal file
View file

@ -0,0 +1,9 @@
"use strict";
import crypto from 'crypto';
export default function hash(algorithm, encoding, data) {
let hasher = crypto.createHash(algorithm);
hasher.update(data);
return hasher.digest(encoding);
}

View file

@ -0,0 +1,56 @@
"use strict";
import { randomBytes, secretbox } from 'tweetnacl';
/**
* Creates a new key ready for encryption.
* @return {string} A new base64-encoded key.
*/
function make_key() {
return randomBytes(secretbox.keyLength).toString("base64");
}
/**
* Encrypts the given data with the given key.
* @param {string} key The base64-encoded key to use to encrypt the data.
* @param {string} data The data to encrypt.
* @return {string} The encrypted data, base64 encoded.
*/
function encrypt(key, data) {
const key_bytes = Buffer.from(key, "base64");
const nonce = randomBytes(secretbox.nonceLength);
const data_bytes = Buffer.from(data, "utf-8");
const cipher_bytes = secretbox(data_bytes, nonce, key_bytes);
const concat_bytes = Buffer.concat([nonce, cipher_bytes]);
key_bytes.fill(0);
nonce.fill(0);
return concat_bytes.toString("base64");
}
/**
* Decrypts the given data with the given key.
* @param {string} key The base64-encoded keyto use to decrypt the data.
* @param {string} cipher_text The base64-encoded ciphertext to decrypt.
* @return {string} The decoded data, utf-8 encoded.
*/
function decrypt(key, cipher_text) {
const concat_bytes = Buffer.from(cipher_text, "base64");
const key_bytes = Buffer.from(key, "basse64");
const nonce = concat_bytes.slice(0, secretbox.nonceLength);
const cipher_bytes = concat_bytes.slice(secretbox.nonceLength);
const data_bytes = secretbox.open(cipher_bytes, nonce, key_bytes);
// Failed to decrypt message. Could be because the nonce, key, or ciphertext is invalid
// Ref https://github.com/dchest/tweetnacl-js/blob/master/test/04-secretbox.quick.js
// Ref https://github.com/dchest/tweetnacl-js/wiki/Examples#secretbox
if(!data_bytes) return null;
return data_bytes.toString("utf-8");
}
export { make_key, encrypt, decrypt };

View file

@ -0,0 +1,73 @@
"use strict";
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
* A pair of functions to make writing to streams in Node.js less painful.
* I've used these in a number of different projects so far, and they both
* *appear* to be stable.
* @licence MPL-2.0
*
* Changelog
*********************************
* 22nd March 2021
* We should have brought this into our code snippet library *ages* ago lol :P
*/
/**
* Writes data to a stream, automatically waiting for the drain event if asked.
* @param {stream.Writable} stream_out The writable stream to write to.
* @param {string|Buffer|Uint8Array} data The data to write.
* @return {Promise} A promise that resolves when writing is complete.
*/
function write_safe(stream_out, data) {
return new Promise(function (resolve, reject) {
// console.log(`Beginning write`);
// Handle errors
let handler_error = (error) => {
stream_out.off("error", handler_error);
// console.log(`Error received, handler detached, rejecting`);
reject(error);
};
stream_out.on("error", handler_error);
let returnval = typeof data == "string" ? stream_out.write(data, "utf-8") : stream_out.write(data);
// console.log(`Write returned`, returnval);
if(returnval) {
// We're good to go
stream_out.off("error", handler_error);
// console.log("We're good to go, handler detached, resolving");
resolve();
}
else {
// We need to wait for the drain event before continuing
// console.log(`Waiting for drain event`);
stream_out.once("drain", () => {
stream_out.off("error", handler_error);
// console.log(`Drain event received, handler detached, resolving`);
resolve();
});
}
});
}
/**
* Waits for the given stream to end and finish writing data.
* @param {stream.Writable} stream The stream to end.
* @param {Buffer|string} [chunk=undefined] Optional. A chunk to write when calling .end().
* @return {Promise} A Promise that resolves when writing is complete.
*/
function end_safe(stream) {
return new Promise((resolve, _reject) => {
// Handle streams that have already been closed
if(stream.writableFinished) resolve();
stream.once("finish", resolve);
stream.end();
});
}
export {
write_safe, end_safe
};

View file

@ -0,0 +1,70 @@
"use strict";
import net from 'net';
import { EventEmitter, once } from 'events';
import l from 'log';
import nexline from 'nexline';
import settings from '../../settings.mjs';
/**
* Represents a connection to a single endpoint.
*/
class Connection extends EventEmitter {
constructor() {
super();
}
/**
* 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");
this.socket.setKeepAlive(true);
// this.reader = nexline({
// input: this.socket
// });
//
// this.read_task = read_loop();
}
destroy() {
this.socket.destroy();
this.emit("destroy");
}
async read_loop() {
try {
for await (let line of this.reader) {
handle_message(line);
}
}
catch(error) {
l.warn(`Warning: Killing connection to ${this.address}:${this.port} after error: ${settings.cli.verbose ? error : error.message}`);
}
finally {
this.destroy();
}
}
handle_message(text) {
let message = JSON.parse(text);
}
}
export default Connection;

View file

@ -0,0 +1,34 @@
"use strict";
import { once } from 'events';
import tls from 'tls';
import { write_safe } from '../io/StreamHelpers.mjs';
import { encrypt, decrypt } from '../crypto/secretbox.mjs';
export default async function starttls(socket, secret_join, { cert: cert_ours, key: key_ours }, is_server = true) {
// 1: Encrypt our self-signed cert and send it to the peer
const cert_encrypted = encrypt(secret_join, cert_ours);
await write_safe(socket, Buffer.from(cert_encrypted, "base64"));
// 2: Receive our peer's certificate and decrypt it
let data_bytes = Buffer.from(await once(socket, "data")[0], "base64");
const cert_theirs = decrypt(secret_join, data_bytes.toString("base64"));
if(cert_theirs === null) {
socket.destroy();
return null;
}
// 3: Upgrade to proper TLS
const tls_socket = new tls.TLSSocket(socket, {
isServer: is_server,
requestCert: true,
ca: cert_theirs,
cert: cert_ours,
key: key_ours
});
return tls_socket;
}