diff --git a/src/service/pgp.js b/src/service/pgp.js new file mode 100644 index 0000000..1066006 --- /dev/null +++ b/src/service/pgp.js @@ -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 . + */ + +'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; \ No newline at end of file diff --git a/src/service/public-key.js b/src/service/public-key.js index 58ba345..b7f5adb 100644 --- a/src/service/public-key.js +++ b/src/service/public-key.js @@ -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); } } diff --git a/src/service/user-id.js b/src/service/user-id.js deleted file mode 100644 index 131499b..0000000 --- a/src/service/user-id.js +++ /dev/null @@ -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 . - */ - -'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; \ No newline at end of file diff --git a/test/integration/public-key-test.js b/test/integration/public-key-test.js index d747d7b..145cf89 100644 --- a/test/integration/public-key-test.js +++ b/test/integration/public-key-test.js @@ -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; - }); - }); - }); \ No newline at end of file diff --git a/test/integration/user-id-test.js b/test/integration/user-id-test.js deleted file mode 100644 index 85796cb..0000000 --- a/test/integration/user-id-test.js +++ /dev/null @@ -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; - }); - }); - -}); \ No newline at end of file diff --git a/test/key2.asc b/test/key2.asc new file mode 100644 index 0000000..e84d248 --- /dev/null +++ b/test/key2.asc @@ -0,0 +1,134 @@ +pub rsa4096/C9DEDC77 2015-10-17 [expires: 2018-10-16] +uid Google Security Team +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 +XnHRaiYZP9+wux6zm5a5wD/h6Iv4hyg/0Vnx5SyH8QOm3Qm6pkUciQkSmZQvf0r7 +HTLk19V1WtAp64YyUgnp9P/dq1bkclZcmWgZwVf88P8Cjm1BLh9RMdy6F+lVuUbz +0JtOyxFtxfZ7ooNzYf8cZbq3IkJtFW22BcHm7jK7fpkwqVvTeK7TS1nvbUjMW4Qw +bcFUJnA5TPkJanoNH9DCya7/PhbAI9hwyOcCsCOIfbIpj06izxxUXu0MJb+9k5US +n7wRLwVsrt21V/PZoqvKMehqZTsVCsWZOzwf7UUY+WGZqT3uORopg9vadj1nSmLA ++HprKhS9m3PA0vWbNvp0NQUWoanUjtpnCBuLk05H2GNgnRMnL0pEIkF2sTaCRjnY +zLSo9QuzrvTgZ4McfcZ28MDuRR4JfS+LZ8AhopdjtR7VTG9IAxfq5JORpokCHAQQ +AQgABgUCViIlJAAKCRDHiaFvb01lGfBgEACw5hlr7fWwSvYf1/Dfs1w5WyKc8cJs +2370rVOzauVnRsFXTcl1D4iYnC2Uu2CwTcbD5pFKikpJnhDxzd6Ub5XapJrA06lu +uGGExhCV3QKJVOrKJyZ+eWh5wu4UbDxSCvLQI/FLV6uLrbauAQpoFBBw2A8epRbY +hqDdJ+EWgt57KfzsAc12jQ2HYGDIrdV35g3D4QANDLl69XLlSuyAHDMKRTs0rXje +H6ds+/s9khKcCwkzOCAJSZHg83rRpLMkN0Izr3ZQB932Ybr7ZvdbkjHS6YhYfXzm +1PIyFq9TikArz8YFcLQEgE6mph+jfEXMEzbg8G0+Wvrl0C0XHJWiCvl7feAxftGV +w0HPWvNTemD7BCtTVEkIh5IOeB+rzdnFaW84PSYmwoPW6a4aOhQ5Y8QyshCA2fnP +eyQACNpvj4nCJNdvyJAm2+5U/TnCEyl7zizm++sJTxAilqXxH5ubppaldmcRYLWZ +pHN+Aup+yiotDRO4s9QunDC6vTGf4Zbe4xN+rL9vlaIH4dU700xFCNY5yCPqIst+ +pLwZo6FduJLsjE71z8UINxr4q0jXDaMyMm70xcDRDhvTPZTP/i3rFrM95x4Q/das +ebNidE0mel0vHJ/5411OrRTCQ5fgv1i7ukZbVATWMOkYTpiYKv+sWPZg3uNxlqHo +BmIunwzFda9LD7hvBFYiIcMTBSuBBAAiAwMEAeDSwQIRp955OGPU5A242FIJp91t +t1+YAVblSkJ+APKCdgEXeIcDheRcozUt5pOvGdibnaPotPCxdUc9QWYV8CFadyZg +QOM57kCSnhTutzPccOLnSJVNy9sUbV91lMzBiQKlBBgBCAAPBQJWIiHDAhsCBQkF +o5qAAIoJELjkEFzJ3tx3fyAEGRMJAAYFAlYiIcMACgkQaEJ4Y3DBbjzLUwF+IF0t +U0CuCwddi9EYW3d66Q9dJv2H7V6oPNJ98mukzGUb7bBZhGdtFn1IGr3nSPgbAX4p +AHfWy+JFh0zlM7HFJPECPtBi1UvuNFxvIZj/FeV/jdqaE2KLwO/9Gv3rPMQ2TurH +WhAAo/ubNGuGZ+r/NI/Z/l9vLKfPVIiR3xtrehyV5GmMGXECoT9hME0jhg5RlSzK +qxZkPgVmQclD3smbudp79rtK6T18DjlA84aXut+5ZhKiVPcyUK80UqNw7/3t/NsM +xXv8z73O8glx3jXGv1zIYW8PHdeJOr7nX89dsM0ibgf7Ti3fdhygMA3nu/sbmrHL +nQ3cix72qGQkMURjBRcSSJu2hMZjDNSPgOPOEABefxIyWG4kQwRRUXPePeJOVa6d +QBJPh755bsbl3kQ0tG3NL9nDNq42M8QGDWnMpP9F8nmFSCw+RTUT5SminWsGhovW +rG25/gkWrRZhMAAm0Bf3y+yMDWdsrnUCOQsgihQcH8i+V1AMfZgjJKPg1vtFdDCh +uGtH3vJSEEhPZjTBBzIQx3etKoVDP8WtNZN5jeh84FYHsivLxSUiPQ//Jk3cnBLx +/0f5Wrimwk7eUi4ueNUyFSWv+soi/FpcnDSvbVMVY2sIXI8aFFDv8U6+EPMyijAf +tWRR4yA8tx0APRh/5z5T9sKj/n+jBZkQXBSKDnI7U4fmTBgh/sPeH61/zOuJBt6G +9tfOmomf9TiTVQdD8T3HpEfJV5rrOFj8fic8OKSWp29jnoP57bIEprSgVTcrlK5b +yr5qDMKEh2P7pgWfLWQsSG4a0iwJUsq5NGOsluzeH4aqDs25Ag0EViIh5QEQALcO +QFtQojykqZmX/oKgAcRhiNM9NZbz3FGED69jesy3VOZxBeiCHO3vkHW9h6s88VuM +qiC1JfZcH/Kkw+XAC+GtYxRMxZhDQ8pIh4PAFnaWRp5kAmmxS+k6O4tEQogOgh0k +29P4+w63cgjw8mvb8acKOyMOCXLgnVNak614ogAFnrCakfA4WQOPGoqrey7z0XKJ +LTbt28W2RALbSoC6KE7KTsx63Jng4Yr5q+elVOqzaSFPeloiC2R05CF6pCsVKX7D +P0HFjcCk7/W8czeKOQWM62edgL4Y3c/x/g/PutAkLOrX/Wt1MejKeXT9QaNAA6QW +qASkzK6L1FGrCzaf6cVZrhBdGdIatqYxpfY3I6tTtlN/5BGieFYXmZsP7t/p7TMv +Jv2oJYtL1qsapQcnE9WOiARRb34hcnfA3UOet9W8vJqCGUYKZbJPyk5eLGuFVuDX +6tnqUgoTkWRhsYNFqop2GnfZIl4a8doZ05oQQlKeRBw8pgnRCRq1fq28Yc4FqiXn +Lfdts5016hc8U0KimMzvRBlSKTLEHC6febqq3XHDR7nHHrXxY29BVFD8r3izkT71 +Xb3Ql8NGvuWcnTS9j5L1EXkFv0wzFSUS5FUNU3JoNO5JsPl+YVczU6RX/QoDzpsx +mJ7ctY0yeSEY2YXvuS6gQXDALx5D9zyCMTj8TrvTABEBAAGJBEQEGAEIAA8FAlYi +IeUCGwIFCQWjmoACKQkQuOQQXMne3HfBXSAEGQEIAAYFAlYiIeUACgkQD8lB2VDL +Q/tq9g/+N+kTlYxpQCvgvjJEM+VLVqUIv7wBqrZXawcrti8DBtVCcuvHYGjVmPqB +OGyp6TNQTX5RQfo64TTh78BnG9Tf08oGv5nzXHxRdk92XZzzS2tq24j1OGiZhhYp +JcFjzBx3qRhYmvN2ZkuCL48tthjKBx/SjfcGV185meNIZWzg67hmo7Szlbpo4lN6 +aLOxVAZelZjH3bFwpMp198ZEuE0B9RzhuJmhrtpl6dLtcQ8rsgy0EdwYons61GU2 +gnpn39kpCRSnmbMYqRfTyHo/pVLxz7XR98MrvB6am9wVE42PQV+viyHLB2pRquGZ +CSCfMrzE38MMJ3BJAcwx6YcAItaBQXaWYEyE/ixr4OvEA+jC4n0Nq8Pik/oUc+7I +2LWAZ50VrE+HroNVomFMMUvp+RZ0S/+J4DuuiwAxnN4oacYQVKqDt7D0V+8da+ee +87ghOrL5xTjG1yEgd3Q9VDbh8gWGtVWevdnAldZzDvYsVsJW4N8YunVOLZZ0+24R +X9LUsJ6Fry7oP4kvOFGFegVC123x7HDrR9333Eq4H59xHXyDQo0O7NvCph8RfSdj +/ouYP/D1/gkS45ladT89qePrwXT6j8DTqkMmrUbXVXtc9tBWXgNB0nacX68TywP9 +LigrBsDiPdwYszKKuZWCEhex5BQo4Pfw8OBHqkENQdMmUgW1zcE4aQ/+Ioq5lvlH +OpZmPGC3xegT0kVC0kVeK12x3dTCc6ydkWanXrCJrCXNnboV34naszTl+Qt75TyB +XqFJamwxjA5K/COmAZTAcW4svGRhqhAMg02tfkrL5a84lImOVmpGbvUAQWBXNKXV +aeOmKVEvO6e/JBVKDQL5h+ePJ1csq8I5P5zelgXWgVkFvlq0H1MrF3eU780A1hLB +Q4O8eJ+zoCLYaR6lBvZTsfVtsdIuIodiJudYB9GUDMcalB7wj/CUN06R/UcDK4HG +qGb/ynS/cK5giZE6v2BNA7PYUNcdr6hO51l3g7CwswZTnx79xyPhWsnOw9MUymyv +/Nm73QX/k635cooVPAaJOPSiEsboqDCuiAfuEamdrT00fUfqCkepI3m0JAJFtoqm +o9agQBSywkZ0Tjuf9NfB7jBWxIyt1gc9vmiCSlnbdDyK/Ze17PhDdkj2kT8p47bN +l2IBk48xkrDq7HfMNOXC50jyiELs+8+NIfwICBJRyMpCQWAs9d+XBnzRzLXmEA/1 +ScdNX0guOOSrTsfIgctO0EWnAYo8PfF9XebZMhTsOhHmq4AAqWFBYxAQa6lGBBcU +fZ0dHylTnuiR5phXMyWYWplZsHOVaHnhoGz1KJkpqYEH7fp38ERdcRiz7nwoyfYz +Jl5qaAebTt8kYtJm3Jn8aJCAjPwtArRzkHO4cwRWIiISEgUrgQQAIgMDBNbEs2RY +eWTLtXrcTUYDhMVzZwsTVJPvgQqtS+UnmPA7+qLEjSInHFfUE0yQEYsCTzP3g9mr +UOte0x/i+u7bmvxYo58SZ51bEd4/IbKecgSJbwLkhHR2HeHh3MsuW8lVtAMBCQmJ +AiUEGAEIAA8FAlYiIhICGwwFCQWjmoAACgkQuOQQXMne3HfJkA/9FIOskOWRjm4e +UuocsD1Rwglk3nWUAJ5krHcKI6/LrKP0OdOnrXrd65FYwpYvhf6/6OCg+NXvQ7T/ +rFs+Cfi+Llko5gDWVEcyPOreN/E9R7rVPxYeqWryELFFXL4eWGA6mXRW3Ab3L6pb +6MwRUWsSfXjaW1uyRPqbJm0ygpVYpVNF9VmI5DvMEHjfNSxHkD3xDWZuUHJ+zswK +uAeRtEgYkzARZtYGBiMuCjULD29cYHaaySxY94Be/WvZI6HnCoXSgQ8LCpTGkiSL +9cLtYIDxq8DmzJhiQkQItxzJRPUTMDZUR+SSNAqxL0K5ohuNzZW8hDfkdudZ4Pr6 +u+sMVHCIG5sL6IHF35dsoUceCML/rTrM/3JYPADuleTmKfv2Dt78FL4s2CNxcBfI +SHjYCquIb5xyc8m222ya8eF2CoSoC1XhChoGjcIbKvHxcK/PgGgrFLI1NaJRN8vR +qCiW1bPNg8cAyLAb5pdtutlsxrhvRlRc65qNBEJ711Gymd54DOK6vW6DRFQPZLxW +MoElc/Mio4X3FA+40kKXXUcBA3Y2qi1vhCottZIXd+37HZZc0WwoLxv+qvwB19IE +SRuRhJyHnuYXHX7Y+GwDz7/7bzxRrEEhcQfzcWp4qhoFc8uCScj98kMeEiW3AQmU +ayyFDmvqEREd2cSpUbrIJVLT2aEOfKe5Ag0EViIiPwEQAMminwtRlkfMZCotqAo2 +GOmJb6gSbJ9GPFaWDBZVMXR8tHmbFlXwsVmuSkV0BS7hnE3N0dbvv5hAv9uNjnqA +vxjP1aSfPNWVOVYSLl6ywUBDasGiiyxf503ggI7nIv4tBpmmh0MITwjyvdHSl0nt +fC7GrdFxTX9Ww655oep3152a33eaos1i3CZqB9+zuyqfe4NWbyaYBoCfESXtmEY4 +AbMFy/xYB6liRJsxCeOo4u+is4jrICwGyMZCOsgswciMIh3x3/K1aa/v4DS/T96V +8BTqYeSS9nIGTkz2jLIRXK43wX07DpsoeQvUvWjmfaqUvQluixvwdE/IJ6O92PiC ++0U1CYP5KM0+fpdh2BhaxHJrs2b4NEsYHuheeZ485HrCX8ZamUMzj2+bC0q/OYHP +UtABk96gjXPmTfme16knDFlRJFPZytQ36p9lGYTCUIMwyxjMfi9E+HnhoJfsqlbk +kDseDEB2nU9SJb8NRPmMURVo+yayqcyFUJ4ZimJJ1MpGvlHj6mdxzIdJjzoT541H +WKz+SvVSjCRVFNCjvmQk31/BiPmCf62+KYOpv1tkOankrYc1yX6kt92+JmG6vIQT +u1Lqbp46jkydyG4BAkv9l8EfUMyPaLglTTMotc62rwtPEWnPoFAcV6ZjTxwMx029 +hzFIp5tjvoxz7AkuGbi3yoXhABEBAAGJAiUEGAEIAA8FAlYiIj8CGwwFCQWjmoAA +CgkQuOQQXMne3HdgVQ/9GjK+aYHgcuGFw1bX8EfSZjmEvdnuePT0Fv9Padqs236P +COmQcU/1EtXhrgO8NzuPb83IasJWyvo4xagCnCiAJ+uk4P4rK6Sbb3VZ+Hm1SyOF +SF3P7JuaSC03k0aD03s2JxSbZoqupoKkEfLlat0J9HoqquNdjUZ2+4aETcZcsXt1 +WVGkzbgwqJbLiuaRKLOvJjMICQA5zhljy7WcIOzIkWyhxhpzZ+a9kdXXWJLI0nkB +jT/5UYT3DNODssrNEfayzxZbvf3Dtl/OIrmKQpgWtVOaiLcxI9FzUN6pGxAlBdP2 +rmj0MPQIpa17T76d5P/VZrR/SYeEsPaPjGBZFOAW1yTef0mXKQ0mc0nwTGHNYjrs +tkBUh/F9ErKN/++UN7pDc5ORVCbg5Z1gd3UIL16lsYnNyq1O0cdWgW+xCUMLVKb5 +Q9f59ld3/yNF5XPyPNH9Ybb5kQJjYsDaIa+NPg9YLZ8DdONgqZyWgKiW5klMSk5Q +1+pxcXjT13eX5L0Ru/w3UjsSaCQOA/OuNep7Nwg29tWogTOSkhwC92Zpjd5PfoJi +j3EuhPUeTupRYM58jib/b9/1mQ1+wVyDEpIxTDjU0x1u4E59HcAu0naLNGd9bJMw +EeiVzNNyKUihENSQh9nsPniQvXgF3pPGQ8ZpS+9R9NyYQID5t3W8UrLpguvAE2U= +=Q/kB +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/key3.asc b/test/key3.asc new file mode 100644 index 0000000..c4f83d4 --- /dev/null +++ b/test/key3.asc @@ -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----- diff --git a/test/unit/pgp-test.js b/test/unit/pgp-test.js new file mode 100644 index 0000000..d3bd15b --- /dev/null +++ b/test/unit/pgp-test.js @@ -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 ']; + 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 = [' ']; + 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 = ['']; + 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/); + }); + }); + +}); \ No newline at end of file