From be7b174df4dac2d1a4d8486e1878e343b26beb78 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Tue, 18 Feb 2020 16:56:55 +0100 Subject: [PATCH] Add openpgp.generateSessionKey --- src/index.js | 2 +- src/message.js | 65 +++++++++++++++++++++++++++----------------------- src/openpgp.js | 26 +++++++++++++++++++- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/index.js b/src/index.js index 393b1318..b3b521ab 100644 --- a/src/index.js +++ b/src/index.js @@ -20,7 +20,7 @@ export default openpgp; export { encrypt, decrypt, sign, verify, generateKey, reformatKey, revokeKey, decryptKey, - encryptSessionKey, decryptSessionKeys, + generateSessionKey, encryptSessionKey, decryptSessionKeys, initWorker, getWorker, destroyWorker } from './openpgp'; diff --git a/src/message.js b/src/message.js index c2dec45e..9509b99e 100644 --- a/src/message.js +++ b/src/message.js @@ -276,6 +276,23 @@ Message.prototype.getText = function() { return null; }; +/** + * Generate a new session key object, taking the algorithm preferences of the passed public keys into account, if any. + * @param {Array} keys (optional) public key(s) to select algorithm preferences for + * @param {Date} date (optional) date to select algorithm preferences at + * @param {Array} userIds (optional) user IDs to select algorithm preferences for + * @returns {Promise<{ data: Uint8Array, algorithm: String }>} object with session key data and algorithm + * @async + */ +export async function generateSessionKey(keys = [], date = new Date(), userIds = []) { + const algorithm = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds)); + const aeadAlgorithm = config.aead_protect && await isAeadSupported(keys, date, userIds) ? + enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds)) : + undefined; + const sessionKeyData = await crypto.generateSessionKey(algorithm); + return { data: sessionKeyData, algorithm, aeadAlgorithm }; +} + /** * Encrypt the message either with public keys, passwords, or both at once. * @param {Array} keys (optional) public key(s) for message encryption @@ -289,38 +306,26 @@ Message.prototype.getText = function() { * @async */ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard = false, date = new Date(), userIds = [], streaming) { - let symAlgo; - let aeadAlgo; - let symEncryptedPacket; - if (sessionKey) { if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { throw new Error('Invalid session key for encryption.'); } - symAlgo = sessionKey.algorithm; - aeadAlgo = sessionKey.aeadAlgorithm; - sessionKey = sessionKey.data; } else if (keys && keys.length) { - symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds)); - if (config.aead_protect && await isAeadSupported(keys, date, userIds)) { - aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds)); - } + sessionKey = await generateSessionKey(keys, date, userIds); } else if (passwords && passwords.length) { - symAlgo = enums.read(enums.symmetric, config.encryption_cipher); - aeadAlgo = enums.read(enums.aead, config.aead_mode); + sessionKey = await generateSessionKey(); } else { throw new Error('No keys, passwords, or session key provided.'); } - if (!sessionKey) { - sessionKey = await crypto.generateSessionKey(symAlgo); - } + const { data: sessionKeyData, algorithm, aeadAlgorithm } = sessionKey; - const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userIds); + const msg = await encryptSessionKey(sessionKeyData, algorithm, aeadAlgorithm, keys, passwords, wildcard, date, userIds); - if (config.aead_protect && aeadAlgo) { + let symEncryptedPacket; + if (aeadAlgorithm) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); - symEncryptedPacket.aeadAlgorithm = aeadAlgo; + symEncryptedPacket.aeadAlgorithm = aeadAlgorithm; } else if (config.integrity_protect) { symEncryptedPacket = new packet.SymEncryptedIntegrityProtected(); } else { @@ -328,7 +333,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard } symEncryptedPacket.packets = this.packets; - await symEncryptedPacket.encrypt(symAlgo, sessionKey, streaming); + await symEncryptedPacket.encrypt(algorithm, sessionKeyData, streaming); msg.packets.push(symEncryptedPacket); symEncryptedPacket.packets = new packet.List(); // remove packets after encryption @@ -345,8 +350,8 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard /** * 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 {String} aeadAlgo (optional) aead algorithm, e.g. 'eax' or 'ocb' + * @param {String} algorithm session key algorithm + * @param {String} aeadAlgorithm (optional) aead algorithm, e.g. 'eax' or 'ocb' * @param {Array} publicKeys (optional) public key(s) for message encryption * @param {Array} passwords (optional) for message encryption * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs @@ -355,7 +360,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard * @returns {Promise} new message with encrypted content * @async */ -export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard = false, date = new Date(), userIds = []) { +export async function encryptSessionKey(sessionKey, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard = false, date = new Date(), userIds = []) { const packetlist = new packet.List(); if (publicKeys) { @@ -365,7 +370,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKey.getKeyId(); pkESKeyPacket.publicKeyAlgorithm = encryptionKey.keyPacket.algorithm; pkESKeyPacket.sessionKey = sessionKey; - pkESKeyPacket.sessionKeyAlgorithm = symAlgo; + pkESKeyPacket.sessionKeyAlgorithm = algorithm; await pkESKeyPacket.encrypt(encryptionKey.keyPacket); delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption return pkESKeyPacket; @@ -384,19 +389,19 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey const sum = (accumulator, currentValue) => accumulator + currentValue; - const encryptPassword = async function(sessionKey, symAlgo, aeadAlgo, password) { + const encryptPassword = async function(sessionKey, algorithm, aeadAlgorithm, password) { const symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); symEncryptedSessionKeyPacket.sessionKey = sessionKey; - symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo; - if (aeadAlgo) { - symEncryptedSessionKeyPacket.aeadAlgorithm = aeadAlgo; + symEncryptedSessionKeyPacket.sessionKeyAlgorithm = algorithm; + if (aeadAlgorithm) { + symEncryptedSessionKeyPacket.aeadAlgorithm = aeadAlgorithm; } await symEncryptedSessionKeyPacket.encrypt(password); if (config.password_collision_check) { const results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd))); if (results.reduce(sum) !== 1) { - return encryptPassword(sessionKey, symAlgo, password); + return encryptPassword(sessionKey, algorithm, password); } } @@ -404,7 +409,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey return symEncryptedSessionKeyPacket; }; - const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, aeadAlgo, pwd))); + const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, algorithm, aeadAlgorithm, pwd))); packetlist.concat(results); } diff --git a/src/openpgp.js b/src/openpgp.js index a3938c49..ffb57835 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -131,7 +131,6 @@ export async function destroyWorker() { * @async * @static */ - export function generateKey({ userIds = [], passphrase = "", numBits = 2048, rsaBits = numBits, keyExpirationTime = 0, curve = "", date = new Date(), subkeys = [{}] }) { userIds = toArray(userIds); const options = { userIds, passphrase, rsaBits, keyExpirationTime, curve, date, subkeys }; @@ -516,6 +515,31 @@ export function verify({ message, publicKeys, format = 'utf8', streaming = messa // // /////////////////////////////////////////////// +/** + * Decrypt symmetric session keys 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|Array} privateKeys (optional) private keys with decrypted secret key data + * @param {String|Array} passwords (optional) passwords to decrypt the session key + * @returns {Promise} Array of decrypted session key, algorithm pairs in form: + * { data:Uint8Array, algorithm:String } + * or 'undefined' if no key packets found + * @async + * @static + */ +export function generateSessionKey({ publicKeys, date = new Date(), toUserIds = [] }) { + publicKeys = toArray(publicKeys); toUserIds = toArray(toUserIds); + + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('generateSessionKey', { publicKeys, date, toUserIds }); + } + + return Promise.resolve().then(async function() { + + return messageLib.generateSessionKey(publicKeys, date, toUserIds); + + }).catch(onError.bind(null, 'Error generating session key')); +} /** * Encrypt a symmetric session key with public keys, passwords, or both at once. At least either public keys