diff --git a/src/message.js b/src/message.js index a7d6854d..a73c6906 100644 --- a/src/message.js +++ b/src/message.js @@ -85,22 +85,21 @@ Message.prototype.getSigningKeyIds = function() { }; /** - * Decrypt the message - * @param {module:key~Key} privateKey private key with decrypted secret data - * @param {String} sessionKey session key as a binary string - * @param {String} password password used to decrypt - * @return {Array} new message with decrypted content + * Decrypt the message. Either a private key, a session key, or a password must be specified. + * @param {Key} privateKey (optional) private key with decrypted secret data + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } + * @param {String} password (optional) password used to decrypt + * @return {Message} new message with decrypted content */ Message.prototype.decrypt = function(privateKey, sessionKey, password) { - var keyObj = this.decryptSessionKey(privateKey, sessionKey, password); - if (!keyObj) { - // nothing to decrypt return unmodified message - return this; + var keyObj = sessionKey || this.decryptSessionKey(privateKey, password); + if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) { + throw new Error('Invalid session key for decryption.'); } var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected); if (symEncryptedPacketlist.length !== 0) { var symEncryptedPacket = symEncryptedPacketlist[0]; - symEncryptedPacket.decrypt(keyObj.algo, keyObj.key); + symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data); var resultMsg = new Message(symEncryptedPacket.packets); // remove packets after decryption symEncryptedPacket.packets = new packet.List(); @@ -109,21 +108,22 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) { }; /** - * Decrypt session key - * @param {module:key~Key} privateKey private key with decrypted secret data - * @param {String} sessionKey session key as a binary string - * @param {String} password password used to decrypt - * @return {Object} object with sessionKey, algo + * Decrypt an encrypted session key either with a private key or a password. + * @param {Key} privateKey (optional) private key with decrypted secret data + * @param {String} password (optional) password used to decrypt + * @return {Object} object with sessionKey, algorithm in the form: + * { data:Uint8Array, algorithm:String } */ -Message.prototype.decryptSessionKey = function(privateKey, sessionKey, password) { +Message.prototype.decryptSessionKey = function(privateKey, password) { var keyPacket; - if (sessionKey || password) { + + if (password) { var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); var symLength = symEncryptedSessionKeyPacketlist.length; for (var i = 0; i < symLength; i++) { keyPacket = symEncryptedSessionKeyPacketlist[i]; try { - keyPacket.decrypt(sessionKey || password); + keyPacket.decrypt(password); break; } catch(err) { @@ -160,7 +160,10 @@ Message.prototype.decryptSessionKey = function(privateKey, sessionKey, password) } if (keyPacket) { - return { key:keyPacket.sessionKey, algo:keyPacket.sessionKeyAlgorithm }; + return { + data: keyPacket.sessionKey, + algorithm: keyPacket.sessionKeyAlgorithm + }; } }; @@ -196,14 +199,12 @@ Message.prototype.getText = function() { }; /** - * Encrypt the message - * @param {(Array|module:key~Key)} public key(s) for message encryption - * @param {(Array|String)} password(s) for message encryption - * @return {Array} new message with encrypted content + * 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 + * @return {Message} new message with encrypted content */ Message.prototype.encrypt = function(keys, passwords) { - - /** Choose symAlgo */ var symAlgo; if (keys) { symAlgo = keyModule.getPreferredSymAlgo(keys); @@ -214,7 +215,6 @@ Message.prototype.encrypt = function(keys, passwords) { } var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); - var msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); var packetlist = msg.packets; @@ -229,28 +229,21 @@ Message.prototype.encrypt = function(keys, passwords) { packetlist.push(symEncryptedPacket); // remove packets after encryption symEncryptedPacket.packets = new packet.List(); + return msg; }; /** - * Encrypt a session key - * @param {String} session key for encryption - * @param {String} session key algorithm - * @param {(Array|module:key~Key)} public key(s) for message encryption - * @param {(Array|String)} password(s) for message encryption - * @return {Array} new message with encrypted content + * Encrypt a session key either with public keys, passwords, or both at once. + * @param {Uint8Array} sessionKey session key for encryption + * @param {String} symAlgo session key algorithm + * @param {Array} publicKeys (optional) public key(s) for message encryption + * @param {Array} passwords (optional) for message encryption + * @return {Message} new message with encrypted content */ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { - - /** Convert to arrays if necessary */ - if (publicKeys && !util.isArray(publicKeys)) { - publicKeys = [publicKeys]; - } - if (passwords && !util.isArray(passwords)) { - passwords = [passwords]; - } - var packetlist = new packet.List(); + if (publicKeys) { publicKeys.forEach(function(key) { var encryptionKeyPacket = key.getEncryptionKeyPacket(); @@ -267,6 +260,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { } }); } + if (passwords) { passwords.forEach(function(password) { var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); diff --git a/src/openpgp.js b/src/openpgp.js index 0664d8c5..3f96773c 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -147,8 +147,8 @@ export function decryptKey({ privateKey, passphrase }) { /** - * Encrypts message text/data with keys or passwords. Either public keys or passwords must be specified. - * If private keys are specified those will be used to sign the message. + * Encrypts message text/data with public keys, passwords or both at once. At least either public keys or passwords + * must be specified. If private keys are specified, those will be used to sign the message. * @param {String|Uint8Array} data text/data to be encrypted as JavaScript binary string or Uint8Array * @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 @@ -186,12 +186,12 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar } /** - * Decrypts a message with the user's private key, a session key or a password. - * Either a private key, a session key or a password must be specified. + * Decrypts a message with the user's private key, a session key or a password. Either a private key, + * a session key or a password must be specified. * @param {Message} message the message object with the encrypted data * @param {Key} privateKey (optional) private key with decrypted secret key data or session key * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures - * @param {String} sessionKey (optional) session key as a binary string + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {String} password (optional) single password to decrypt the message * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @return {Promise} decrypted and verified message in the form: @@ -226,7 +226,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, /** - * Signs a cleartext message + * Signs a cleartext message. * @param {String} data cleartext input to be signed * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object @@ -291,44 +291,48 @@ export function verify({ message, publicKeys }) { /** - * Encrypts session key with public keys or passwords. Either public keys or password must be specified. - * @param {String} sessionKey session key as a binary string - * @param {String} algo algorithm of sessionKey + * Encrypt a symmetric session key with public keys, passwords, or both at once. At least either public keys + * or passwords must be specified. + * @param {Uint8Array} data the session key to be encrypted e.g. 16 random bytes (for aes128) + * @param {String} algorithm algorithm of the symmetric session key e.g. 'aes128' or 'aes256' * @param {Key|Array} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {String|Array} passwords (optional) passwords for the message - * @return {Promise} Message object containing encrypted key packets + * @return {Promise} the encrypted session key packets contained in a message object * @static */ -export function encryptSessionKey({ sessionKey, algo, publicKeys, passwords }) { +export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) { + checkbinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords); + if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('encryptSessionKey', { sessionKey, algo, publicKeys, passwords }); + return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords }); } return execute(() => ({ - data: messageLib.encryptSessionKey(sessionKey, algo, publicKeys, passwords).packets.write() + message: messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords) }), 'Error encrypting session key'); } /** - * Decrypts session key with a private key, a session key or password. - * Either a private key, session key or a password must be specified. - * @param {Message} message the message object with the encrypted session key packets - * @param {Key} privateKey (optional) private key with decrypted secret key data - * @param {String} sessionKey (optional) session key as a binary string - * @param {String} password (optional) a single password to decrypt the session key - * @return {Promise} decrypted session key and algorithm in object form: - * { key:String, algo:String } - * or null if no key packets found + * Decrypt a symmetric session key with a private key or password. Either a private key or + * a password must be specified. + * @param {Message} message a message object containing the encrypted session key packets + * @param {Key} privateKey (optional) private key with decrypted secret key data + * @param {String} password (optional) a single password to decrypt the session key + * @return {Promise} decrypted session key and algorithm in object form: + * { data:Uint8Array, algorithm:String } + * or 'undefined' if no key packets found * @static */ -export function decryptSessionKey({ message, privateKey, sessionKey, password }) { +export function decryptSessionKey({ message, privateKey, password }) { + checkMessage(message); + if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('decryptSessionKey', { message, privateKey, sessionKey, password }); + return asyncProxy.delegate('decryptSessionKey', { message, privateKey, password }); } - return execute(() => message.decryptSessionKey(privateKey, sessionKey, password), 'Error decrypting session key'); + return execute(() => message.decryptSessionKey(privateKey, password), 'Error decrypting session key'); } @@ -347,6 +351,11 @@ function checkString(data, name) { throw new Error('Parameter [' + (name || 'data') + '] must be of type String'); } } +function checkbinary(data, name) { + if (!util.isUint8Array(data)) { + throw new Error('Parameter [' + (name || 'data') + '] must be of type Uint8Array'); + } +} function checkData(data, name) { if (!util.isUint8Array(data) && !util.isString(data)) { throw new Error('Parameter [' + (name || 'data') + '] must be of type String or Uint8Array'); diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 2265a33e..f2eaa985 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -116,7 +116,7 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm * * @param {module:enums.symmetric} sessionKeyAlgorithm * The selected symmetric encryption algorithm to be used - * @param {String} key The key of cipher blocksize length to be used + * @param {Uint8Array} key The key of cipher blocksize length to be used * @return {String} The decrypted data of this packet */ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index c5ff626d..22c20973 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -418,6 +418,94 @@ describe('OpenPGP.js public api tests', function() { }); }); + describe('encryptSessionKey, decryptSessionKey', function() { + var sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]); + + beforeEach(function() { + expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; + }); + + it('should encrypt with public key', function(done) { + openpgp.encryptSessionKey({ + data: sk, + algorithm: 'aes128', + publicKeys: publicKey.keys + }).then(function(encrypted) { + return openpgp.decryptSessionKey({ + message: encrypted.message, + privateKey: privateKey.keys[0] + }); + }).then(function(decrypted) { + expect(decrypted.data).to.deep.equal(sk); + done(); + }); + }); + + it('should encrypt with password', function(done) { + openpgp.encryptSessionKey({ + data: sk, + algorithm: 'aes128', + passwords: password1 + }).then(function(encrypted) { + return openpgp.decryptSessionKey({ + message: encrypted.message, + password: password1 + }); + }).then(function(decrypted) { + expect(decrypted.data).to.deep.equal(sk); + done(); + }); + }); + + it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with pgp key pair', function(done) { + var msgAsciiArmored; + openpgp.encrypt({ + data: plaintext, + publicKeys: publicKey.keys + }).then(function(encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKey({ + message: openpgp.message.readArmored(msgAsciiArmored), + privateKey: privateKey.keys[0] + }); + + }).then(function(decryptedSessionKey) { + return openpgp.decrypt({ + sessionKey: decryptedSessionKey, + message: openpgp.message.readArmored(msgAsciiArmored) + }); + + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + done(); + }); + }); + + it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with password', function(done) { + var msgAsciiArmored; + openpgp.encrypt({ + data: plaintext, + passwords: password1 + }).then(function(encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKey({ + message: openpgp.message.readArmored(msgAsciiArmored), + password: password1 + }); + + }).then(function(decryptedSessionKey) { + return openpgp.decrypt({ + sessionKey: decryptedSessionKey, + message: openpgp.message.readArmored(msgAsciiArmored) + }); + + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + done(); + }); + }); + }); + describe('with pgp key pair', function() { var wrong_pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' + 'Version: OpenPGP.js v0.9.0\r\n' + @@ -607,50 +695,15 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt and decrypt with one session key', function(done) { - var encOpt = { - data: plaintext, - passwords: password1 - }; - var decOpt = { - sessionKey: password1 - }; - openpgp.encrypt(encOpt).then(function(encrypted) { - decOpt.message = openpgp.message.readArmored(encrypted.data); - return openpgp.decrypt(decOpt); - }).then(function(decrypted) { - expect(decrypted.data).to.equal(plaintext); - done(); - }); - }); - - it('should encrypt and decrypt with two session keys and not ascii armor', function(done) { - var encOpt = { - data: plaintext, - passwords: [password1, password2], - armor: false - }; - var decOpt = { - sessionKey: password2 - }; - openpgp.encrypt(encOpt).then(function(encrypted) { - decOpt.message = encrypted.message; - return openpgp.decrypt(decOpt); - }).then(function(decrypted) { - expect(decrypted.data).to.equal(plaintext); - done(); - }); - }); - it('should encrypt and decrypt with binary data and transferable objects', function(done) { openpgp.config.zeroCopy = true; // activate transferable objects var encOpt = { data: new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]), - passwords: [password1, password2], + passwords: password1, armor: false }; var decOpt = { - sessionKey: password2, + password: password1, format: 'binary' }; openpgp.encrypt(encOpt).then(function(encrypted) {