diff --git a/src/message.js b/src/message.js index 26a6ab04..539c1791 100644 --- a/src/message.js +++ b/src/message.js @@ -213,21 +213,31 @@ Message.prototype.getText = function() { * Encrypt the message either with public keys, passwords, or both at once. * @param {Array<Key>} keys (optional) public key(s) for message encryption * @param {Array<String>} 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) { - 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'); } - let sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); - msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); + if (sessionKey) { + if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { + throw new Error('Invalid session key for encryption.'); + } + symAlgo = sessionKey.algorithm; + sessionKey = sessionKey.data; + } else { + sessionKey = crypto.generateSessionKey(symAlgo); + } + + msg = encryptSessionKey(sessionKey, symAlgo, keys, passwords); if (config.aead_protect) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); @@ -238,12 +248,18 @@ Message.prototype.encrypt = function(keys, passwords) { } symEncryptedPacket.packets = this.packets; - return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey); + return symEncryptedPacket.encrypt(symAlgo, sessionKey); }).then(() => { msg.packets.push(symEncryptedPacket); symEncryptedPacket.packets = new packet.List(); // remove packets after encryption - return msg; + return { + message: msg, + sessionKey: { + data: sessionKey, + algorithm: symAlgo + } + }; }); }; diff --git a/src/openpgp.js b/src/openpgp.js index b01beef4..0129598e 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -177,20 +177,22 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Key|Array<Key>} publicKeys (optional) array of keys or single key, used to encrypt the message * @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed * @param {String|Array<String>} 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) * @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<Object>} 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} * @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, 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, filename, armor, detached, signature }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey }); } var result = {}; return Promise.resolve().then(() => { @@ -211,13 +213,16 @@ 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 => { + }).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 e3481b2d..0d31e93f 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -629,6 +629,71 @@ 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'), + 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,