rename decryptSessionKey to decryptSessionKeys, return only unique session keys

This commit is contained in:
Bart Butler 2018-02-06 21:25:49 -08:00
parent 210ec26ed3
commit 602bbb707d
5 changed files with 48 additions and 21 deletions

View File

@ -47,7 +47,7 @@ export default {
zero_copy: false, // use transferable objects between the Web Worker and main thread zero_copy: false, // use transferable objects between the Web Worker and main thread
debug: false, debug: false,
tolerant: true, // ignore unsupported/unrecognizable packets instead of throwing an error, 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_version: true,
show_comment: true, show_comment: true,
versionstring: "OpenPGP.js VERSION", versionstring: "OpenPGP.js VERSION",

View File

@ -22,7 +22,7 @@ export default openpgp;
export { export {
encrypt, decrypt, sign, verify, encrypt, decrypt, sign, verify,
generateKey, reformatKey, decryptKey, generateKey, reformatKey, decryptKey,
encryptSessionKey, decryptSessionKey, encryptSessionKey, decryptSessionKeys,
initWorker, getWorker, destroyWorker initWorker, getWorker, destroyWorker
} from './openpgp'; } from './openpgp';

View File

@ -97,7 +97,7 @@ Message.prototype.getSigningKeyIds = function() {
* @return {Message} new message with decrypted content * @return {Message} new message with decrypted content
*/ */
Message.prototype.decrypt = async function(privateKey, sessionKey, password) { 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)) { if (!util.isArray(keyObjs)) {
keyObjs = [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: * @return {Array} array of object with potential sessionKey, algorithm pairs in the form:
* { data:Uint8Array, algorithm:String } * { data:Uint8Array, algorithm:String }
*/ */
Message.prototype.decryptSessionKey = function(privateKey, password) { Message.prototype.decryptSessionKeys = function(privateKey, password) {
var keyPackets = []; var keyPackets = [];
return Promise.resolve().then(async () => { return Promise.resolve().then(async () => {
if (password) { if (password) {
@ -154,12 +154,12 @@ Message.prototype.decryptSessionKey = function(privateKey, password) {
if (!symESKeyPacketlist) { if (!symESKeyPacketlist) {
throw new Error('No symmetrically encrypted session key packet found.'); 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 { try {
await packet.decrypt(password); await packet.decrypt(password);
keyPackets.push(packet); keyPackets.push(packet);
} catch (err) {} } catch (err) {}
}); }));
} else if (privateKey) { } else if (privateKey) {
var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
@ -170,21 +170,34 @@ Message.prototype.decryptSessionKey = function(privateKey, password) {
if (!privateKeyPacket.isDecrypted) { if (!privateKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
// TODO replace when Promise.some or Promise.any are implemented await Promise.all(pkESKeyPacketlist.map(async function(packet) {
// eslint-disable-next-line no-await-in-loop
await pkESKeyPacketlist.some(async function(packet) {
if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) { if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) {
try { try {
await packet.decrypt(privateKeyPacket); await packet.decrypt(privateKeyPacket);
keyPackets.push(packet); keyPackets.push(packet);
} catch (err) {} } catch (err) {}
} }
}); }));
} else { } else {
throw new Error('No key or password specified.'); throw new Error('No key or password specified.');
} }
}).then(() => { }).then(() => {
if (keyPackets.length) { 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 })); return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm }));
} else { } else {
throw new Error('Session key decryption failed.'); throw new Error('Session key decryption failed.');

View File

@ -414,18 +414,18 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) {
* or 'undefined' if no key packets found * or 'undefined' if no key packets found
* @static * @static
*/ */
export function decryptSessionKey({ message, privateKey, password }) { export function decryptSessionKeys({ message, privateKey, password }) {
checkMessage(message); checkMessage(message);
if (asyncProxy) { // use web worker if available 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 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'));
} }

View File

@ -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]); var sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]);
beforeEach(function(done) { beforeEach(function(done) {
@ -539,7 +539,7 @@ describe('OpenPGP.js public api tests', function() {
algorithm: 'aes128', algorithm: 'aes128',
publicKeys: publicKey.keys publicKeys: publicKey.keys
}).then(function(encrypted) { }).then(function(encrypted) {
return openpgp.decryptSessionKey({ return openpgp.decryptSessionKeys({
message: encrypted.message, message: encrypted.message,
privateKey: privateKey.keys[0] privateKey: privateKey.keys[0]
}); });
@ -554,7 +554,7 @@ describe('OpenPGP.js public api tests', function() {
algorithm: 'aes128', algorithm: 'aes128',
passwords: password1 passwords: password1
}).then(function(encrypted) { }).then(function(encrypted) {
return openpgp.decryptSessionKey({ return openpgp.decryptSessionKeys({
message: encrypted.message, message: encrypted.message,
password: password1 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; var msgAsciiArmored;
return openpgp.encrypt({ return openpgp.encrypt({
data: plaintext, data: plaintext,
publicKeys: publicKey.keys publicKeys: publicKey.keys
}).then(function(encrypted) { }).then(function(encrypted) {
msgAsciiArmored = encrypted.data; msgAsciiArmored = encrypted.data;
return openpgp.decryptSessionKey({ return openpgp.decryptSessionKeys({
message: openpgp.message.readArmored(msgAsciiArmored), message: openpgp.message.readArmored(msgAsciiArmored),
privateKey: privateKey.keys[0] 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; var msgAsciiArmored;
return openpgp.encrypt({ return openpgp.encrypt({
data: plaintext, data: plaintext,
passwords: password1 passwords: password1
}).then(function(encrypted) { }).then(function(encrypted) {
msgAsciiArmored = encrypted.data; msgAsciiArmored = encrypted.data;
return openpgp.decryptSessionKey({ return openpgp.decryptSessionKeys({
message: openpgp.message.readArmored(msgAsciiArmored), message: openpgp.message.readArmored(msgAsciiArmored),
password: password1 password: password1
}); });
@ -608,6 +608,20 @@ describe('OpenPGP.js public api tests', function() {
expect(decrypted.data).to.equal(plaintext); 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() { describe('AES / RSA encrypt, decrypt, sign, verify', function() {