Rename files
This commit is contained in:
147
src/service/public-key.js
Normal file
147
src/service/public-key.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const log = require('npmlog');
|
||||
const util = require('./util');
|
||||
|
||||
/**
|
||||
* Database documents have the format:
|
||||
* {
|
||||
* _id: "02C134D079701934", // the 16 byte key id in uppercase hex
|
||||
* publicKeyArmored: "-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK-----"
|
||||
* }
|
||||
*/
|
||||
const DB_TYPE = 'publickey';
|
||||
|
||||
/**
|
||||
* A controller that handlers PGP public keys queries to the database
|
||||
*/
|
||||
class PublicKey {
|
||||
|
||||
/**
|
||||
* Create an instance of the controller
|
||||
* @param {Object} openpgp An instance of OpenPGP.js
|
||||
* @param {Object} mongo An instance of the MongoDB client
|
||||
* @param {Object} email An instance of the Email Sender
|
||||
* @param {Object} userid An instance of the UserId controller
|
||||
*/
|
||||
constructor(openpgp, mongo, email, userid) {
|
||||
this._openpgp = openpgp;
|
||||
this._mongo = mongo;
|
||||
this._email = email;
|
||||
this._userid = userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a new public key
|
||||
* @param {String} options.publicKeyArmored The ascii armored pgp key block
|
||||
* @param {String} options.primaryEmail (optional) The key's primary email address
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*put(options) {
|
||||
// parse key block
|
||||
let publicKeyArmored = options.publicKeyArmored;
|
||||
let params = this.parseKey(publicKeyArmored);
|
||||
// check for existing verfied key by id or email addresses
|
||||
let verified = yield this._userid.getVerfied(params);
|
||||
if (verified) {
|
||||
throw util.error(304, 'Key for this user already exists: ' + verified.stringify());
|
||||
}
|
||||
// delete old/unverified key and user ids with the same key id
|
||||
yield this.remove({ keyid:params.keyid });
|
||||
// persist new key
|
||||
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
|
||||
if (r.insertedCount !== 1) {
|
||||
throw util.error(500, 'Failed to persist key');
|
||||
}
|
||||
// persist new user ids
|
||||
let userIds = yield this._userid.batch(params);
|
||||
// send mails to verify user ids (send only one if primary email is provided)
|
||||
yield this._email.sendVerification({ userIds, primaryEmail:options.primaryEmail });
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an ascii armored pgp key block and get its parameters.
|
||||
* @param {String} publicKeyArmored The ascii armored pgp key block
|
||||
* @return {Object} The key's id and user ids
|
||||
*/
|
||||
parseKey(publicKeyArmored) {
|
||||
let keys, userIds = [];
|
||||
try {
|
||||
keys = this._openpgp.key.readArmored(publicKeyArmored).keys;
|
||||
} catch(e) {
|
||||
log.error('public-key', 'Failed to parse PGP key:\n%s', publicKeyArmored, e);
|
||||
throw util.error(500, 'Failed to parse PGP key');
|
||||
}
|
||||
// get key user ids
|
||||
keys.forEach(key => userIds = userIds.concat(key.getUserIds()));
|
||||
userIds = util.deDup(userIds);
|
||||
// get key id
|
||||
return {
|
||||
keyid: keys[0].primaryKey.getKeyId().toHex().toUpperCase(),
|
||||
userIds: util.parseUserIds(userIds)
|
||||
};
|
||||
}
|
||||
|
||||
verify() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a verified public key from the database. Either the key id or the
|
||||
* email address muss be provided.
|
||||
* @param {String} options.keyid (optional) The public key id
|
||||
* @param {String} options.email (optional) The user's email address
|
||||
* @yield {Object} The public key document
|
||||
*/
|
||||
*get(options) {
|
||||
let keyid = options.keyid, email = options.email;
|
||||
let verified = yield this._userid.getVerfied({
|
||||
keyid: keyid ? keyid.toUpperCase() : undefined,
|
||||
userIds: email ? [{ email:email.toLowerCase() }] : undefined
|
||||
});
|
||||
if (!verified) {
|
||||
throw util.error(404, 'Key not found');
|
||||
}
|
||||
return yield this._mongo.get({ _id:verified.keyid }, DB_TYPE);
|
||||
}
|
||||
|
||||
flagForRemove() {
|
||||
|
||||
}
|
||||
|
||||
verifyRemove() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a public key document and its corresponding user id documents.
|
||||
* @param {String} options.keyid The key id
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*remove(options) {
|
||||
// remove key document
|
||||
yield this._mongo.remove({ _id:options.keyid }, DB_TYPE);
|
||||
// remove matching user id documents
|
||||
yield this._userid.remove({ keyid:options.keyid });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PublicKey;
|
||||
102
src/service/user-id.js
Normal file
102
src/service/user-id.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Database documents have the format:
|
||||
* {
|
||||
* _id: ObjectID, // randomly generated by MongoDB
|
||||
* email: "jon@example.com", // the email address in lowercase
|
||||
* name: "Jon Smith",
|
||||
* keyid: "02C134D079701934", // id of the public key document in uppercase hex
|
||||
* nonce: "123e4567-e89b-12d3-a456-426655440000", // verifier used to prove ownership
|
||||
* verified: true // if the user ID has been verified
|
||||
* }
|
||||
*/
|
||||
const DB_TYPE = 'userid';
|
||||
|
||||
/**
|
||||
* A controller that handles User ID queries to the database
|
||||
*/
|
||||
class UserId {
|
||||
|
||||
/**
|
||||
* Create an instance of the controller
|
||||
* @param {Object} mongo An instance of the MongoDB client
|
||||
*/
|
||||
constructor(mongo) {
|
||||
this._mongo = mongo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a list of user ids. There can only be one verified user ID for
|
||||
* an email address at any given time.
|
||||
* @param {String} options.keyid The public key id
|
||||
* @param {Array} options.userIds The userIds to persist
|
||||
* @yield {Array} A list of user ids with generated nonces
|
||||
*/
|
||||
*batch(options) {
|
||||
options.userIds.forEach(u => u.keyid = options.keyid); // set keyid on docs
|
||||
let r = yield this._mongo.batch(options.userIds, DB_TYPE);
|
||||
if (r.insertedCount !== options.userIds.length) {
|
||||
throw new Error('Failed to persist user ids');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a verified user IDs either by key id or email address.
|
||||
* There can only be one verified user ID for an email address
|
||||
* at any given time.
|
||||
* @param {String} options.keyid The public key id
|
||||
* @param {String} options.userIds A list of user ids to check
|
||||
* @yield {Object} The verified user ID document
|
||||
*/
|
||||
*getVerfied(options) {
|
||||
let keyid = options.keyid, userIds = options.userIds;
|
||||
if (keyid) {
|
||||
// try by key id
|
||||
let uids = yield this._mongo.list({ keyid }, DB_TYPE);
|
||||
let verified = uids.find(u => u.verified);
|
||||
if (verified) {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
if (userIds) {
|
||||
// try by email addresses
|
||||
for (let uid of userIds) {
|
||||
let uids = yield this._mongo.list({ email:uid.email }, DB_TYPE);
|
||||
let verified = uids.find(u => u.verified);
|
||||
if (verified) {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all user ids matching a certain query
|
||||
* @param {String} options.keyid The public key id
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*remove(options) {
|
||||
yield this._mongo.remove({ keyid:options.keyid }, DB_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserId;
|
||||
109
src/service/util.js
Normal file
109
src/service/util.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const addressparser = require('addressparser');
|
||||
|
||||
/**
|
||||
* Checks for a valid string
|
||||
* @param {} data The input to be checked
|
||||
* @return {boolean} If data is a string
|
||||
*/
|
||||
exports.isString = function(data) {
|
||||
return typeof data === 'string' || String.prototype.isPrototypeOf(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for a valid key id which is between 8 and 40 hex chars.
|
||||
* @param {string} data The key id
|
||||
* @return {boolean} If the key id if valid
|
||||
*/
|
||||
exports.validateKeyId = function(data) {
|
||||
if (!this.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
return /^[a-fA-F0-9]{8,40}$/.test(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for a valid email address.
|
||||
* @param {string} data The email address
|
||||
* @return {boolean} If the email address if valid
|
||||
*/
|
||||
exports.validateAddress = function(data) {
|
||||
if (!this.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
return re.test(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate an ascii armored public PGP key block.
|
||||
* @param {string} data The armored key block
|
||||
* @return {boolean} If the key is valid
|
||||
*/
|
||||
exports.validatePublicKey = function(data) {
|
||||
if (!this.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
const begin = /-----BEGIN PGP PUBLIC KEY BLOCK-----/;
|
||||
const end = /-----END PGP PUBLIC KEY BLOCK-----/;
|
||||
return begin.test(data) && end.test(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse an array of user id string to objects
|
||||
* @param {Array} userIds A list of user ids strings
|
||||
* @return {Array} An array of user id objects
|
||||
*/
|
||||
exports.parseUserIds = function(userIds) {
|
||||
let result = [];
|
||||
userIds.forEach(uid => result = result.concat(addressparser(uid)));
|
||||
return result.map(u => ({
|
||||
email: u.address ? u.address.toLowerCase() : undefined,
|
||||
name: u.name
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Deduplicates items in an array
|
||||
* @param {Array} list The list of items with duplicates
|
||||
* @return {Array} The list of items without duplicates
|
||||
*/
|
||||
exports.deDup = function(list) {
|
||||
var result = [];
|
||||
(list || []).forEach(function(i) {
|
||||
if (result.indexOf(i) === -1) {
|
||||
result.push(i);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an error with a custom status attribute e.g. for http codes.
|
||||
* @param {number} status The error's http status code
|
||||
* @param {string} message The error message
|
||||
* @return {Error} The resulting error object
|
||||
*/
|
||||
exports.error = function(status, message) {
|
||||
let err = new Error(message);
|
||||
err.status = status;
|
||||
return err;
|
||||
};
|
||||
Reference in New Issue
Block a user