Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS and https handling #12

Merged
merged 1 commit into from
Nov 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions localhost/csr.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIChzCCAW8CAQAwQjELMAkGA1UEBhMCVVMxFTATBgNVBAcMDERlZmF1bHQgQ2l0
eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMxePK+2UJJIu37foPBWSOfWy2BVfrqIkEMJ8rCBA0tC
D2GLu7Pp23NN/N1aSfFWKSLBlE9ipxi6x13dTEqj2GO4sq9ONAU87F6C1xuNz3qU
VOrmKQFFfjn2Wi0uULfaHqDUH0JNe1sZBUhXKXAXA3pHznvYjqixxL0QHlEO+knG
yB3fxtPDsVAz1pSCo48HA4gF5HyYypgbqtZvKCW0uLYcNHrigM/K6yhffYrssFGp
B2Tu+CK8Kem6iGgaQVBM/Y4+Dph/83YbaYBGQXyEfGLfOaNZ7EalGU15eCThox1w
mGDKxo31mihQHVVQkRwEOu7ZdAnrnIIqhg1MfbfZGXsCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQBhQ55pSEvnyIfb5MbjvDqSSwn1+rNZwZc8OQ4nwA4S8NKmKSFT
qtLPa+s5C2ML6cdz0nWOuVgRrPmGTC6T3Wb8vQcrwIBXVwtBJoLz9qK7xg7hJGME
3bSHNv5vZgki6JNM+qyR/UAWMaoe1WvXOtdmm0rH16ye4sSKaKFrHdsyFD5KxlpE
qTCu4/bSSrzPuK6LGDQn1eFiJmS+uOQHfdkVORvkHp535E6a9Ybrsr6WiTByv5bc
zJi4KbV6QtYYjASQb79qF5K02+8Lci7bb02XoxTObZWBSR1iDfwjv8F4BE/+CeNW
RpWPKG6kIwApVUS9IlrlwFiwduCubFM+tceY
-----END CERTIFICATE REQUEST-----
19 changes: 19 additions & 0 deletions localhost/tls.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCzCCAfMCFH6KzlAt2udwaL20C0EwIpsZILf3MA0GCSqGSIb3DQEBCwUAMEIx
CzAJBgNVBAYTAlVTMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQwHhcNMjExMTI1MTkyMjIwWhcNNDkwNDExMTkyMjIw
WjBCMQswCQYDVQQGEwJVUzEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQK
DBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAzF48r7ZQkki7ft+g8FZI59bLYFV+uoiQQwnysIEDS0IPYYu7s+nbc038
3VpJ8VYpIsGUT2KnGLrHXd1MSqPYY7iyr040BTzsXoLXG43PepRU6uYpAUV+OfZa
LS5Qt9oeoNQfQk17WxkFSFcpcBcDekfOe9iOqLHEvRAeUQ76ScbIHd/G08OxUDPW
lIKjjwcDiAXkfJjKmBuq1m8oJbS4thw0euKAz8rrKF99iuywUakHZO74Irwp6bqI
aBpBUEz9jj4OmH/zdhtpgEZBfIR8Yt85o1nsRqUZTXl4JOGjHXCYYMrGjfWaKFAd
VVCRHAQ67tl0CeucgiqGDUx9t9kZewIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/
5sHm2veq1OXtC1LrsEr8fwh9k6Xkr536HCdxOlj19dlYeIXfScBbY6mv8v7zuSks
A83hQ9WgG8SvXCDhZiVapkKk+yZ88QrCvhMLyJse0r1NwPNLAVuZqnwQnSICKSU4
fd7t8isUm3gnI+n/bDlYij3xeTAKAJb+LBuryrUWVXdW1CsmKJmUDRlD90EvtQrQ
8o5q21QGVnRY46x9/rttHAKocxQk3U7lnqYB7iOLT025m80lpO4q67JUdw43XM/b
89sZGgXS0HImqlJSCSHS0n+Cm/GC0B2zPBPUafY3qabSpgYZOKgCofbhA+ArKHn+
LQxNM1QlUq/qzpSotE5D
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions localhost/tls.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAzF48r7ZQkki7ft+g8FZI59bLYFV+uoiQQwnysIEDS0IPYYu7
s+nbc0383VpJ8VYpIsGUT2KnGLrHXd1MSqPYY7iyr040BTzsXoLXG43PepRU6uYp
AUV+OfZaLS5Qt9oeoNQfQk17WxkFSFcpcBcDekfOe9iOqLHEvRAeUQ76ScbIHd/G
08OxUDPWlIKjjwcDiAXkfJjKmBuq1m8oJbS4thw0euKAz8rrKF99iuywUakHZO74
Irwp6bqIaBpBUEz9jj4OmH/zdhtpgEZBfIR8Yt85o1nsRqUZTXl4JOGjHXCYYMrG
jfWaKFAdVVCRHAQ67tl0CeucgiqGDUx9t9kZewIDAQABAoIBAQCDnq22/NQnYnBe
5efg4bFSnyOch3N27zz58A49XtmgPotpZ3UcCiErwa55YQz+QV984u+BsSes5Z5A
9aWM7LkQgIOUI+mc9f/FXr7rIAngCGgoYNNH3lnNOrwZHRsfTXssWXFIYl5v7U1Z
qckmR6wVtOlnGbHHM7ZhjV/5FIxdtmG6MibxtkQSgm4T8954XMcziA86LnQ1v5M7
7mHHyBm7Qe3ib0OdSqVz9O1blFd8BFEG9L9XaHkDhFXO5iCN2XBwl97tpcWQTFHA
mf6dmyjtfBziV0SFErbUyJYDrNRx5UQeCVNOpfT5rpM9eA49xx7QxyVBkyqzLp/a
pMvI5vkBAoGBAOmw4RBrFHYDgkPD7FHYp0avEGmV0JkR+vA+7W01BZr9Igmy6hxp
3EmCNG/L2ZI55Ept/RYBOkDg+bi9bz3WvPYa0MeDOi6XEMsaPUdnWqWWA5icHl4v
/KheUt/iTCibGYWX5Eu7PFdpDdmFzF9A1uTOHuamOZpM1sMOTwLLqHSFAoGBAN/g
vnaH8weejwXFtN+D8LkPmUW1TdTrUiqbYPGJwiCdDhpB37TeLeMQj8bOAk98VKvR
EB9jcw9j/WgaGSkzVvpi4Q2ZnGi9jNaa8wzEErQBnm3Mfw41LsYXt+ZnDg3UFrJ/
dEBzqYSLao51+VoX/ZyhXzKyvAbT77EVedm/b7X/AoGBANGllyONjNuapkB5Agcj
IF4vK8AtYOgR01e4fHPef1reAK1GzvQSnEduAfDRpiyitwV2yvf0vff6XM25VJTb
ksYOpIJ4XbfyWmR688KdHBs1C6DbXfsNfdLmW97yO3SqQCkzbOHr5WRdoMkmWYSS
vLajm+E7+q1MhdaTfZp6bnOpAoGBAMsdeU/K6giYp4QCKqa7avRLnbCr3FB3q5Vy
YRLi/BhgxYG3EEJlbVZcGUWydFAvKha0V59St/pXqnn/a6KArMIAYdTX8BrrFlNC
Q47qeVmNOnK9nOyD/crFjBhimVKcgHczwYIULdFON7/GcxN1PqgTlG5H0OWU9RtB
s8qFr9F7AoGBAJC/rw2A58NQjv4lQEKHRxP1wkTH5kc7WqJS3kQ4HDNwsAe9YbN8
SyagwqueZIEEimgXguCYIMnk9Z/gN4tdS2J8UZxDki2VUs2Q0b/M7TRb8MtImdox
SrrNE80JiGaJN7Ys9rlys4kNDD9oXq6Svj4DR+201ykQii7NcdtaABq6
-----END RSA PRIVATE KEY-----
29 changes: 18 additions & 11 deletions munkey/munkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,27 @@ async function main(services: ServiceContainer): Promise<void> {
.then(() => process.exit(0));
}

generateNewIdentity()
.then(id => {
const args = parseCommandLineArgs(process.argv.slice(2));
const {
root_dir: rootPath,
port: portNum,
discovery_port: discoveryPortNum,
} = args;
const commandLineArgs = parseCommandLineArgs(process.argv.slice(2));

generateNewIdentity(commandLineArgs.root_dir)
.then(({ uniqueId, ...keyPair }) => {
// IMPORTANT: This line is to allow for self-signed certificates.
// Since we use TLS only for establishing an encrypted connection, not for validation,
// there is no need to validate the source. So, we set strict TLS to false.
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";

const storedProcedures = {
putAttachment: PouchDB.prototype.putAttachment,
getAttachment: PouchDB.prototype.getAttachment,
};

const {
root_dir: rootPath,
port: portNum,
discovery_port: discoveryPortNum,
in_memory: isInMemory,
} = commandLineArgs;

// Plugin options are created separately so that we can do full type-checking (see call to .plugin)
const pluginOptions: DatabasePluginAttachment = {
putEncryptedAttachment(...args) {
Expand Down Expand Up @@ -252,20 +259,20 @@ generateNewIdentity()
const LocalDB = configurePlugins<DatabaseDocument, DatabasePluginAttachment>(
{
prefix: rootPath + path.sep + "munkey" + path.sep,
db: args.in_memory ? MemDown : undefined,
db: isInMemory ? MemDown : undefined,
} as PouchDB.Configuration.DatabaseConfiguration,
pluginOptions,
);
const AdminDB = configurePlugins<AdminDatabaseDocument, {}>(
{
prefix: rootPath + path.sep + "admin" + path.sep,
db: args.in_memory ? MemDown : undefined,
db: isInMemory ? MemDown : undefined,
} as PouchDB.Configuration.DatabaseConfiguration,
);

return Promise.resolve(configureLogging({
vault: new VaultService(LocalDB),
identity: new IdentityService(id),
identity: new IdentityService(uniqueId, keyPair),
activity: new ActivityService(bonjour()),
connection: new ConnectionService(),
web: new WebService(express()),
Expand Down
88 changes: 78 additions & 10 deletions munkey/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {

import express from "express";
import ip from "ip";
import { readFile } from "fs";
import * as bonjour from "bonjour";
import PouchDB from "pouchdb";
import usePouchDB from "express-pouchdb";
import {randomUUID} from "crypto";
import http from "http";
import https from "https";
import winston from "winston";
import ErrnoException = NodeJS.ErrnoException;
import path from "path";
Expand Down Expand Up @@ -64,8 +66,17 @@ type VaultSyncToken = PouchDB.Replication.Sync<DatabaseDocument>;
*
* @returns Promise which resolves to a new unique identifier string.
*/
function generateNewIdentity(): Promise<string> {
return Promise.resolve(randomUUID());
async function generateNewIdentity(rootDir: string): Promise<{ uniqueId: string } & TlsKeyPair> {
const keyPair = await IdentityService.loadTlsKeyPair(rootDir)
.catch(err => {
console.error("Could not load TLS certificate:", err);
return { key: undefined, cert: undefined };
});

return {
uniqueId: randomUUID(),
...keyPair,
};
}

/**
Expand Down Expand Up @@ -111,7 +122,7 @@ function configureRoutes(services: ServiceContainer, options?: ServerOptions): P
return services
.admin.initialize()
.then(adminService => services.vault.useAdminService(adminService))
.then(() => services.web.listen(portNum))
.then(() => services.web.listen({ portNum, tlsKeyPair: services.identity.getTlsKeyPair() }))
.then(async () => {
if (discoveryPortNum && await services.activity.broadcast(
services.identity.getId(), discoveryPortNum, portNum))
Expand Down Expand Up @@ -357,6 +368,11 @@ class VaultService extends Service {
}
}

interface TlsKeyPair {
key: Buffer,
cert: Buffer,
}

/**
* @name IdentityService
* @summary Service container for local identity information.
Expand All @@ -368,7 +384,7 @@ class VaultService extends Service {
*/
class IdentityService extends Service {
private readonly uniqueId: string;
constructor(uniqueId: string) {
constructor(uniqueId: string, private readonly keyPair?: TlsKeyPair) {
super();
this.uniqueId = uniqueId;
}
Expand All @@ -382,6 +398,32 @@ class IdentityService extends Service {
public getId(): string {
return this.uniqueId;
}

public static loadTlsKeyPair(rootDir: string,
keyPath: string = path.join(rootDir, "tls.key"),
certPath: string = path.join(rootDir, "tls.crt")): Promise<TlsKeyPair>
{
return Promise.all([
IdentityService.loadKey(keyPath),
IdentityService.loadKey(certPath)
])
.then(([ key, cert ]) => ({ key, cert }));
}

private static loadKey(keyPath: string): Promise<Buffer> {
return new Promise<Buffer>(function(resolve, reject) {
readFile(keyPath, (err, data: Buffer) => {
if (err) reject(err);
else {
resolve(data);
}
});
});
}

public getTlsKeyPair(): TlsKeyPair {
return this.keyPair;
}
}

/**
Expand Down Expand Up @@ -430,10 +472,11 @@ class ActivityService extends Service {
{
const logger = this.logger;
const peerResponse: string|null = await new Promise<string>(function(resolve, reject) {
http.get({
https.get({
hostname,
port: portNum?.toString(),
path: "/link",
rejectUnauthorized: false,
},
function(res: http.IncomingMessage) {
const data: string[] = [];
Expand Down Expand Up @@ -714,7 +757,7 @@ class ConnectionService extends Service {
{
let connectionMap = this.getOrCreateMap(vaultId);
let connectionKey = `${device.hostname}:${device.portNum}`;
let connectionUrl = `http://${connectionKey}/db/${vaultName}`
let connectionUrl = `https://${connectionKey}/db/${vaultName}`

if (!connectionMap.get(connectionKey)) {
this.logger.info("Adding remote connection to %s", connectionKey);
Expand Down Expand Up @@ -781,27 +824,52 @@ class ConnectionService extends Service {
}
}

interface WebServiceListenerOptions {
hostname?: string;
portNum?: number;
tlsKeyPair?: TlsKeyPair;
}

class WebService extends Service {
private server: http.Server;
private server: http.Server | https.Server;
private defaultPort: number;
private defaultTlsKeyPair: TlsKeyPair;

constructor(private app: express.Application) {
super();
this.server = null;
this.defaultPort = 8000;
this.defaultTlsKeyPair = null;
}

public getApplication(): express.Application {
return this.app;
}

public listen(portNum: number = this.defaultPort, hostname: string = ip.address()): Promise<http.Server> {
public listen(options?: WebServiceListenerOptions): Promise<http.Server>
{
const {
hostname = ip.address(),
portNum = this.defaultPort,
tlsKeyPair = this.defaultTlsKeyPair,
} = options;
this.defaultTlsKeyPair = this.defaultTlsKeyPair ?? tlsKeyPair;

if (tlsKeyPair) {
this.logger.info("Creating HTTPS server at https://%s:%d", hostname, portNum);
this.server = https.createServer({ rejectUnauthorized: false, ...tlsKeyPair }, this.getApplication());
}
else {
this.logger.info("Creating HTTP server at http://%s:%d", hostname, portNum);
this.server = http.createServer(this.getApplication());
}

return new Promise<http.Server>((resolve, reject) => {
const server: http.Server = this.getApplication().listen(
this.server.listen(
this.defaultPort = portNum,
hostname, () => {
this.logger.info("Listening on port %d", portNum);
resolve(server);
resolve(this.server);
})
.on("error", (err: ErrnoException) => {
if (err.code === "EADDRINUSE") {
Expand Down