From 1effe19c1dd98aabaabb9192466d9b9be7f7f0df Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Fri, 1 Jun 2018 12:19:16 +0200
Subject: [PATCH] Allow simultaneously reading data and waiting for signature
 verification

This makes openpgp.{decrypt,verify}().signatures a Promise when passing a
stream or when asStream=true
---
 src/openpgp.js            |  8 ++++----
 src/packet/clone.js       | 16 ++++++++++++++--
 src/worker/async_proxy.js |  2 +-
 test/general/signature.js |  1 +
 test/general/streaming.js |  8 ++++++++
 5 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/src/openpgp.js b/src/openpgp.js
index e4e01730..6ea51b1e 100644
--- a/src/openpgp.js
+++ b/src/openpgp.js
@@ -370,7 +370,8 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
     }
 
     const result = {};
-    result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date);
+    result.signatures = signature ? message.verifyDetached(signature, publicKeys, date) : message.verify(publicKeys, date);
+    if (!asStream) result.signatures = await result.signatures;
     result.data = format === 'binary' ? message.getLiteralData() : message.getText();
     result.data = await convertStream(result.data, asStream);
     result.filename = message.getFilename();
@@ -456,9 +457,8 @@ export function verify({ message, publicKeys, asStream, signature=null, date=new
 
   return Promise.resolve().then(async function() {
     const result = {};
-    result.signatures = signature ?
-      await message.verifyDetached(signature, publicKeys, date) :
-      await message.verify(publicKeys, date);
+    result.signatures = signature ? message.verifyDetached(signature, publicKeys, date) : message.verify(publicKeys, date);
+    if (!asStream) result.signatures = await result.signatures;
     result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData();
     result.data = await convertStream(result.data, asStream);
     return result;
diff --git a/src/packet/clone.js b/src/packet/clone.js
index 5253a9f9..eb20c875 100644
--- a/src/packet/clone.js
+++ b/src/packet/clone.js
@@ -28,6 +28,7 @@ import { CleartextMessage } from '../cleartext';
 import { Signature } from '../signature';
 import List from './packetlist';
 import type_keyid from '../type/keyid';
+import stream from '../stream';
 import util from '../util';
 
 
@@ -68,7 +69,12 @@ export function clonePackets(options) {
     options.signature = options.signature.packets;
   }
   if (options.signatures) {
-    options.signatures = options.signatures.map(sig => verificationObjectToClone(sig));
+    if (options.signatures instanceof Promise) {
+      const signatures = options.signatures;
+      options.signatures = stream.fromAsync(async () => (await signatures).map(verificationObjectToClone));
+    } else {
+      options.signatures.forEach(verificationObjectToClone);
+    }
   }
   return options;
 }
@@ -110,7 +116,13 @@ export function parseClonedPackets(options) {
     options.message = packetlistCloneToMessage(options.message);
   }
   if (options.signatures) {
-    options.signatures = options.signatures.map(packetlistCloneToSignatures);
+    if (util.isStream(options.signatures)) {
+      options.signatures = stream.readToEnd(options.signatures, arr => arr).then(([signatures]) => {
+        return signatures.map(packetlistCloneToSignatures);
+      });
+    } else {
+      options.signatures = options.signatures.map(packetlistCloneToSignatures);
+    }
   }
   if (options.signature) {
     options.signature = packetlistCloneToSignature(options.signature);
diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js
index 28de7f41..2f1a7a42 100644
--- a/src/worker/async_proxy.js
+++ b/src/worker/async_proxy.js
@@ -149,7 +149,7 @@ AsyncProxy.prototype.delegate = function(method, options) {
     this.workers[workerId].requests++;
 
     // remember to handle parsing cloned packets from worker
-    this.tasks[id] = { resolve: data => resolve(util.restoreStreams(packet.clone.parseClonedPackets(data, method))), reject };
+    this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(util.restoreStreams(data), method)), reject };
   });
 };
 
diff --git a/test/general/signature.js b/test/general/signature.js
index 2f932d0a..5bedfc43 100644
--- a/test/general/signature.js
+++ b/test/general/signature.js
@@ -680,6 +680,7 @@ yYDnCgA=
     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);
+      cleartextSig.signatures = await cleartextSig.signatures;
       expect(cleartextSig.signatures).to.have.length(1);
       expect(cleartextSig.signatures[0].valid).to.be.true;
       expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
diff --git a/test/general/streaming.js b/test/general/streaming.js
index d85c4cdd..2a20065c 100644
--- a/test/general/streaming.js
+++ b/test/general/streaming.js
@@ -48,6 +48,8 @@ describe('Streaming', function() {
       data,
       passwords: ['test'],
     });
+    await openpgp.stream.getReader(openpgp.stream.clone(encrypted.data)).readBytes(1000);
+    if (i > 10) throw new Error('Data did not arrive early.');
     const msgAsciiArmored = await openpgp.stream.readToEnd(encrypted.data);
     const message = await openpgp.message.readArmored(msgAsciiArmored);
     const decrypted = await openpgp.decrypt({
@@ -90,12 +92,15 @@ describe('Streaming', function() {
 
   it('Encrypt and decrypt larger message roundtrip (draft04)', async function() {
     let aead_protectValue = openpgp.config.aead_protect;
+    let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte;
     openpgp.config.aead_protect = true;
+    openpgp.config.aead_chunk_size_byte = 4;
     try {
       let plaintext = [];
       let i = 0;
       const data = new ReadableStream({
         async pull(controller) {
+          await new Promise(setTimeout);
           if (i++ < 10) {
             let randomBytes = await openpgp.crypto.random.getRandomBytes(1024);
             controller.enqueue(randomBytes);
@@ -118,9 +123,12 @@ describe('Streaming', function() {
         format: 'binary'
       });
       expect(util.isStream(decrypted.data)).to.be.true;
+      await openpgp.stream.getReader(openpgp.stream.clone(decrypted.data)).readBytes(1000);
+      if (i > 10) throw new Error('Data did not arrive early.');
       expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(util.concatUint8Array(plaintext));
     } finally {
       openpgp.config.aead_protect = aead_protectValue;
+      openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue;
     }
   });
 });