From 602bbb707dbeabcdcfa3396f69fb5fbd76f62e73 Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Tue, 6 Feb 2018 21:25:49 -0800 Subject: [PATCH] rename decryptSessionKey to decryptSessionKeys, return only unique session keys --- src/config/config.js | 2 +- src/index.js | 2 +- src/message.js | 29 +++++++++++++++++++++-------- src/openpgp.js | 8 ++++---- test/general/openpgp.js | 28 +++++++++++++++++++++------- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index d0ef9424..9ac93762 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -47,7 +47,7 @@ export default { zero_copy: false, // use transferable objects between the Web Worker and main thread debug: false, tolerant: true, // ignore unsupported/unrecognizable packets instead of throwing an error, - password_collision_check: false, // work-around for rare GPG decryption bug with encrypting with multiple passwords + password_collision_check: false, // work-around for rare GPG decryption bug when encrypting with multiple passwords. Slower and slightly less secure show_version: true, show_comment: true, versionstring: "OpenPGP.js VERSION", diff --git a/src/index.js b/src/index.js index 30987f0b..f87f3db6 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,7 @@ export default openpgp; export { encrypt, decrypt, sign, verify, generateKey, reformatKey, decryptKey, - encryptSessionKey, decryptSessionKey, + encryptSessionKey, decryptSessionKeys, initWorker, getWorker, destroyWorker } from './openpgp'; diff --git a/src/message.js b/src/message.js index a92bd372..fd3e6fbe 100644 --- a/src/message.js +++ b/src/message.js @@ -97,7 +97,7 @@ Message.prototype.getSigningKeyIds = function() { * @return {Message} new message with decrypted content */ Message.prototype.decrypt = async function(privateKey, sessionKey, password) { - let keyObjs = sessionKey || await this.decryptSessionKey(privateKey, password); + let keyObjs = sessionKey || await this.decryptSessionKeys(privateKey, password); if (!util.isArray(keyObjs)) { keyObjs = [keyObjs]; } @@ -146,7 +146,7 @@ Message.prototype.decrypt = async function(privateKey, sessionKey, password) { * @return {Array} array of object with potential sessionKey, algorithm pairs in the form: * { data:Uint8Array, algorithm:String } */ -Message.prototype.decryptSessionKey = function(privateKey, password) { +Message.prototype.decryptSessionKeys = function(privateKey, password) { var keyPackets = []; return Promise.resolve().then(async () => { if (password) { @@ -154,12 +154,12 @@ Message.prototype.decryptSessionKey = function(privateKey, password) { if (!symESKeyPacketlist) { throw new Error('No symmetrically encrypted session key packet found.'); } - await symESKeyPacketlist.map(async function(packet) { + await Promise.all(symESKeyPacketlist.map(async function(packet) { try { await packet.decrypt(password); keyPackets.push(packet); } catch (err) {} - }); + })); } else if (privateKey) { var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); @@ -170,21 +170,34 @@ Message.prototype.decryptSessionKey = function(privateKey, password) { if (!privateKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - // TODO replace when Promise.some or Promise.any are implemented - // eslint-disable-next-line no-await-in-loop - await pkESKeyPacketlist.some(async function(packet) { + await Promise.all(pkESKeyPacketlist.map(async function(packet) { if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) { try { await packet.decrypt(privateKeyPacket); keyPackets.push(packet); } catch (err) {} } - }); + })); } else { throw new Error('No key or password specified.'); } }).then(() => { + if (keyPackets.length) { + + // Return only unique session keys + if (keyPackets.length > 1) { + var seen = {}; + keyPackets = keyPackets.filter(function(item) { + var k = item.sessionKeyAlgorithm + util.Uint8Array2str(item.sessionKey); + if (seen.hasOwnProperty(k)) { + return false; + } + seen[k] = true; + return true; + }); + } + return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); } else { throw new Error('Session key decryption failed.'); diff --git a/src/openpgp.js b/src/openpgp.js index a7d56022..d5a4bbc1 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -414,18 +414,18 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) { * or 'undefined' if no key packets found * @static */ -export function decryptSessionKey({ message, privateKey, password }) { +export function decryptSessionKeys({ message, privateKey, password }) { checkMessage(message); if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('decryptSessionKey', { message, privateKey, password }); + return asyncProxy.delegate('decryptSessionKeys', { message, privateKey, password }); } return Promise.resolve().then(async function() { - return message.decryptSessionKey(privateKey, password); + return message.decryptSessionKeys(privateKey, password); - }).catch(onError.bind(null, 'Error decrypting session key')); + }).catch(onError.bind(null, 'Error decrypting session keys')); } diff --git a/test/general/openpgp.js b/test/general/openpgp.js index cebaf23f..e7fcf2fa 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -525,7 +525,7 @@ describe('OpenPGP.js public api tests', function() { }); }); - describe('encryptSessionKey, decryptSessionKey', function() { + describe('encryptSessionKey, decryptSessionKeys', function() { var sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]); beforeEach(function(done) { @@ -539,7 +539,7 @@ describe('OpenPGP.js public api tests', function() { algorithm: 'aes128', publicKeys: publicKey.keys }).then(function(encrypted) { - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: encrypted.message, privateKey: privateKey.keys[0] }); @@ -554,7 +554,7 @@ describe('OpenPGP.js public api tests', function() { algorithm: 'aes128', passwords: password1 }).then(function(encrypted) { - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: encrypted.message, password: password1 }); @@ -563,14 +563,14 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with pgp key pair', function() { + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair', function() { var msgAsciiArmored; return openpgp.encrypt({ data: plaintext, publicKeys: publicKey.keys }).then(function(encrypted) { msgAsciiArmored = encrypted.data; - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: openpgp.message.readArmored(msgAsciiArmored), privateKey: privateKey.keys[0] }); @@ -586,14 +586,14 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with password', function() { + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with password', function() { var msgAsciiArmored; return openpgp.encrypt({ data: plaintext, passwords: password1 }).then(function(encrypted) { msgAsciiArmored = encrypted.data; - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: openpgp.message.readArmored(msgAsciiArmored), password: password1 }); @@ -608,6 +608,20 @@ describe('OpenPGP.js public api tests', function() { expect(decrypted.data).to.equal(plaintext); }); }); + + it('roundtrip workflow: encrypt twice with one password, decryptSessionKeys, only one session key', function() { + return openpgp.encrypt({ + data: plaintext, + passwords: [password1, password1] + }).then(function(encrypted) { + return openpgp.decryptSessionKeys({ + message: openpgp.message.readArmored(encrypted.data), + password: password1 + }); + }).then(function(decryptedSessionKeys) { + expect(decryptedSessionKeys.length).to.equal(1); + }); + }); }); describe('AES / RSA encrypt, decrypt, sign, verify', function() {