Merge pull request #611 from openpgpjs/encrypt_session_key

Option to pass in custom session key in top-level encrypt function
This commit is contained in:
Bart Butler 2017-12-02 13:27:59 -08:00 committed by GitHub
commit e91129aeeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 14 deletions

View File

@ -213,21 +213,31 @@ Message.prototype.getText = function() {
* Encrypt the message either with public keys, passwords, or both at once. * 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<Key>} keys (optional) public key(s) for message encryption
* @param {Array<String>} passwords (optional) password(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 * @return {Message} new message with encrypted content
*/ */
Message.prototype.encrypt = function(keys, passwords) { Message.prototype.encrypt = function(keys, passwords, sessionKey) {
let symAlgo, msg, symEncryptedPacket; let symAlgo, msg, symEncryptedPacket;
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (keys) { if (keys) {
symAlgo = keyModule.getPreferredSymAlgo(keys); symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys));
} else if (passwords) { } else if (passwords) {
symAlgo = config.encryption_cipher; symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
} else { } else {
throw new Error('No keys or passwords'); throw new Error('No keys or passwords');
} }
let sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); if (sessionKey) {
msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); 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) { if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected(); symEncryptedPacket = new packet.SymEncryptedAEADProtected();
@ -238,12 +248,18 @@ Message.prototype.encrypt = function(keys, passwords) {
} }
symEncryptedPacket.packets = this.packets; symEncryptedPacket.packets = this.packets;
return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey); return symEncryptedPacket.encrypt(symAlgo, sessionKey);
}).then(() => { }).then(() => {
msg.packets.push(symEncryptedPacket); msg.packets.push(symEncryptedPacket);
symEncryptedPacket.packets = new packet.List(); // remove packets after encryption symEncryptedPacket.packets = new packet.List(); // remove packets after encryption
return msg; return {
message: msg,
sessionKey: {
data: sessionKey,
algorithm: symAlgo
}
};
}); });
}; };

View File

@ -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>} 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 {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 {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 {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} 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 {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 {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: * @return {Promise<Object>} encrypted (and optionally signed message) in the form:
* {data: ASCII armored message if 'armor' is true, * {data: ASCII armored message if 'armor' is true,
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
* @static * @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); checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported 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 = {}; var result = {};
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
@ -211,13 +213,16 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
message = message.sign(privateKeys, signature); message = message.sign(privateKeys, signature);
} }
} }
return message.encrypt(publicKeys, passwords); return message.encrypt(publicKeys, passwords, sessionKey);
}).then(message => { }).then(encrypted => {
if (armor) { if (armor) {
result.data = message.armor(); result.data = encrypted.message.armor();
} else { } else {
result.message = message; result.message = encrypted.message;
}
if (returnSessionKey) {
result.sessionKey = encrypted.sessionKey;
} }
return result; return result;
}).catch(onError.bind(null, 'Error encrypting message')); }).catch(onError.bind(null, 'Error encrypting message'));

View File

@ -931,7 +931,7 @@ describe('Key', function() {
key = newKey; key = newKey;
return openpgp.message.fromText('hello').encrypt([key.key]); return openpgp.message.fromText('hello').encrypt([key.key]);
}).then(function(msg) { }).then(function(msg) {
return msg.decrypt(key.key); return msg.message.decrypt(key.key);
}).catch(function(err) { }).catch(function(err) {
expect(err.message).to.equal('Private key is not decrypted.'); expect(err.message).to.equal('Private key is not decrypted.');
}); });

View File

@ -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() { it('should encrypt/sign and decrypt/verify', function() {
var encOpt = { var encOpt = {
data: plaintext, data: plaintext,