diff --git a/package.json b/package.json
index f4887f0..88648de 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,11 @@
"test": "grunt test"
},
"dependencies": {
- "mongodb": "^2.1.20"
+ "co": "^4.6.0",
+ "koa": "^1.2.0",
+ "koa-router": "^5.4.0",
+ "mongodb": "^2.1.20",
+ "npmlog": "^2.0.4"
},
"devDependencies": {
"chai": "^3.5.0",
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..530af93
--- /dev/null
+++ b/server.js
@@ -0,0 +1,64 @@
+/**
+ * Mailvelope - secure email with OpenPGP encryption for Webmail
+ * Copyright (C) 2016 Mailvelope GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License version 3
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+const cluster = require('cluster');
+const numCPUs = require('os').cpus().length;
+const log = require('npmlog');
+
+//
+// Error handling
+//
+
+process.on('SIGTERM', () => {
+ log.warn('exit', 'Exited on SIGTERM');
+ process.exit(0);
+});
+
+process.on('SIGINT', () => {
+ log.warn('exit', 'Exited on SIGINT');
+ process.exit(0);
+});
+
+process.on('uncaughtException', err => {
+ log.error('server', 'Uncaught exception ', err);
+ process.exit(1);
+});
+
+//
+// Start worker cluster depending on number of CPUs
+//
+
+if (cluster.isMaster) {
+ for (var i = 0; i < numCPUs; i++) {
+ cluster.fork();
+ }
+
+ cluster.on('fork', worker => {
+ log.info('cluster', 'Forked worker #%s [pid:%s]', worker.id, worker.process.pid);
+ });
+
+ cluster.on('exit', worker => {
+ log.warn('cluster', 'Worker #%s [pid:%s] died', worker.id, worker.process.pid);
+ setTimeout(() => {
+ cluster.fork();
+ }, 5000);
+ });
+} else {
+ require('./src/worker');
+}
\ No newline at end of file
diff --git a/src/ctrl/public-key.js b/src/ctrl/public-key.js
new file mode 100644
index 0000000..704f025
--- /dev/null
+++ b/src/ctrl/public-key.js
@@ -0,0 +1,35 @@
+/**
+ * Mailvelope - secure email with OpenPGP encryption for Webmail
+ * Copyright (C) 2016 Mailvelope GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License version 3
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+/**
+ * A controller that handlers PGP public keys queries to the database
+ */
+class PublicKey {
+
+ /**
+ * Create an instance of the controller
+ * @param {Object} mongo An instance of the MongoDB client
+ */
+ constructor(mongo) {
+ this._mongo = mongo;
+ }
+
+}
+
+module.exports = PublicKey;
\ No newline at end of file
diff --git a/src/routes/hkp.js b/src/routes/hkp.js
new file mode 100644
index 0000000..cef2084
--- /dev/null
+++ b/src/routes/hkp.js
@@ -0,0 +1,129 @@
+/**
+ * Mailvelope - secure email with OpenPGP encryption for Webmail
+ * Copyright (C) 2016 Mailvelope GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License version 3
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+/**
+ * An implementation of the OpenPGP HTTP Keyserver Protocol (HKP)
+ * See https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
+ */
+class HKP {
+
+ /**
+ * Create an instance of the HKP server
+ * @param {Object} publicKey An instance of the public key controller
+ */
+ constructor(publicKey) {
+ this._publicKey = publicKey;
+ }
+
+ /**
+ * Public key lookup via http GET
+ * @param {Object} ctx The koa request/response context
+ */
+ *lookup(ctx) {
+ var params = this.parseQueryString(ctx);
+ if (!params) {
+ return; // invalid request
+ }
+
+ this.setHeaders(ctx);
+ if (params.mr) {
+ this.setGetMRHEaders(ctx);
+ }
+ ctx.body = yield Promise.resolve('----- BEGIN PUBLIC PGP KEY -----');
+ }
+
+ /**
+ * Public key upload via http POST
+ * @param {Object} ctx The koa request/response context
+ */
+ *add(ctx) {
+ ctx.throw(501, 'Not implemented!');
+ return yield Promise.resolve();
+ }
+
+ /**
+ * Parse the query string for a lookup request and set a corresponding
+ * error code if the requests is not supported or invalid.
+ * @param {Object} ctx The koa request/response context
+ * @return {Object} The query parameters or undefined for an invalid request
+ */
+ parseQueryString(ctx) {
+ let q = ctx.query;
+ let params = {
+ op: q.op, // operation ... only 'get' is supported
+ mr: q.options === 'mr', // machine readable
+ keyid: this.checkId(q.search) ? q.search.replace('0x', '') : null,
+ email: this.checkEmail(q.search) ? q.search : null,
+ };
+
+ if (params.op !== 'get') {
+ ctx.status = 501;
+ ctx.body = 'Not implemented!';
+ return;
+ } else if (!params.keyid && !params.email) {
+ ctx.status = 404;
+ ctx.body = 'Invalid request!';
+ return;
+ }
+
+ return params;
+ }
+
+ /**
+ * Checks for a valid email address.
+ * @param {String} address The email address
+ * @return {Boolean} If the email address if valid
+ */
+ checkEmail(address) {
+ return /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/.test(address);
+ }
+
+ /**
+ * Checks for a valid key id in the query string. A key must be prepended
+ * with '0x' and can be between 8 and 40 characters long.
+ * @param {String} keyid The key id
+ * @return {Boolean} If the key id is valid
+ */
+ checkId(keyid) {
+ return /^0x[a-fA-Z0-9]{8,40}/.test(keyid);
+ }
+
+ /**
+ * Set HTTP headers for the HKP requests.
+ * @param {Object} ctx The koa request/response context
+ */
+ setHeaders(ctx) {
+ ctx.set('Access-Control-Allow-Origin', '*');
+ ctx.set('Cache-Control', 'no-cache');
+ ctx.set('Pragma', 'no-cache');
+ ctx.set('Connection', 'keep-alive');
+ }
+
+ /**
+ * Set HTTP headers for a GET requests with 'mr' (machine readable) options.
+ * @param {Object} ctx The koa request/response context
+ */
+ setGetMRHEaders(ctx) {
+ ctx.set('Content-Type', 'application/pgp-keys; charset=UTF-8');
+ ctx.set('Content-Disposition', 'attachment; filename=openpgpkey.asc');
+ }
+
+}
+
+module.exports = HKP;
\ No newline at end of file
diff --git a/src/worker.js b/src/worker.js
new file mode 100644
index 0000000..45fe4e1
--- /dev/null
+++ b/src/worker.js
@@ -0,0 +1,79 @@
+/**
+ * Mailvelope - secure email with OpenPGP encryption for Webmail
+ * Copyright (C) 2016 Mailvelope GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License version 3
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+const co = require('co');
+const fs = require('fs');
+const app = require('koa')();
+const log = require('npmlog');
+const router = require('koa-router')();
+const Mongo = require('./dao/mongo');
+const PublicKey = require('./ctrl/public-key');
+const HKP = require('./routes/hkp');
+
+let mongo, publicKey, hkp;
+
+//
+// Configure koa router
+//
+
+router.get('/pks/lookup', function *() {
+ yield hkp.lookup(this);
+});
+router.post('/pks/add', function *() {
+ yield hkp.add(this);
+});
+
+app.use(router.routes());
+app.use(router.allowedMethods());
+app.on('error', (err, ctx) => log.error('worker', 'Unknown server error', err, ctx));
+
+//
+// Module initialization
+//
+
+function injectDependencies() {
+ let credentials = readCredentials();
+ mongo = new Mongo({
+ uri: process.env.MONGO_URI || credentials.mongoUri,
+ user: process.env.MONGO_USER || credentials.mongoUser,
+ password: process.env.MONGO_PASS || credentials.mongoPass
+ });
+ publicKey = new PublicKey(mongo);
+ hkp = new HKP(publicKey);
+}
+
+function readCredentials() {
+ try {
+ return JSON.parse(fs.readFileSync(__dirname + '/../credentials.json'));
+ } catch(e) {
+ log.info('worker', 'No credentials.json found ... using environment vars.');
+ }
+}
+
+//
+// Start app ... connect to the database and start listening
+//
+
+co(function *() {
+
+ injectDependencies();
+ yield mongo.connect();
+ app.listen(process.env.PORT || 8888);
+
+}).catch(err => log.error('worker', 'Initialization failed!', err));
\ No newline at end of file