From 7e03410bc9c2ac81e0517db7e075bc24f0114620 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Fri, 1 Dec 2017 21:25:43 -0800 Subject: [PATCH 1/5] allow sessionKey param in top level encrypt --- src/message.js | 14 +++++++++++-- src/openpgp.js | 7 ++++--- test/general/openpgp.js | 44 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/message.js b/src/message.js index 26a6ab04..c4fedd1b 100644 --- a/src/message.js +++ b/src/message.js @@ -213,9 +213,10 @@ Message.prototype.getText = function() { * Encrypt the message either with public keys, passwords, or both at once. * @param {Array} keys (optional) public key(s) for message encryption * @param {Array} passwords (optional) password(s) for message encryption + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @return {Message} new message with encrypted content */ -Message.prototype.encrypt = function(keys, passwords) { +Message.prototype.encrypt = function(keys, passwords, sessionKey) { let symAlgo, msg, symEncryptedPacket; return Promise.resolve().then(() => { if (keys) { @@ -226,7 +227,16 @@ Message.prototype.encrypt = function(keys, passwords) { throw new Error('No keys or passwords'); } - let sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); + if (sessionKey) { + if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { + throw new Error('Invalid session key for encryption.'); + } + symAlgo = enums.write(enums.symmetric, sessionKey.algorithm); + sessionKey = sessionKey.data; + } else { + sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); + } + msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); if (config.aead_protect) { diff --git a/src/openpgp.js b/src/openpgp.js index b01beef4..f131161f 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -177,6 +177,7 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Key|Array} publicKeys (optional) array of keys or single key, used to encrypt the message * @param {Key|Array} privateKeys (optional) private keys for signing. If omitted message will not be signed * @param {String|Array} passwords (optional) array of passwords or a single password to encrypt the message + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {String} filename (optional) a filename for the literal data packet * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) @@ -186,11 +187,11 @@ export function decryptKey({ privateKey, passphrase }) { * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false, signature=null }) { +export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, armor=true, detached=false, signature=null }) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor, detached, signature }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature }); } var result = {}; return Promise.resolve().then(() => { @@ -211,7 +212,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar message = message.sign(privateKeys, signature); } } - return message.encrypt(publicKeys, passwords); + return message.encrypt(publicKeys, passwords, sessionKey); }).then(message => { if (armor) { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index e3481b2d..d8acea17 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -629,6 +629,50 @@ describe('OpenPGP.js public api tests', function() { }); }); + it('should encrypt with custom session key and decrypt using session key', function() { + var sessionKey = { + data: openpgp.crypto.generateSessionKey('aes256'), + algorithm: 'aes256' + }; + var encOpt = { + data: plaintext, + sessionKey: sessionKey, + publicKeys: publicKey.keys + }; + var decOpt = { + sessionKey: sessionKey + }; + return openpgp.encrypt(encOpt).then(function(encrypted) { + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + }); + }); + + it('should encrypt using custom session key and decrypt using private key', function() { + var sessionKey = { + data: openpgp.crypto.generateSessionKey('aes128'), + algorithm: 'aes128' + }; + var encOpt = { + data: plaintext, + sessionKey: sessionKey, + publicKeys: publicKey.keys + }; + var decOpt = { + privateKey: privateKey.keys[0] + }; + return openpgp.encrypt(encOpt).then(function(encrypted) { + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + }); + }); + it('should encrypt/sign and decrypt/verify', function() { var encOpt = { data: plaintext, From f0d65780ad86685c4423c69bf080aa5c083a1447 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Fri, 1 Dec 2017 21:30:19 -0800 Subject: [PATCH 2/5] wording --- test/general/openpgp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/general/openpgp.js b/test/general/openpgp.js index d8acea17..d60b59ab 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -629,7 +629,7 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt with custom session key and decrypt using session key', function() { + it('should encrypt using custom session key and decrypt using session key', function() { var sessionKey = { data: openpgp.crypto.generateSessionKey('aes256'), algorithm: 'aes256' From 6189cd4568f5268acfc5b9faa1b36e2d93e23237 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Sat, 2 Dec 2017 11:04:29 -0800 Subject: [PATCH 3/5] returnSessionKey flag in encrypt function --- src/message.js | 8 +++++++- src/openpgp.js | 13 ++++++++----- test/general/key.js | 2 +- test/general/openpgp.js | 21 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/message.js b/src/message.js index c4fedd1b..169aa081 100644 --- a/src/message.js +++ b/src/message.js @@ -253,7 +253,13 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { }).then(() => { msg.packets.push(symEncryptedPacket); symEncryptedPacket.packets = new packet.List(); // remove packets after encryption - return msg; + return { + message: msg, + sessionKey: { + data: sessionKey, + algorithm: enums.read(enums.symmetric, symAlgo) + } + }; }); }; diff --git a/src/openpgp.js b/src/openpgp.js index f131161f..514843cc 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -187,11 +187,11 @@ export function decryptKey({ privateKey, passphrase }) { * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, armor=true, detached=false, signature=null }) { +export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, armor=true, detached=false, signature=null, returnSessionKey=false}) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey }); } var result = {}; return Promise.resolve().then(() => { @@ -214,11 +214,14 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, } return message.encrypt(publicKeys, passwords, sessionKey); - }).then(message => { + }).then(encrypted => { if (armor) { - result.data = message.armor(); + result.data = encrypted.message.armor(); } else { - result.message = message; + result.message = encrypted.message; + } + if (returnSessionKey) { + result.sessionKey = encrypted.sessionKey; } return result; }).catch(onError.bind(null, 'Error encrypting message')); diff --git a/test/general/key.js b/test/general/key.js index 2de33d11..10a8dde3 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -931,7 +931,7 @@ describe('Key', function() { key = newKey; return openpgp.message.fromText('hello').encrypt([key.key]); }).then(function(msg) { - return msg.decrypt(key.key); + return msg.message.decrypt(key.key); }).catch(function(err) { expect(err.message).to.equal('Private key is not decrypted.'); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index d60b59ab..0d31e93f 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -629,6 +629,27 @@ describe('OpenPGP.js public api tests', function() { }); }); + it('should encrypt then decrypt using returned session key', function() { + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys, + returnSessionKey: true + }; + + return openpgp.encrypt(encOpt).then(function(encrypted) { + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + var decOpt = { + sessionKey: encrypted.sessionKey, + message: openpgp.message.readArmored(encrypted.data) + }; + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures).to.exist; + expect(decrypted.signatures.length).to.equal(0); + }); + }); + it('should encrypt using custom session key and decrypt using session key', function() { var sessionKey = { data: openpgp.crypto.generateSessionKey('aes256'), From 0e254a8c8ccfc96079bb7f875c0614d4da46b3e2 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Sat, 2 Dec 2017 11:17:39 -0800 Subject: [PATCH 4/5] simplify --- src/message.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/message.js b/src/message.js index 169aa081..539c1791 100644 --- a/src/message.js +++ b/src/message.js @@ -220,9 +220,9 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { let symAlgo, msg, symEncryptedPacket; return Promise.resolve().then(() => { if (keys) { - symAlgo = keyModule.getPreferredSymAlgo(keys); + symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys)); } else if (passwords) { - symAlgo = config.encryption_cipher; + symAlgo = enums.read(enums.symmetric, config.encryption_cipher); } else { throw new Error('No keys or passwords'); } @@ -231,13 +231,13 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { throw new Error('Invalid session key for encryption.'); } - symAlgo = enums.write(enums.symmetric, sessionKey.algorithm); + symAlgo = sessionKey.algorithm; sessionKey = sessionKey.data; } else { - sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); + sessionKey = crypto.generateSessionKey(symAlgo); } - msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); + msg = encryptSessionKey(sessionKey, symAlgo, keys, passwords); if (config.aead_protect) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); @@ -248,7 +248,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { } symEncryptedPacket.packets = this.packets; - return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey); + return symEncryptedPacket.encrypt(symAlgo, sessionKey); }).then(() => { msg.packets.push(symEncryptedPacket); @@ -257,7 +257,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { message: msg, sessionKey: { data: sessionKey, - algorithm: enums.read(enums.symmetric, symAlgo) + algorithm: symAlgo } }; }); From 135bd3d46aa879ea5d8785de281f5e069841a751 Mon Sep 17 00:00:00 2001 From: Sanjana Rajan Date: Sat, 2 Dec 2017 11:48:14 -0800 Subject: [PATCH 5/5] doc update --- src/openpgp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openpgp.js b/src/openpgp.js index 514843cc..0129598e 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -182,6 +182,7 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) * @param {Signature} signature (optional) a detached signature to add to the encrypted message + * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}