diff --git a/src/crypto/public_key/elliptic/key.js b/src/crypto/public_key/elliptic/key.js index 08c485df..4505bca7 100644 --- a/src/crypto/public_key/elliptic/key.js +++ b/src/crypto/public_key/elliptic/key.js @@ -45,7 +45,7 @@ function KeyPair(curve, options) { } KeyPair.prototype.sign = async function (message, hash_algo, hashed) { - if (!message.locked) { + if (message && !message.locked) { message = await stream.readToEnd(message); if (this.curve.web && util.getWebCrypto()) { // If browser doesn't support a curve, we'll catch it @@ -65,7 +65,7 @@ KeyPair.prototype.sign = async function (message, hash_algo, hashed) { }; KeyPair.prototype.verify = async function (message, signature, hash_algo, hashed) { - if (!message.locked) { + if (message && !message.locked) { message = await stream.readToEnd(message); if (this.curve.web && util.getWebCrypto()) { // If browser doesn't support a curve, we'll catch it diff --git a/src/message.js b/src/message.js index 401f50dc..c16e373a 100644 --- a/src/message.js +++ b/src/message.js @@ -127,6 +127,9 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) exception = e; } } + // We don't await stream.cancel here because... it sometimes hangs indefinitely. No clue why. + stream.cancel(symEncryptedPacket.encrypted); // Don't keep copy of encrypted data in memory. + symEncryptedPacket.encrypted = null; if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) { throw exception || new Error('Decryption failed.'); @@ -163,6 +166,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { util.print_debug_error(err); } })); + stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory. + keyPacket.encrypted = null; })); } else if (privateKeys) { const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); @@ -188,6 +193,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { util.print_debug_error(err); } })); + stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory. + keyPacket.encrypted = null; })); } else { throw new Error('No key or password specified.'); @@ -543,7 +550,7 @@ Message.prototype.verify = async function(keys, date=new Date()) { onePassSig.signatureData = stream.fromAsync(() => new Promise(resolve => { onePassSig.signatureDataResolve = resolve; })); - onePassSig.hash(literalDataList[0]); + onePassSig.hashed = onePassSig.hash(literalDataList[0]); }); const reader = stream.getReader(msg.packets.stream); for (let i = 0; ; i++) { diff --git a/src/openpgp.js b/src/openpgp.js index be8f87c8..e4e01730 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -365,14 +365,15 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe } return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(message) { - - const result = await parseMessage(message, format, asStream); - if (!publicKeys) { publicKeys = []; } + const result = {}; result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); + result.data = format === 'binary' ? message.getLiteralData() : message.getText(); + result.data = await convertStream(result.data, asStream); + result.filename = message.getFilename(); return result; }).catch(onError.bind(null, 'Error decrypting message')); } @@ -596,27 +597,6 @@ function createMessage(data, filename, date=new Date(), type) { return msg; } -/** - * Parse the message given a certain format. - * @param {Message} message the message object to be parse - * @param {String} format the output format e.g. 'utf8' or 'binary' - * @param {Boolean} asStream whether to return a ReadableStream - * @returns {Object} the parse data in the respective format - */ -async function parseMessage(message, format, asStream) { - let data; - if (format === 'binary') { - data = message.getLiteralData(); - } else if (format === 'utf8') { - data = message.getText(); - } else { - throw new Error('Invalid format'); - } - data = await convertStream(data, asStream); - const filename = message.getFilename(); - return { data, filename }; -} - /** * Convert data to or from Stream * @param {Object} data the data to convert diff --git a/src/packet/literal.js b/src/packet/literal.js index 9b1592a1..d3c10f2c 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -61,8 +61,8 @@ Literal.prototype.setText = function(text, format='utf8') { * with normalized end of line to \n * @returns {String} literal data as text */ -Literal.prototype.getText = function() { - if (this.text === null) { +Literal.prototype.getText = function(clone=false) { + if (this.text === null || this.text.locked) { let lastChar = ''; const decoder = new TextDecoder('utf8'); // eslint-disable-next-line no-inner-declarations @@ -79,9 +79,9 @@ Literal.prototype.getText = function() { lastChar = ''; return normalized; } - this.text = stream.transform(stream.clone(this.data), process, () => process(new Uint8Array(), true)); + this.text = stream.transform(this.getBytes(clone), process, () => process(new Uint8Array(), true)); } - return stream.clone(this.text); + return this.text; }; /** @@ -100,14 +100,17 @@ Literal.prototype.setBytes = function(bytes, format) { * Get the byte sequence representing the literal packet data * @returns {Uint8Array} A sequence of bytes */ -Literal.prototype.getBytes = function() { +Literal.prototype.getBytes = function(clone=false) { if (this.data === null) { // normalize EOL to \r\n const text = util.canonicalizeEOL(this.text); // encode UTF8 this.data = util.str_to_Uint8Array(util.encode_utf8(text)); } - return stream.clone(this.data); + if (clone) { + return stream.clone(this.data); + } + return this.data; }; diff --git a/src/packet/signature.js b/src/packet/signature.js index 490da2fc..1a4ddfe9 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -561,12 +561,12 @@ Signature.prototype.toSign = function (type, data) { switch (type) { case t.binary: if (data.text !== null) { - return util.str_to_Uint8Array(data.getText()); + return util.str_to_Uint8Array(data.getText(true)); } - return data.getBytes(); + return data.getBytes(true); case t.text: { - let text = data.getText(); + let text = data.getText(true); // normalize EOL to \r\n text = util.canonicalizeEOL(text); // encode UTF8 @@ -659,11 +659,8 @@ Signature.prototype.toHash = function(data) { }; Signature.prototype.hash = function(data, toHash) { - if (!this.hashed) { - const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - this.hashed = crypto.hash.digest(hashAlgorithm, toHash || this.toHash(data)); - } - return this.hashed; + const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); + return crypto.hash.digest(hashAlgorithm, toHash || this.toHash(data)); }; @@ -679,8 +676,15 @@ Signature.prototype.verify = async function (key, data) { const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - const toHash = this.toHash(data); - const hash = await stream.readToEnd(this.hash(data, toHash)); + let toHash; + let hash; + if (this.hashed) { + hash = this.hashed; + } else { + toHash = this.toHash(data); + hash = this.hash(data, toHash); + } + hash = await stream.readToEnd(hash); if (this.signedHashValue[0] !== hash[0] || this.signedHashValue[1] !== hash[1]) { diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 33e51fa1..d5a94364 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -96,7 +96,7 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith if (config.aead_protect_version !== 4) { this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm); } - await this.packets.read(await this.crypt('decrypt', key, this.encrypted)); + await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted))); return true; }; diff --git a/src/stream.js b/src/stream.js index c243b14e..c415814d 100644 --- a/src/stream.js +++ b/src/stream.js @@ -62,8 +62,16 @@ function tee(input) { function clone(input) { if (util.isStream(input)) { const teed = tee(input); - input.getReader = teed[0].getReader.bind(teed[0]); - input.tee = teed[0].tee.bind(teed[0]); + // Overwrite input.getReader, input.locked, etc to point to teed[0] + Object.entries(Object.getOwnPropertyDescriptors(ReadableStream.prototype)).forEach(([name, descriptor]) => { + if (name === 'constructor') return; + if (descriptor.value) { + descriptor.value = descriptor.value.bind(teed[0]); + } else { + descriptor.get = descriptor.get.bind(teed[0]); + } + Object.defineProperty(input, name, descriptor); + }); return teed[1]; } return subarray(input); @@ -108,6 +116,12 @@ async function readToEnd(input, join) { return input; } +async function cancel(input) { + if (util.isStream(input)) { + return input.cancel(); + } +} + function fromAsync(fn) { return new ReadableStream({ pull: async controller => { @@ -187,7 +201,7 @@ if (nodeStream) { } -export default { concat, getReader, transform, clone, subarray, readToEnd, nodeToWeb, webToNode, fromAsync }; +export default { concat, getReader, transform, clone, subarray, readToEnd, cancel, nodeToWeb, webToNode, fromAsync }; /*const readerAcquiredMap = new Map(); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 04bb25a5..15611d03 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1828,14 +1828,13 @@ describe('OpenPGP.js public api tests', function() { const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(+literals[0].date).to.equal(+past); - expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext); - return packets.verify(encryptOpt.publicKeys, past); - }).then(function (signatures) { + const signatures = await packets.verify(encryptOpt.publicKeys, past); expect(+signatures[0].signature.packets[0].created).to.equal(+past); expect(signatures[0].valid).to.be.true; expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, past)) .to.be.not.null; expect(signatures[0].signature.packets.length).to.equal(1); + expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext); }); }); @@ -1857,14 +1856,13 @@ describe('OpenPGP.js public api tests', function() { expect(literals.length).to.equal(1); expect(literals[0].format).to.equal('binary'); expect(+literals[0].date).to.equal(+future); - expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); - return packets.verify(encryptOpt.publicKeys, future); - }).then(function (signatures) { + const signatures = await packets.verify(encryptOpt.publicKeys, future); expect(+signatures[0].signature.packets[0].created).to.equal(+future); expect(signatures[0].valid).to.be.true; expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future)) .to.be.not.null; expect(signatures[0].signature.packets.length).to.equal(1); + expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); }); }); @@ -1887,14 +1885,13 @@ describe('OpenPGP.js public api tests', function() { expect(literals.length).to.equal(1); expect(literals[0].format).to.equal('mime'); expect(+literals[0].date).to.equal(+future); - expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); - return packets.verify(encryptOpt.publicKeys, future); - }).then(function (signatures) { + const signatures = await packets.verify(encryptOpt.publicKeys, future); expect(+signatures[0].signature.packets[0].created).to.equal(+future); expect(signatures[0].valid).to.be.true; expect(encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future)) .to.be.not.null; expect(signatures[0].signature.packets.length).to.equal(1); + expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 08e13175..2f932d0a 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -523,15 +523,15 @@ describe("Signature", function() { expect(pubKey2.getKeys(keyids[1])).to.not.be.empty; expect(pubKey3.getKeys(keyids[0])).to.not.be.empty; - expect(await openpgp.stream.readToEnd(sMsg.getText())).to.equal(plaintext); - - return sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => { + return sMsg.verify([pubKey2, pubKey3]).then(async verifiedSig => { expect(verifiedSig).to.exist; expect(verifiedSig).to.have.length(2); expect(verifiedSig[0].valid).to.be.true; expect(verifiedSig[1].valid).to.be.true; expect(verifiedSig[0].signature.packets.length).to.equal(1); expect(verifiedSig[1].signature.packets.length).to.equal(1); + + expect(await openpgp.stream.readToEnd(sMsg.getText())).to.equal(plaintext); }); });