diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f7ed1001..32d3b9b9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1525,7 +1525,7 @@ "browserify-rsa": "4.0.1", "create-hash": "1.1.3", "create-hmac": "1.1.6", - "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#8b8ee8475b86402b125d4ad3a863a4ccd762e48c", "inherits": "2.0.3", "parse-asn1": "5.1.0" } @@ -1937,7 +1937,7 @@ "dev": true, "requires": { "bn.js": "4.11.8", - "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f" + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#8b8ee8475b86402b125d4ad3a863a4ccd762e48c" } }, "create-hash": { @@ -2325,7 +2325,7 @@ "dev": true }, "elliptic": { - "version": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", + "version": "git+https://github.com/openpgpjs/elliptic.git#8b8ee8475b86402b125d4ad3a863a4ccd762e48c", "requires": { "bn.js": "4.11.8", "brorand": "1.1.0", @@ -5604,7 +5604,7 @@ "integrity": "sha1-1QfOzkAInFJI4J7GgmaiAwqcYyU=", "requires": { "asn1.js": "4.9.2", - "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#8b8ee8475b86402b125d4ad3a863a4ccd762e48c", "safe-buffer": "5.1.1" }, "dependencies": { diff --git a/src/key.js b/src/key.js index 4acc880c..a929729c 100644 --- a/src/key.js +++ b/src/key.js @@ -208,22 +208,23 @@ Key.prototype.getKeyIds = function() { }; /** - * Returns first key packet for given array of key IDs - * @param {Array} keyIds + * Returns array containing first key packet for given key ID or all key packets in the case of a wildcard ID + * @param {type/keyid>} keyIds * @return {(module:packet/public_subkey|module:packet/public_key| * module:packet/secret_subkey|module:packet/secret_key|null)} */ -Key.prototype.getKeyPacket = function(keyIds) { +Key.prototype.getKeyPackets = function(packetKeyId) { var keys = this.getAllKeyPackets(); + if (packetKeyId.isWildcard()) { + return keys; + } for (var i = 0; i < keys.length; i++) { var keyId = keys[i].getKeyId(); - for (var j = 0; j < keyIds.length; j++) { - if (keyId.equals(keyIds[j])) { - return keys[i]; - } + if (keyId.equals(packetKeyId)) { + return [keys[i]]; } } - return null; + return []; }; /** diff --git a/src/message.js b/src/message.js index 97e1b348..04009b55 100644 --- a/src/message.js +++ b/src/message.js @@ -92,16 +92,13 @@ Message.prototype.getSigningKeyIds = function() { /** * Decrypt the message. Either a private key, a session key, or a password must be specified. - * @param {Array} privateKeys (optional) private key with decrypted secret data - * @param {Array} passwords (optional) password used to decrypt - * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } + * @param {Array} privateKeys (optional) private keys with decrypted secret data + * @param {Array} passwords (optional) passwords used to decrypt + * @param {Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } * @return {Message} new message with decrypted content */ -Message.prototype.decrypt = async function(privateKeys, passwords, sessionKey) { - let keyObjs = sessionKey || await this.decryptSessionKeys(privateKeys, passwords); - if (!util.isArray(keyObjs)) { - keyObjs = [keyObjs]; - } +Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) { + const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); const symEncryptedPacketlist = this.packets.filterByTag( enums.packet.symmetricallyEncrypted, @@ -141,9 +138,9 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKey) { }; /** - * Decrypt an encrypted session key either with private keys or passwords. - * @param {Array} privateKeys (optional) private key with decrypted secret data - * @param {Array} passwords (optional) password used to decrypt + * Decrypt encrypted session keys either with private keys or passwords. + * @param {Array} privateKeys (optional) private keys with decrypted secret data + * @param {Array} passwords (optional) passwords used to decrypt * @return {Array<{ data:Uint8Array, algorithm:String }>} array of object with potential sessionKey, algorithm pairs */ Message.prototype.decryptSessionKeys = function(privateKeys, passwords) { @@ -155,14 +152,12 @@ Message.prototype.decryptSessionKeys = function(privateKeys, passwords) { throw new Error('No symmetrically encrypted session key packet found.'); } await Promise.all(symESKeyPacketlist.map(async function(packet) { - for (var i = 0; i < passwords.length; i++) { + await Promise.all(passwords.map(async function(password) { try { - // eslint-disable-next-line no-await-in-loop - await packet.decrypt(passwords[i]); + await packet.decrypt(password); keyPackets.push(packet); - break; } catch (err) {} - } + })); })); } else if (privateKeys) { @@ -171,30 +166,21 @@ Message.prototype.decryptSessionKeys = function(privateKeys, passwords) { throw new Error('No public key encrypted session key packet found.'); } await Promise.all(pkESKeyPacketlist.map(async function(packet) { - for (var i = 0; i < privateKeys.length; i++){ - var privateKeyPackets; - if (packet.publicKeyId.isWildcard()) { - // wildcard key ID - try all key packets - privateKeyPackets = privateKeys[i].getAllKeyPackets(); - } else { - privateKeyPackets = [privateKeys[i].getKeyPacket([packet.publicKeyId])]; + var privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { + return acc.concat(privateKey.getKeyPackets(packet.publicKeyId)); + }, []); + await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { + if (!privateKeyPacket) { + return; } - for (var j = 0; j < privateKeyPackets.length; j++) { - var privateKeyPacket = privateKeyPackets[j]; - if (!privateKeyPacket) { - continue; - } - if (!privateKeyPacket.isDecrypted) { - throw new Error('Private key is not decrypted.'); - } - try { - // eslint-disable-next-line no-await-in-loop - await packet.decrypt(privateKeyPacket); - keyPackets.push(packet); - break; - } catch (err) {} + if (!privateKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); } - } + try { + await packet.decrypt(privateKeyPacket); + keyPackets.push(packet); + } catch (err) {} + })); })); } else { throw new Error('No key or password specified.'); @@ -259,9 +245,10 @@ Message.prototype.getText = function() { * @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 } + * @param {Boolean} useWildcard (optional) use a key ID of 0 instead of the public key IDs * @return {Message} new message with encrypted content */ -Message.prototype.encrypt = function(keys, passwords, sessionKey, useWildcard) { +Message.prototype.encrypt = function(keys, passwords, sessionKey, useWildcard=false) { let symAlgo, msg, symEncryptedPacket; return Promise.resolve().then(async () => { if (sessionKey) { @@ -314,9 +301,10 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, useWildcard) { * @param {String} symAlgo session key algorithm * @param {Array} publicKeys (optional) public key(s) for message encryption * @param {Array} passwords (optional) for message encryption + * @param {Boolean} useWildcard (optional) use a key ID of 0 instead of the public key IDs * @return {Message} new message with encrypted content */ -export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, useWildcard) { +export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, useWildcard=false) { var results, packetlist = new packet.List(); return Promise.resolve().then(async () => { @@ -328,11 +316,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, us throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); } var pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); - if (!useWildcard) { - pkESKeyPacket.publicKeyId = encryptionKeyPacket.getKeyId(); - } else { - pkESKeyPacket.publicKeyId = type_keyid.wildcard(); - } + pkESKeyPacket.publicKeyId = useWildcard ? type_keyid.wildcard() : encryptionKeyPacket.getKeyId(); pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm; pkESKeyPacket.sessionKey = sessionKey; pkESKeyPacket.sessionKeyAlgorithm = symAlgo; diff --git a/src/openpgp.js b/src/openpgp.js index fa36b8ef..f0cc7892 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -196,6 +196,7 @@ export function decryptKey({ privateKey, passphrase }) { * @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 + * @param {Boolean} useWildcard (optional) use a key ID of 0 instead of the public key IDs * @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} @@ -217,11 +218,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { var detachedSignature = await message.signDetached(privateKeys, signature); - if (armor) { - result.signature = detachedSignature.armor(); - } else { - result.signature = detachedSignature; - } + result.signature = armor ? detachedSignature.armor() : detachedSignature; } else { message = await message.sign(privateKeys, signature); } @@ -245,25 +242,25 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, /** * 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|Array} privateKeys (optional) private key with decrypted secret key data or session key - * @param {String|Array} passwords (optional) single password to decrypt the message - * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } - * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures - * @param {String} format (optional) return data format either as 'utf8' or 'binary' - * @param {Signature} signature (optional) detached signature for verification + * @param {Message} message the message object with the encrypted data + * @param {Key|Array} privateKeys (optional) private keys with decrypted secret key data or session key + * @param {String|Array} passwords (optional) passwords to decrypt the message + * @param {Object|Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } + * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures + * @param {String} format (optional) return data format either as 'utf8' or 'binary' + * @param {Signature} signature (optional) detached signature for verification * @return {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @static */ -export function decrypt({ message, privateKeys, passwords, sessionKey, publicKeys, format='utf8', signature=null }) { - checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); +export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null }) { + checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKey, publicKeys, format, signature }); + return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, signature }); } - return message.decrypt(privateKeys, passwords, sessionKey).then(async function(message) { + return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(message) { const result = parseMessage(message, format); @@ -271,13 +268,7 @@ export function decrypt({ message, privateKeys, passwords, sessionKey, publicKey publicKeys = []; } - if (signature) { - //detached signature - result.signatures = await message.verifyDetached(signature, publicKeys); - } else { - result.signatures = await message.verify(publicKeys); - } - + result.signatures = signature ? await message.verifyDetached(signature, publicKeys) : await message.verify(publicKeys); return result; }).catch(onError.bind(null, 'Error decrypting message')); @@ -312,21 +303,11 @@ export function sign({ data, privateKeys, armor=true, detached=false}) { var result = {}; return Promise.resolve().then(async function() { - var message; - - if (util.isString(data)) { - message = new CleartextMessage(data); - } else { - message = messageLib.fromBinary(data); - } + var message = util.isString(data) ? new CleartextMessage(data) : messageLib.fromBinary(data); if (detached) { var signature = await message.signDetached(privateKeys); - if (armor) { - result.signature = signature.armor(); - } else { - result.signature = signature; - } + result.signature = armor ? signature.armor() : signature; } else { message = await message.sign(privateKeys); if (armor) { @@ -360,18 +341,8 @@ export function verify({ message, publicKeys, signature=null }) { return Promise.resolve().then(async function() { var result = {}; - if (CleartextMessage.prototype.isPrototypeOf(message)) { - result.data = message.getText(); - } else { - result.data = message.getLiteralData(); - } - - if (signature) { - //detached signature - result.signatures = await message.verifyDetached(signature, publicKeys); - } else { - result.signatures = await message.verify(publicKeys); - } + result.data = CleartextMessage.prototype.isPrototypeOf(message) ? message.getText() : message.getLiteralData(); + result.signatures = signature ? await message.verifyDetached(signature, publicKeys) : await message.verify(publicKeys); return result; }).catch(onError.bind(null, 'Error verifying cleartext signed message')); @@ -392,6 +363,7 @@ export function verify({ message, publicKeys, signature=null }) { * @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 + * @param {Boolean} useWildcard (optional) use a key ID of 0 instead of the public key IDs * @return {Promise} the encrypted session key packets contained in a message object * @static */ @@ -410,7 +382,7 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords, useW } /** - * Decrypt a symmetric session key with a private key or password. Either a private key or + * 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