From 37517313307ae2d2f8e200d265dd9fffeada21f4 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Thu, 4 Oct 2018 16:14:44 +0200 Subject: [PATCH] Don't hang when signature packet corresponding to one-pass sig is missing --- src/message.js | 15 ++++++++------ src/packet/one_pass_signature.js | 3 +++ test/general/signature.js | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/message.js b/src/message.js index 3a727f66..3ebf07c7 100644 --- a/src/message.js +++ b/src/message.js @@ -545,7 +545,7 @@ Message.prototype.verify = async function(keys, date=new Date(), streaming) { if (literalDataList.length !== 1) { throw new Error('Can only verify message with one literal data packet.'); } - const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature); + const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse(); const signatureList = msg.packets.filterByTag(enums.packet.signature); if (onePassSigList.length && !signatureList.length && msg.packets.stream) { onePassSigList.forEach(onePassSig => { @@ -555,25 +555,28 @@ Message.prototype.verify = async function(keys, date=new Date(), streaming) { onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData); onePassSig.hashed = onePassSig.hash(literalDataList[0], undefined, streaming); }); - const verificationObjects = await createVerificationObjects(onePassSigList, literalDataList, keys, date); msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => { + const reader = stream.getReader(readable); const writer = stream.getWriter(writable); try { - await stream.readToEnd(stream.transform(readable, signature => { - onePassSigList.pop().correspondingSigResolve(signature); - })); + for (let i = 0; i < onePassSigList.length; i++) { + const { value: signature } = await reader.read(); + onePassSigList[i].correspondingSigResolve(signature); + } + await reader.readToEnd(); await writer.ready; await writer.close(); } catch(e) { onePassSigList.forEach(onePassSig => { onePassSig.correspondingSigResolve({ + tag: enums.packet.signature, verify: () => undefined }); }); await writer.abort(e); } }); - return verificationObjects.reverse(); + return createVerificationObjects(onePassSigList, literalDataList, keys, date); } return createVerificationObjects(signatureList, literalDataList, keys, date); }; diff --git a/src/packet/one_pass_signature.js b/src/packet/one_pass_signature.js index c72df02b..de49a3da 100644 --- a/src/packet/one_pass_signature.js +++ b/src/packet/one_pass_signature.js @@ -142,6 +142,9 @@ OnePassSignature.prototype.calculateTrailer = Signature.prototype.calculateTrail OnePassSignature.prototype.verify = async function() { const correspondingSig = await this.correspondingSig; + if (!correspondingSig || correspondingSig.tag !== enums.packet.signature) { + throw new Error('Corresponding signature packet missing'); + } correspondingSig.hashed = this.hashed; return correspondingSig.verify.apply(correspondingSig, arguments); }; diff --git a/test/general/signature.js b/test/general/signature.js index dee2e04e..22b25832 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -600,6 +600,41 @@ yYDnCgA= }); }); + it('Streaming verify signed message with missing signature packet', async function() { + const msg_armor = + `-----BEGIN PGP MESSAGE----- +Version: OpenPGP.js v3.1.3 +Comment: https://openpgpjs.org + +yFgBO8LLzMjE+KDlu0uOgs50xtNRJdzFBYnJqcW6JanFJVE3r9eCuVYKvFxg +hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA + +=D6TZ +-----END PGP MESSAGE-----`.split(''); + + const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; + const sMsg = await openpgp.message.readArmored(new ReadableStream({ + async pull(controller) { + await new Promise(setTimeout); + controller.enqueue(msg_armor.shift()); + if (!msg_armor.length) controller.close(); + } + })); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + + const keyids = sMsg.getSigningKeyIds(); + + expect(pubKey.getKeys(keyids[0])).to.not.be.empty; + + return openpgp.verify({ publicKeys:[pubKey], message:sMsg }).then(async function(cleartextSig) { + expect(cleartextSig).to.exist; + expect(openpgp.util.nativeEOL(openpgp.util.Uint8Array_to_str(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext); + expect(cleartextSig.signatures).to.have.length(1); + await expect(cleartextSig.signatures[0].verified).to.be.rejectedWith('Corresponding signature packet missing'); + expect((await cleartextSig.signatures[0].signature).packets.length).to.equal(0); + }); + }); + it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', async function() { const plaintext = 'short message\nnext line \n한국어/조선말'; const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0];