Refactor public-key.js to use embedded user id documents
This commit is contained in:
parent
7bc4f8a9d9
commit
fe03ae213a
119
src/service/pgp.js
Normal file
119
src/service/pgp.js
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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');
|
||||
const openpgp = require('openpgp');
|
||||
const addressparser = require('addressparser');
|
||||
|
||||
const KEY_BEGIN = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
const KEY_END = '-----END PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
/**
|
||||
* A simple wrapper around OpenPGP.js
|
||||
*/
|
||||
class PGP {
|
||||
|
||||
/**
|
||||
* Parse an ascii armored pgp key block and get its parameters.
|
||||
* @param {String} publicKeyArmored ascii armored pgp key block
|
||||
* @return {Object} public key document to persist
|
||||
*/
|
||||
parseKey(publicKeyArmored) {
|
||||
publicKeyArmored = this.trimKey(publicKeyArmored);
|
||||
|
||||
let r = openpgp.key.readArmored(publicKeyArmored);
|
||||
if (r.err) {
|
||||
let error = r.err[0];
|
||||
log.error('pgp', 'Failed to parse PGP key:\n%s', publicKeyArmored, error);
|
||||
util.throw(500, 'Failed to parse PGP key');
|
||||
} else if (!r.keys || r.keys.length !== 1 || !r.keys[0].primaryKey) {
|
||||
util.throw(400, 'Invalid PGP key: only one key can be uploaded');
|
||||
}
|
||||
|
||||
let key = {
|
||||
keyId: r.keys[0].primaryKey.getKeyId().toHex(),
|
||||
fingerprint: r.keys[0].primaryKey.fingerprint,
|
||||
userIds: this.parseUserIds(r.keys[0].getUserIds()),
|
||||
created: r.keys[0].primaryKey.created,
|
||||
algorithm: r.keys[0].primaryKey.algorithm,
|
||||
keySize: r.keys[0].primaryKey.getBitSize(),
|
||||
publicKeyArmored
|
||||
};
|
||||
|
||||
if (!util.isKeyId(key.keyId) || !util.isFingerPrint(key.fingerprint)) {
|
||||
util.throw(400, 'Invalid PGP key: only v4 keys are accepted');
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all characters before and after the ascii armored key block
|
||||
* @param {string} data The ascii armored key
|
||||
* @return {string} The trimmed key block
|
||||
*/
|
||||
trimKey(data) {
|
||||
if (!this.validateKeyBlock(data)) {
|
||||
util.throw(400, 'Invalid PGP key: key block not found');
|
||||
}
|
||||
return KEY_BEGIN + data.split(KEY_BEGIN)[1].split(KEY_END)[0] + KEY_END;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an ascii armored public PGP key block.
|
||||
* @param {string} data The armored key block
|
||||
* @return {boolean} If the key is valid
|
||||
*/
|
||||
validateKeyBlock(data) {
|
||||
if (!util.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
const begin = data.indexOf(KEY_BEGIN);
|
||||
const end = data.indexOf(KEY_END);
|
||||
return begin >= 0 && end > begin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
parseUserIds(userIds) {
|
||||
if (!userIds.length) {
|
||||
util.throw(400, 'Invalid PGP key: no user id found');
|
||||
}
|
||||
|
||||
let result = [];
|
||||
userIds.forEach(uid => result = result.concat(addressparser(uid)));
|
||||
return result.map(uid => {
|
||||
if (!util.isEmail(uid.address)) {
|
||||
util.throw(400, 'Invalid PGP key: invalid user id');
|
||||
}
|
||||
return {
|
||||
name: uid.name,
|
||||
email: uid.address.toLowerCase(),
|
||||
verified: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PGP;
|
@ -17,15 +17,28 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const log = require('npmlog');
|
||||
const util = require('./util');
|
||||
const uuid = require('node-uuid');
|
||||
const tpl = require('../email/templates.json');
|
||||
|
||||
/**
|
||||
* 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-----"
|
||||
* _id: ObjectId, // a randomly generated MongoDB document ID
|
||||
* keyId: 'b8e4105cc9dedc77', // the 16 char key id in lowercase hex
|
||||
* fingerprint: 'e3317db04d3958fd5f662c37b8e4105cc9dedc77', // the 40 char key fingerprint in lowercase hex
|
||||
* userIds: [
|
||||
* {
|
||||
* name:'Jon Smith',
|
||||
* email:'jon@smith.com',
|
||||
* nonce: "123e4567-e89b-12d3-a456-426655440000", // UUID v4 verifier used to prove ownership
|
||||
* verified: true // if the user ID has been verified
|
||||
* }
|
||||
* ],
|
||||
* created: Sat Oct 17 2015 12:17:03 GMT+0200 (CEST), // key creation time as JavaScript Date
|
||||
* algorithm: 'rsa_encrypt_sign', // primary key alogrithm
|
||||
* keySize: 4096, // key length in bits
|
||||
* publicKeyArmored: '-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK-----'
|
||||
* }
|
||||
*/
|
||||
const DB_TYPE = 'publickey';
|
||||
@ -37,16 +50,14 @@ class PublicKey {
|
||||
|
||||
/**
|
||||
* Create an instance of the service
|
||||
* @param {Object} openpgp An instance of OpenPGP.js
|
||||
* @param {Object} pgp An instance of the OpenPGP.js wrapper
|
||||
* @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 service
|
||||
*/
|
||||
constructor(openpgp, mongo, email, userId) {
|
||||
this._openpgp = openpgp;
|
||||
constructor(pgp, mongo, email) {
|
||||
this._pgp = pgp;
|
||||
this._mongo = mongo;
|
||||
this._email = email;
|
||||
this._userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,69 +70,35 @@ class PublicKey {
|
||||
*put(options) {
|
||||
// parse key block
|
||||
let publicKeyArmored = options.publicKeyArmored, primaryEmail = options.primaryEmail, origin = options.origin;
|
||||
publicKeyArmored = publicKeyArmored.trim(); // remove whitespace
|
||||
let params = this._parseKey(publicKeyArmored);
|
||||
let key = this._pgp.parseKey(publicKeyArmored);
|
||||
// check for existing verfied key by id or email addresses
|
||||
let verified = yield this._userId.getVerfied(params);
|
||||
let verified = yield this.getVerified(key);
|
||||
if (verified) {
|
||||
util.throw(304, 'Key for this user already exists');
|
||||
}
|
||||
// store key in database
|
||||
let userIds = yield this._persisKey(publicKeyArmored, params);
|
||||
yield this._persisKey(key);
|
||||
// send mails to verify user ids (send only one if primary email is provided)
|
||||
yield this._sendVerifyEmail(userIds, primaryEmail, origin, publicKeyArmored);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an ascii armored pgp key block and get its parameters.
|
||||
* @param {String} publicKeyArmored ascii armored pgp key block
|
||||
* @return {Object} 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);
|
||||
util.throw(500, 'Failed to parse PGP key');
|
||||
}
|
||||
if (!keys || !keys.length || !keys[0].primaryKey) {
|
||||
util.throw(400, 'Invalid PGP key');
|
||||
}
|
||||
// get key user ids
|
||||
keys.forEach(key => userIds = userIds.concat(key.getUserIds()));
|
||||
userIds = util.deDup(userIds);
|
||||
// get key id
|
||||
let primKey = keys[0].primaryKey;
|
||||
return {
|
||||
keyid: primKey.getKeyId().toHex().toUpperCase(),
|
||||
userIds: util.parseUserIds(userIds),
|
||||
fingerprint: primKey.fingerprint.toUpperCase(),
|
||||
created: primKey.created,
|
||||
algorithm: primKey.algorithm,
|
||||
keylen: primKey.getBitSize()
|
||||
};
|
||||
yield this._sendVerifyEmail(key, primaryEmail, origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the public key and its user ids in the database.
|
||||
* @param {String} publicKeyArmored ascii armored pgp key block
|
||||
* @param {Object} params public key parameters
|
||||
* @yield {Array} The persisted user id documents
|
||||
* @param {Object} key public key parameters
|
||||
* @yield {undefined} The persisted user id documents
|
||||
*/
|
||||
*_persisKey(publicKeyArmored, params) {
|
||||
// delete old/unverified key and user ids with the same key id
|
||||
yield this.remove({ keyid:params.keyid });
|
||||
// persist new user ids
|
||||
let userIds = yield this._userId.batch(params);
|
||||
*_persisKey(key) {
|
||||
// delete old/unverified key
|
||||
yield this._mongo.remove({ fingerprint:key.fingerprint }, DB_TYPE);
|
||||
// generate nonces for verification
|
||||
for (let uid of key.userIds) {
|
||||
uid.nonce = uuid.v4();
|
||||
}
|
||||
// persist new key
|
||||
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
|
||||
let r = yield this._mongo.create(key, DB_TYPE);
|
||||
if (r.insertedCount !== 1) {
|
||||
// rollback user ids
|
||||
yield this.remove({ keyid:params.keyid });
|
||||
util.throw(500, 'Failed to persist key');
|
||||
}
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,63 +107,107 @@ class PublicKey {
|
||||
* @param {Array} userIds user id documents containg the verification nonces
|
||||
* @param {string} primaryEmail the public key's primary email address
|
||||
* @param {Object} origin the server's origin (required for email links)
|
||||
* @param {String} publicKeyArmored The ascii armored pgp key block
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*_sendVerifyEmail(userIds, primaryEmail, origin, publicKeyArmored) {
|
||||
*_sendVerifyEmail(key, primaryEmail, origin) {
|
||||
let userIds = key.userIds, keyId = key.keyId;
|
||||
// check for primary email (send only one email)
|
||||
let primaryUserId = userIds.find(uid => uid.email === primaryEmail);
|
||||
if (primaryUserId) {
|
||||
userIds = [primaryUserId];
|
||||
}
|
||||
// send emails
|
||||
for (let userId of userIds) {
|
||||
userId.publicKeyArmored = publicKeyArmored; // set key for encryption
|
||||
yield this._email.send({ template:tpl.verifyKey, userId, origin });
|
||||
userId.publicKeyArmored = key.publicKeyArmored; // set key for encryption
|
||||
yield this._email.send({ template:tpl.verifyKey, userId, keyId, origin });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a user id by proving knowledge of the nonce.
|
||||
* @param {string} keyId Correspronding public key id
|
||||
* @param {string} nonce The verification nonce proving email address ownership
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*verify(options) {
|
||||
let keyId = options.keyId, nonce = options.nonce;
|
||||
// look for verification nonce in database
|
||||
let query = { keyId, 'userIds.nonce':nonce };
|
||||
let key = yield this._mongo.get(query, DB_TYPE);
|
||||
if (!key) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
// flag the user id as verified
|
||||
yield this._mongo.update(query, {
|
||||
'userIds.$.verified': true,
|
||||
'userIds.$.nonce': null
|
||||
}, DB_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a verified key already exists either by fingerprint, 16 char key id,
|
||||
* or email address. There can only be one verified user ID for an email address
|
||||
* at any given time.
|
||||
* @param {Array} userIds A list of user ids to check
|
||||
* @param {string} fingerprint The public key fingerprint
|
||||
* @param {string} keyId (optional) The public key id
|
||||
* @yield {Object} The verified key document
|
||||
*/
|
||||
*getVerified(options) {
|
||||
let fingerprint = options.fingerprint, userIds = options.userIds, keyId = options.keyId;
|
||||
let queries = [];
|
||||
// query by fingerprint
|
||||
if (fingerprint) {
|
||||
queries.push({
|
||||
fingerprint: fingerprint.toLowerCase(),
|
||||
'userIds.verified': true
|
||||
});
|
||||
}
|
||||
// query by key id (to prevent key id collision)
|
||||
if (keyId) {
|
||||
queries.push({
|
||||
keyId: keyId.toLowerCase(),
|
||||
'userIds.verified': true
|
||||
});
|
||||
}
|
||||
// query by user id
|
||||
if (userIds) {
|
||||
queries = queries.concat(userIds.map(uid => ({
|
||||
userIds: {
|
||||
$elemMatch: {
|
||||
'email': uid.email.toLowerCase(),
|
||||
'verified': true
|
||||
}
|
||||
}
|
||||
})));
|
||||
}
|
||||
return yield this._mongo.get({ $or:queries }, DB_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a verified public key from the database. Either the key id or the
|
||||
* email address muss be provided.
|
||||
* @param {String} keyid (optional) The public key id
|
||||
* @param {String} email (optional) The user's email address
|
||||
* @yield {Object} The public key document
|
||||
* @param {string} fingerprint (optional) The public key fingerprint
|
||||
* @param {string} keyId (optional) The public key id
|
||||
* @param {String} 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: this._formatKeyId(keyid),
|
||||
userIds: this._formatUserIds(email)
|
||||
});
|
||||
if (!verified) {
|
||||
let fingerprint = options.fingerprint, keyId = options.keyId, email = options.email;
|
||||
// look for verified key
|
||||
let userIds = email ? [{ email:email }] : undefined;
|
||||
let key = yield this.getVerified({ keyId, fingerprint, userIds });
|
||||
if (!key) {
|
||||
util.throw(404, 'Key not found');
|
||||
}
|
||||
let key = yield this._mongo.get({ _id:verified.keyid }, DB_TYPE);
|
||||
let params = this._parseKey(key.publicKeyArmored);
|
||||
params.publicKeyArmored = key.publicKeyArmored;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert key id to the format used in the database.
|
||||
* @param {string} keyid the public key id
|
||||
* @return {string} the formatted key id
|
||||
*/
|
||||
_formatKeyId(keyid) {
|
||||
if (!util.isString(keyid)) {
|
||||
return;
|
||||
}
|
||||
keyid = keyid.toUpperCase(); // use uppercase key ids
|
||||
let len = keyid.length;
|
||||
return (len > 16) ? keyid.substr(len - 16, len) : keyid; // shorten to 16 bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the email address to the format used in the database.
|
||||
* @param {[type]} email [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
_formatUserIds(email) {
|
||||
return email ? [{ email:email.toLowerCase() }] : undefined;
|
||||
// clean json return value (_id, nonce)
|
||||
delete key._id;
|
||||
key.userIds = key.userIds.map(uid => ({
|
||||
name: uid.name,
|
||||
email: uid.email,
|
||||
verified: uid.verified
|
||||
}));
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,49 +215,66 @@ class PublicKey {
|
||||
* a verification email to the primary email address. Only one email
|
||||
* needs to sent to a single user id to authenticate removal of all user ids
|
||||
* that belong the a certain key id.
|
||||
* @param {String} keyid (optional) The public key id
|
||||
* @param {String} keyId (optional) The public key id
|
||||
* @param {String} email (optional) The user's email address
|
||||
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*requestRemove(options) {
|
||||
let keyid = options.keyid, email = options.email, origin = options.origin;
|
||||
let userIds = yield this._userId.flagForRemove({ keyid, email }, DB_TYPE);
|
||||
let keyId = options.keyId, email = options.email, origin = options.origin;
|
||||
let userIds = yield this._flagForRemove(keyId, email);
|
||||
if (!userIds.length) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
for (let userId of userIds) {
|
||||
yield this._email.send({ template:tpl.verifyRemove, userId, origin });
|
||||
yield this._email.send({ template:tpl.verifyRemove, userId, keyId, origin });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag all user IDs of a key for removal by generating a new nonce and
|
||||
* saving it. Either a key id or email address must be provided
|
||||
* @param {String} keyId (optional) The public key id
|
||||
* @param {String} email (optional) The user's email address
|
||||
* @yield {Array} A list of user ids with nonces
|
||||
*/
|
||||
*_flagForRemove(keyId, email) {
|
||||
let query = email ? { 'userIds.email':email } : { keyId };
|
||||
let key = yield this._mongo.get(query, DB_TYPE);
|
||||
if (!key) {
|
||||
return [];
|
||||
}
|
||||
if (email) {
|
||||
let nonce = uuid.v4();
|
||||
yield this._mongo.update(query, { 'userIds.$.nonce':nonce }, DB_TYPE);
|
||||
let uid = key.userIds.find(u => u.email === email);
|
||||
uid.nonce = nonce;
|
||||
return [uid];
|
||||
}
|
||||
if (keyId) {
|
||||
for (let uid of key.userIds) {
|
||||
let nonce = uuid.v4();
|
||||
yield this._mongo.update({ 'userIds.email':uid.email }, { 'userIds.$.nonce':nonce }, DB_TYPE);
|
||||
uid.nonce = nonce;
|
||||
}
|
||||
return key.userIds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the removal of the user's key id by proving knowledge of the nonce.
|
||||
* Also deletes all user id documents of that key id.
|
||||
* @param {string} keyid public key id
|
||||
* @param {string} keyId public key id
|
||||
* @param {string} nonce The verification nonce proving email address ownership
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*verifyRemove(options) {
|
||||
let keyid = options.keyid, nonce = options.nonce;
|
||||
let flagged = yield this._userId.getFlaggedForRemove({ keyid, nonce });
|
||||
let keyId = options.keyId, nonce = options.nonce;
|
||||
let flagged = yield this._mongo.get({ keyId, 'userIds.nonce':nonce }, DB_TYPE);
|
||||
if (!flagged) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
yield this.remove({ keyid });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a public key document and its corresponding user id documents.
|
||||
* @param {String} keyid The key id
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*remove(options) {
|
||||
let keyid = options.keyid;
|
||||
// remove key document
|
||||
yield this._mongo.remove({ _id:keyid }, DB_TYPE);
|
||||
// remove matching user id documents
|
||||
yield this._userId.remove({ keyid });
|
||||
yield this._mongo.remove({ keyId }, DB_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,164 +0,0 @@
|
||||
/**
|
||||
* 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 uuid = require('node-uuid');
|
||||
const util = require('./util');
|
||||
|
||||
/**
|
||||
* 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 service that handles User ID queries to the database
|
||||
*/
|
||||
class UserId {
|
||||
|
||||
/**
|
||||
* Create an instance of the service
|
||||
* @param {Object} mongo An instance of the MongoDB client
|
||||
*/
|
||||
constructor(mongo) {
|
||||
this._mongo = mongo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate nonces for verification and store a list of user ids. There
|
||||
* can only be one verified user ID for an email address at any given time.
|
||||
* @param {String} keyid The public key id
|
||||
* @param {Array} userIds The userIds to persist
|
||||
* @yield {Array} A list of user ids with generated nonces
|
||||
*/
|
||||
*batch(options) {
|
||||
let userIds = options.userIds, keyid = options.keyid;
|
||||
for (let uid of userIds) {
|
||||
uid.keyid = keyid; // set keyid on docs
|
||||
uid.nonce = uuid.v4(); // generate nonce for verification
|
||||
}
|
||||
let r = yield this._mongo.batch(userIds, DB_TYPE);
|
||||
if (r.insertedCount !== userIds.length) {
|
||||
util.throw(500, 'Failed to persist user ids');
|
||||
}
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a user id by proving knowledge of the nonce.
|
||||
* @param {string} keyid Correspronding public key id
|
||||
* @param {string} nonce The verification nonce proving email address ownership
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*verify(options) {
|
||||
let keyid = options.keyid, nonce = options.nonce;
|
||||
let uid = yield this._mongo.get({ keyid, nonce }, DB_TYPE);
|
||||
if (!uid) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
yield this._mongo.update(uid, { verified:true, nonce:null }, DB_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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} keyid The public key id
|
||||
* @param {String} 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) {
|
||||
let verified = yield this._mongo.get({ keyid, verified:true }, DB_TYPE);
|
||||
if (verified) {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
if (userIds) {
|
||||
for (let uid of userIds) {
|
||||
let verified = yield this._mongo.get({ email:uid.email, verified:true }, DB_TYPE);
|
||||
if (verified) {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag all user IDs of a key for removal by generating a new nonce and
|
||||
* saving it. Either a key id or email address must be provided
|
||||
* @param {String} keyid (optional) The public key id
|
||||
* @param {String} email (optional) The user's email address
|
||||
* @yield {Array} A list of user ids with nonces
|
||||
*/
|
||||
*flagForRemove(options) {
|
||||
let keyid = options.keyid, email = options.email;
|
||||
if (email) {
|
||||
let uid = yield this._mongo.get({ email }, DB_TYPE);
|
||||
if (uid) {
|
||||
let nonce = uuid.v4();
|
||||
yield this._mongo.update(uid, { nonce }, DB_TYPE);
|
||||
uid.nonce = nonce;
|
||||
return [uid];
|
||||
}
|
||||
}
|
||||
if (keyid) {
|
||||
let uids = yield this._mongo.list({ keyid }, DB_TYPE);
|
||||
for (let uid of uids) {
|
||||
let nonce = uuid.v4();
|
||||
yield this._mongo.update(uid, { nonce }, DB_TYPE);
|
||||
uid.nonce = nonce;
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* get user id which has been flagged for removal by proving knowledge of
|
||||
* the nonce.
|
||||
* @param {string} keyid public key id
|
||||
* @param {string} nonce The verification nonce proving email address ownership
|
||||
* @yield {Object} The matching user id document
|
||||
*/
|
||||
*getFlaggedForRemove(options) {
|
||||
let keyid = options.keyid, nonce = options.nonce;
|
||||
return yield this._mongo.get({ keyid, nonce }, DB_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all user ids for a public key.
|
||||
* @param {String} keyid The public key id
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*remove(options) {
|
||||
let keyid = options.keyid;
|
||||
yield this._mongo.remove({ keyid }, DB_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserId;
|
@ -3,11 +3,10 @@
|
||||
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
|
||||
|
||||
const config = require('config');
|
||||
const openpgp = require('openpgp');
|
||||
const nodemailer = require('nodemailer');
|
||||
const Email = require('../../src/email/email');
|
||||
const Mongo = require('../../src/dao/mongo');
|
||||
const UserId = require('../../src/service/user-id');
|
||||
const PGP = require('../../src/service/pgp');
|
||||
const PublicKey = require('../../src/service/public-key');
|
||||
const expect = require('chai').expect;
|
||||
const sinon = require('sinon');
|
||||
@ -15,23 +14,21 @@ const sinon = require('sinon');
|
||||
describe('Public Key Integration Tests', function() {
|
||||
this.timeout(20000);
|
||||
|
||||
let publicKey, email, mongo, userId,
|
||||
let publicKey, email, mongo, pgp,
|
||||
sendEmailStub, publicKeyArmored, emailParams;
|
||||
|
||||
const DB_TYPE_PUB_KEY = 'publickey';
|
||||
const DB_TYPE_USER_ID = 'userid';
|
||||
const primaryEmail = 'safewithme.testuser@gmail.com';
|
||||
const DB_TYPE = 'publickey';
|
||||
const primaryEmail = 'test1@example.com';
|
||||
const origin = { host:'localhost', protocol:'http' };
|
||||
|
||||
before(function *() {
|
||||
publicKeyArmored = require('fs').readFileSync(__dirname + '/../key1.asc', 'utf8');
|
||||
publicKeyArmored = require('fs').readFileSync(__dirname + '/../key3.asc', 'utf8');
|
||||
mongo = new Mongo();
|
||||
yield mongo.init(config.mongo);
|
||||
});
|
||||
|
||||
beforeEach(function *() {
|
||||
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||
yield mongo.clear(DB_TYPE_USER_ID);
|
||||
yield mongo.clear(DB_TYPE);
|
||||
emailParams = null;
|
||||
sendEmailStub = sinon.stub().returns(Promise.resolve({ response:'250' }));
|
||||
sendEmailStub.withArgs(sinon.match(recipient => {
|
||||
@ -49,8 +46,8 @@ describe('Public Key Integration Tests', function() {
|
||||
auth: { user:'user', pass:'pass' },
|
||||
sender: { name:'Foo Bar', email:'foo@bar.com' }
|
||||
});
|
||||
userId = new UserId(mongo);
|
||||
publicKey = new PublicKey(openpgp, mongo, email, userId);
|
||||
pgp = new PGP();
|
||||
publicKey = new PublicKey(pgp, mongo, email);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -58,8 +55,7 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
after(function *() {
|
||||
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||
yield mongo.clear(DB_TYPE_USER_ID);
|
||||
yield mongo.clear(DB_TYPE);
|
||||
yield mongo.disconnect();
|
||||
});
|
||||
|
||||
@ -83,7 +79,7 @@ describe('Public Key Integration Tests', function() {
|
||||
|
||||
it('should throw 304 if key already exists', function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
yield userId.verify(emailParams);
|
||||
yield publicKey.verify(emailParams);
|
||||
try {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
expect(false).to.be.true;
|
||||
@ -93,26 +89,151 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('verify', () => {
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
});
|
||||
|
||||
it('should update the document', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
|
||||
expect(gotten.userIds[0].verified).to.be.true;
|
||||
expect(gotten.userIds[0].nonce).to.be.null;
|
||||
expect(gotten.userIds[1].verified).to.be.false;
|
||||
expect(gotten.userIds[1].nonce).to.exist;
|
||||
});
|
||||
|
||||
it('should not find the document', function *() {
|
||||
try {
|
||||
yield publicKey.verify({ keyId:emailParams.keyId, nonce:'fake_nonce' });
|
||||
expect(true).to.be.false;
|
||||
} catch(e) {
|
||||
expect(e.status).to.equal(404);
|
||||
}
|
||||
let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
|
||||
expect(gotten.userIds[0].verified).to.be.false;
|
||||
expect(gotten.userIds[0].nonce).to.equal(emailParams.nonce);
|
||||
expect(gotten.userIds[1].verified).to.be.false;
|
||||
expect(gotten.userIds[1].nonce).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVerified', () => {
|
||||
let key;
|
||||
|
||||
describe('should find a verified key', () => {
|
||||
beforeEach(function *() {
|
||||
key = pgp.parseKey(publicKeyArmored);
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
yield publicKey.verify(emailParams);
|
||||
});
|
||||
|
||||
it('by fingerprint', function *() {
|
||||
let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by all userIds', function *() {
|
||||
let verified = yield publicKey.getVerified({ userIds:key.userIds });
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by verified userId', function *() {
|
||||
let verified = yield publicKey.getVerified({ userIds:[key.userIds[0]] });
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by unverified userId', function *() {
|
||||
let verified = yield publicKey.getVerified({ userIds:[key.userIds[1]] });
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by keyId', function *() {
|
||||
let verified = yield publicKey.getVerified({ keyId:key.keyId });
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by all params', function *() {
|
||||
let verified = yield publicKey.getVerified(key);
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not find an unverified key', () => {
|
||||
beforeEach(function *() {
|
||||
key = pgp.parseKey(publicKeyArmored);
|
||||
key.userIds[0].verified = false;
|
||||
yield mongo.create(key, DB_TYPE);
|
||||
});
|
||||
|
||||
it('by fingerprint', function *() {
|
||||
let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by userIds', function *() {
|
||||
let verified = yield publicKey.getVerified({ userIds:key.userIds });
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by keyId', function *() {
|
||||
let verified = yield publicKey.getVerified({ keyId:key.keyId });
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by all params', function *() {
|
||||
let verified = yield publicKey.getVerified(key);
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
});
|
||||
|
||||
it('should return verified key by key id', function *() {
|
||||
yield userId.verify(emailParams);
|
||||
let key = yield publicKey.get({ keyid:emailParams.keyid });
|
||||
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
|
||||
yield publicKey.verify(emailParams);
|
||||
let key = yield publicKey.get({ keyId:emailParams.keyId });
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by key id (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
let key = yield publicKey.get({ keyId:emailParams.keyId.toUpperCase() });
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by fingerprint', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint;
|
||||
let key = yield publicKey.get({ fingerprint });
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by fingerprint (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase();
|
||||
let key = yield publicKey.get({ fingerprint });
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by email address', function *() {
|
||||
yield userId.verify(emailParams);
|
||||
yield publicKey.verify(emailParams);
|
||||
let key = yield publicKey.get({ email:primaryEmail });
|
||||
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by email address (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
let key = yield publicKey.get({ email:primaryEmail.toUpperCase() });
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should throw 404 for unverified key', function *() {
|
||||
try {
|
||||
yield publicKey.get({ keyid:emailParams.keyid });
|
||||
yield publicKey.get({ keyId:emailParams.keyId });
|
||||
expect(false).to.be.true;
|
||||
} catch(e) {
|
||||
expect(e.status).to.equal(404);
|
||||
@ -121,23 +242,23 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe('requestRemove', () => {
|
||||
let keyid;
|
||||
let keyId;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
keyid = emailParams.keyid;
|
||||
keyId = emailParams.keyId;
|
||||
});
|
||||
|
||||
it('should work for verified key', function *() {
|
||||
yield userId.verify(emailParams);
|
||||
yield publicKey.verify(emailParams);
|
||||
emailParams = null;
|
||||
yield publicKey.requestRemove({ keyid, origin });
|
||||
yield publicKey.requestRemove({ keyId, origin });
|
||||
expect(emailParams.nonce).to.exist;
|
||||
});
|
||||
|
||||
it('should work for unverified key', function *() {
|
||||
emailParams = null;
|
||||
yield publicKey.requestRemove({ keyid, origin });
|
||||
yield publicKey.requestRemove({ keyId, origin });
|
||||
expect(emailParams.nonce).to.exist;
|
||||
});
|
||||
|
||||
@ -148,9 +269,9 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
it('should throw 404 for no key', function *() {
|
||||
yield publicKey.remove({ keyid });
|
||||
yield mongo.remove({ keyId }, DB_TYPE);
|
||||
try {
|
||||
yield publicKey.requestRemove({ keyid, origin });
|
||||
yield publicKey.requestRemove({ keyId, origin });
|
||||
expect(false).to.be.true;
|
||||
} catch(e) {
|
||||
expect(e.status).to.equal(404);
|
||||
@ -159,25 +280,23 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe('verifyRemove', () => {
|
||||
let keyid;
|
||||
let keyId;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
keyid = emailParams.keyid;
|
||||
keyId = emailParams.keyId;
|
||||
emailParams = null;
|
||||
yield publicKey.requestRemove({ keyid, origin });
|
||||
yield publicKey.requestRemove({ keyId, origin });
|
||||
});
|
||||
|
||||
it('should remove key', function *() {
|
||||
yield publicKey.verifyRemove(emailParams);
|
||||
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
|
||||
expect(uid).to.not.exist;
|
||||
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
|
||||
let key = yield mongo.get({ keyId }, DB_TYPE);
|
||||
expect(key).to.not.exist;
|
||||
});
|
||||
|
||||
it('should throw 404 for no key', function *() {
|
||||
yield publicKey.remove({ keyid });
|
||||
yield mongo.remove({ keyId }, DB_TYPE);
|
||||
try {
|
||||
yield publicKey.verifyRemove(emailParams);
|
||||
expect(false).to.be.true;
|
||||
@ -187,21 +306,4 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
let keyid;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||
keyid = emailParams.keyid;
|
||||
});
|
||||
|
||||
it('should remove key', function *() {
|
||||
yield publicKey.remove({ keyid });
|
||||
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
|
||||
expect(uid).to.not.exist;
|
||||
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
|
||||
expect(key).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,148 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
|
||||
|
||||
const config = require('config');
|
||||
const UserId = require('../../src/service/user-id');
|
||||
const Mongo = require('../../src/dao/mongo');
|
||||
const expect = require('chai').expect;
|
||||
|
||||
describe('User ID Integration Tests', function() {
|
||||
this.timeout(20000);
|
||||
|
||||
const DB_TYPE = 'userid';
|
||||
const keyid = '0123456789ABCDEF';
|
||||
let mongo, userId, uid1, uid2;
|
||||
|
||||
before(function *() {
|
||||
mongo = new Mongo();
|
||||
yield mongo.init(config.mongo);
|
||||
userId = new UserId(mongo);
|
||||
});
|
||||
|
||||
beforeEach(function *() {
|
||||
uid1 = {
|
||||
name: 'name1',
|
||||
email: 'email1'
|
||||
};
|
||||
uid2 = {
|
||||
name: 'name2',
|
||||
email: 'email2'
|
||||
};
|
||||
yield mongo.clear(DB_TYPE);
|
||||
});
|
||||
|
||||
after(function *() {
|
||||
yield mongo.clear(DB_TYPE);
|
||||
yield mongo.disconnect();
|
||||
});
|
||||
|
||||
describe("batch", () => {
|
||||
it('should persist all the things', function *() {
|
||||
let uids = yield userId.batch({ userIds:[uid1, uid2], keyid });
|
||||
expect(uids[0].keyid).to.equal(keyid);
|
||||
expect(uids[1].keyid).to.equal(keyid);
|
||||
expect(uids[0].nonce).to.exist;
|
||||
expect(uids[1].nonce).to.exist;
|
||||
expect(uids[0]._id).to.exist;
|
||||
expect(uids[1]._id).to.exist;
|
||||
let gotten = yield mongo.list({ keyid }, DB_TYPE);
|
||||
expect(gotten).to.deep.equal(uids);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify", () => {
|
||||
it('should update the document', function *() {
|
||||
let uids = yield userId.batch({ userIds:[uid1], keyid });
|
||||
yield userId.verify({ keyid, nonce:uids[0].nonce });
|
||||
let gotten = yield mongo.get({ _id:uid1._id }, DB_TYPE);
|
||||
expect(gotten.verified).to.be.true;
|
||||
expect(gotten.nonce).to.be.null;
|
||||
});
|
||||
|
||||
it('should not find the document', function *() {
|
||||
yield userId.batch({ userIds:[uid1], keyid });
|
||||
try {
|
||||
yield userId.verify({ keyid, nonce:'fake_nonce' });
|
||||
} catch(e) {
|
||||
expect(e.status).to.equal(404);
|
||||
}
|
||||
let gotten = yield mongo.get({ _id:uid1._id }, DB_TYPE);
|
||||
expect(gotten.verified).to.be.undefined;
|
||||
expect(gotten.nonce).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVerfied", () => {
|
||||
beforeEach(function *() {
|
||||
let uids = yield userId.batch({ userIds:[uid1], keyid });
|
||||
yield userId.verify({ keyid, nonce:uids[0].nonce });
|
||||
});
|
||||
|
||||
it('should find verified by key id', function *() {
|
||||
let gotten = yield userId.getVerfied({ keyid });
|
||||
expect(gotten).to.exist;
|
||||
});
|
||||
|
||||
it('should find verified by email address', function *() {
|
||||
let gotten = yield userId.getVerfied({ userIds:[uid2,uid1] });
|
||||
expect(gotten).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe("flagForRemove", () => {
|
||||
let stored;
|
||||
beforeEach(function *() {
|
||||
stored = yield userId.batch({ userIds:[uid1, uid2], keyid });
|
||||
});
|
||||
|
||||
it('should flag one documents for email param', function *() {
|
||||
let flagged = yield userId.flagForRemove({ email:uid1.email });
|
||||
expect(flagged.length).to.equal(1);
|
||||
expect(flagged[0]._id.toHexString()).to.equal(stored[0]._id.toHexString());
|
||||
expect(flagged[0].nonce).to.not.equal(stored[0].nonce);
|
||||
let gotten = yield mongo.list({ email:uid1.email }, DB_TYPE);
|
||||
expect(gotten).to.deep.equal(flagged);
|
||||
});
|
||||
|
||||
it('should flag all documents for key id param', function *() {
|
||||
let flagged = yield userId.flagForRemove({ keyid });
|
||||
expect(flagged.length).to.equal(2);
|
||||
expect(flagged[0]._id.toHexString()).to.equal(stored[0]._id.toHexString());
|
||||
expect(flagged[0].nonce).to.not.equal(stored[0].nonce);
|
||||
let gotten = yield mongo.list({ keyid }, DB_TYPE);
|
||||
expect(gotten).to.deep.equal(flagged);
|
||||
});
|
||||
|
||||
it('should flag no documents for wrong key id param', function *() {
|
||||
let flagged = yield userId.flagForRemove({ keyid:'4' });
|
||||
expect(flagged.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should flag no documents no param', function *() {
|
||||
let flagged = yield userId.flagForRemove({});
|
||||
expect(flagged.length).to.equal(0);
|
||||
let gotten = yield mongo.list({ keyid }, DB_TYPE);
|
||||
expect(gotten).to.deep.equal(stored);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFlaggedForRemove", () => {
|
||||
it('should find flagged document', function *() {
|
||||
yield userId.batch({ userIds:[uid1, uid2], keyid });
|
||||
let flagged = yield userId.flagForRemove({ keyid });
|
||||
let gotten = yield userId.getFlaggedForRemove({ keyid, nonce:flagged[0].nonce });
|
||||
expect(gotten).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it('should delete all documents', function *() {
|
||||
yield userId.batch({ userIds:[uid1, uid2], keyid });
|
||||
yield userId.remove({ keyid });
|
||||
let gotten = yield mongo.get({ keyid }, DB_TYPE);
|
||||
expect(gotten).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
134
test/key2.asc
Normal file
134
test/key2.asc
Normal file
@ -0,0 +1,134 @@
|
||||
pub rsa4096/C9DEDC77 2015-10-17 [expires: 2018-10-16]
|
||||
uid Google Security Team <security@google.com>
|
||||
sub nistp384/70C16E3C 2015-10-17 [expires: 2018-10-16]
|
||||
sub rsa4096/50CB43FB 2015-10-17 [expires: 2018-10-16]
|
||||
sub nistp384/102D9086 2015-10-17 [expires: 2018-10-16]
|
||||
sub rsa4096/DFC40367 2015-10-17 [expires: 2018-10-16]
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v2
|
||||
|
||||
mQINBFYiIB8BEACxs55+7GG6ONQV3UFYf36UDSVFbuvNB5V1NaEnkY0t+RVMigLR
|
||||
Zdl0HHsiaTKfKs4jqjLQAoR6Fcre9jlEhatotRg3AvHV1XYebxRlzdfXxyD0d6i9
|
||||
Quc1zbca0T8F1C5c7xfYP5g9iKWn5yFtHC3S7mLeOg7Ltx84bTo8AF7bHGA3uIQf
|
||||
uCtE8l6Z57HTeaf2IR/893jLOir8lvmTef83m/+e1j6ZwmKxxZO2s+aGKre6Fqsz
|
||||
Oo89CpWKNrdZ3IN8+Y4udZNlr7u0os7ffY0shfbLrqt+eVEu4EHfbpQTJxvalZJK
|
||||
tEnGtV8S7Z3dcPcimxvO7HZu7Wz8VnRzY/AZtee4fC+i2yBu1rWKgY3V1tFKdxVr
|
||||
KDnmS5MBgBAxv69mM3bf8QPinL4mtIQ65/Dt4ksJuysRmGwQ8LkjSLQCMMepnjBs
|
||||
/63wJ3e4wN1LCwnJonA2f8gZQHNeGPUhVVd/dWFDtmQaLwKFcI0GS/DiUPBIJir5
|
||||
DWnrEedtlcSLlwwcUglFsG4Sds/tLr+z5yE88ZrDrIlX9fb9cCAsDq7c8/NCzgvw
|
||||
kFez14sXgGhMz6ZfFzM49o0XwlvAeuSJRWBvnKonxM7/laqv4gK0zur3a6+D6qCN
|
||||
vt9iWO/YG+0Fvhmyxe34/Q71nXWc9t5aLcokmYLGY1Dpzf9oB8hDRdMCAQARAQAB
|
||||
tCpHb29nbGUgU2VjdXJpdHkgVGVhbSA8c2VjdXJpdHlAZ29vZ2xlLmNvbT6JAjwE
|
||||
EwEIACYFAlYiIB8CGwEFCQWjmoAFCwkIBwIGFQgJCgsCAxYCAQIeAQIXgAAKCRC4
|
||||
5BBcyd7cd8MzD/9YdMVZniQH4qBKxLFIoYGfLzCEI0S9IVUA37wrZ4YiRODSJRMf
|
||||
El6oVfTO/g8xpeQlDgHj1w2IDoSkeQrY+7rf9H41sGGOBDGXSQT+7Z7XFH2mPPvC
|
||||
cqYqR32BDNDkO/LL1BzzRlQvNmnGHxo098sqTgb7hoVsP+qFoem7JUMpcAV1KrUo
|
||||
P81haV8a/25ouWFZu5P68WFh861TyIjIYLQCns2fG+zlKFGN9Uynv6E5+Qk7dmni
|
||||
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
|
||||
=Q/kB
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
232
test/key3.asc
Normal file
232
test/key3.asc
Normal file
@ -0,0 +1,232 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=sqvb
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
Qeh1ZTzVvm3VhScYKhDPg8x2OpDvKAoo
|
||||
=qFMO
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
112
test/unit/pgp-test.js
Normal file
112
test/unit/pgp-test.js
Normal file
@ -0,0 +1,112 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const expect = require('chai').expect;
|
||||
const openpgp = require('openpgp');
|
||||
const PGP = require('../../src/service/pgp');
|
||||
|
||||
|
||||
describe('PGP Unit Tests', () => {
|
||||
let pgp, key1Armored, key2Armored;
|
||||
|
||||
beforeEach(() => {
|
||||
key1Armored = fs.readFileSync(__dirname + '/../key1.asc', 'utf8');
|
||||
key2Armored = fs.readFileSync(__dirname + '/../key2.asc', 'utf8');
|
||||
pgp = new PGP(openpgp);
|
||||
});
|
||||
|
||||
describe('parseKey', () => {
|
||||
it('should be able to parse RSA key', () => {
|
||||
let params = pgp.parseKey(key1Armored);
|
||||
expect(params.keyId).to.equal('dbc0b3d92b1b86e9');
|
||||
expect(params.fingerprint).to.equal('4277257930867231ce393fb8dbc0b3d92b1b86e9');
|
||||
expect(params.userIds.length).to.equal(1);
|
||||
expect(params.created.getTime()).to.exist;
|
||||
expect(params.algorithm).to.equal('rsa_encrypt_sign');
|
||||
expect(params.keySize).to.equal(2048);
|
||||
expect(params.publicKeyArmored).to.equal(key1Armored);
|
||||
});
|
||||
|
||||
it('should be able to parse RSA/ECC key', () => {
|
||||
let params = pgp.parseKey(key2Armored);
|
||||
expect(params.keyId).to.equal('b8e4105cc9dedc77');
|
||||
expect(params.fingerprint).to.equal('e3317db04d3958fd5f662c37b8e4105cc9dedc77');
|
||||
expect(params.userIds.length).to.equal(1);
|
||||
expect(params.created.getTime()).to.exist;
|
||||
expect(params.algorithm).to.equal('rsa_encrypt_sign');
|
||||
expect(params.keySize).to.equal(4096);
|
||||
expect(params.publicKeyArmored).to.equal(pgp.trimKey(key2Armored));
|
||||
});
|
||||
});
|
||||
|
||||
describe('trimKey', () => {
|
||||
it('should be the same as key1', () => {
|
||||
let trimmed = pgp.trimKey(key1Armored);
|
||||
expect(trimmed).to.equal(key1Armored);
|
||||
});
|
||||
|
||||
it('should not be the same as key2', () => {
|
||||
let trimmed = pgp.trimKey(key2Armored);
|
||||
expect(trimmed).to.not.equal(key2Armored);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateKeyBlock', () => {
|
||||
const KEY_BEGIN = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
const KEY_END = '-----END PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
it('should return true for valid key block', () => {
|
||||
let input = KEY_BEGIN + KEY_END;
|
||||
expect(pgp.validateKeyBlock(input)).to.be.true;
|
||||
});
|
||||
|
||||
it('should return false for invalid key block', () => {
|
||||
let input = KEY_END + KEY_BEGIN;
|
||||
expect(pgp.validateKeyBlock(input)).to.be.false;
|
||||
});
|
||||
|
||||
it('should return false for invalid key block', () => {
|
||||
let input = KEY_END;
|
||||
expect(pgp.validateKeyBlock(input)).to.be.false;
|
||||
});
|
||||
|
||||
it('should return false for invalid key block', () => {
|
||||
let input = KEY_BEGIN;
|
||||
expect(pgp.validateKeyBlock(input)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseUserIds', () => {
|
||||
it('should parse a valid user id', () => {
|
||||
let input = ['a <b@c.de>'];
|
||||
let parsed = pgp.parseUserIds(input);
|
||||
expect(parsed[0].name).to.equal('a');
|
||||
expect(parsed[0].email).to.equal('b@c.de');
|
||||
});
|
||||
|
||||
it('should parse a valid user id', () => {
|
||||
let input = [' <b@c.de>'];
|
||||
let parsed = pgp.parseUserIds(input);
|
||||
expect(parsed[0].name).to.equal('');
|
||||
expect(parsed[0].email).to.equal('b@c.de');
|
||||
});
|
||||
|
||||
it('should parse a valid user id', () => {
|
||||
let input = ['<b@c.de>'];
|
||||
let parsed = pgp.parseUserIds(input);
|
||||
expect(parsed[0].name).to.equal('');
|
||||
expect(parsed[0].email).to.equal('b@c.de');
|
||||
});
|
||||
|
||||
it('should throw for a invalid user id', () => {
|
||||
let input = ['a <@c.de>'];
|
||||
expect(pgp.parseUserIds.bind(pgp, input)).to.throw(/invalid user id/);
|
||||
});
|
||||
|
||||
it('should throw for no user ids', () => {
|
||||
let input = [];
|
||||
expect(pgp.parseUserIds.bind(pgp, input)).to.throw(/no user id found/);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user