From e6820d7b2a16c5d517806dca6c27bda77603ef1f Mon Sep 17 00:00:00 2001
From: Mahrud Sayrafi <mahrud@berkeley.edu>
Date: Thu, 4 Jan 2018 01:40:08 -0800
Subject: [PATCH] Passing all tests, on Node, Firefox, and Chrome

---
 src/cleartext.js                         |  25 ++--
 src/crypto/public_key/elliptic/curves.js |  71 +++++------
 src/crypto/public_key/elliptic/ecdsa.js  | 142 +++++++++++----------
 src/enums.js                             |  11 ++
 src/message.js                           | 150 +++++++++++------------
 src/worker/worker.js                     |   2 +-
 test/general/ecc.js                      |   8 +-
 test/general/key.js                      |   2 +-
 test/general/openpgp.js                  |  20 +--
 test/general/signature.js                |  32 ++---
 test/unittests.html                      |   2 +-
 11 files changed, 233 insertions(+), 232 deletions(-)

diff --git a/src/cleartext.js b/src/cleartext.js
index be3bee3b..cf12f9e6 100644
--- a/src/cleartext.js
+++ b/src/cleartext.js
@@ -95,8 +95,10 @@ CleartextMessage.prototype.signDetached = async function(privateKeys) {
       throw new Error('Private key is not decrypted.');
     }
     await signaturePacket.sign(signingKeyPacket, literalDataPacket);
-    packetlist.push(signaturePacket);
-  }));
+    return signaturePacket;
+  })).then(signatureList => {
+    signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
+  });
 
   return new sigModule.Signature(packetlist);
 };
@@ -116,16 +118,14 @@ CleartextMessage.prototype.verify = function(keys) {
  * @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
  */
 CleartextMessage.prototype.verifyDetached = function(signature, keys) {
-  var result = [];
   var signatureList = signature.packets;
   var literalDataPacket = new packet.Literal();
   // we assume that cleartext signature is generated based on UTF8 cleartext
   literalDataPacket.setText(this.text);
-  // TODO await Promise.all(signatureList.map(async function(signature) {  }));
-  for (var i = 0; i < signatureList.length; i++) {
+  return Promise.all(signatureList.map(async function(signature) {
     var keyPacket = null;
     for (var j = 0; j < keys.length; j++) {
-      keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId);
+      keyPacket = keys[j].getSigningKeyPacket(signature.issuerKeyId);
       if (keyPacket) {
         break;
       }
@@ -133,20 +133,19 @@ CleartextMessage.prototype.verifyDetached = function(signature, keys) {
 
     var verifiedSig = {};
     if (keyPacket) {
-      verifiedSig.keyid = signatureList[i].issuerKeyId;
-      verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataPacket);
+      verifiedSig.keyid = signature.issuerKeyId;
+      verifiedSig.valid = await signature.verify(keyPacket, literalDataPacket);
     } else {
-      verifiedSig.keyid = signatureList[i].issuerKeyId;
+      verifiedSig.keyid = signature.issuerKeyId;
       verifiedSig.valid = null;
     }
 
     var packetlist = new packet.List();
-    packetlist.push(signatureList[i]);
+    packetlist.push(signature);
     verifiedSig.signature = new sigModule.Signature(packetlist);
 
-    result.push(verifiedSig);
-  }
-  return result;
+    return verifiedSig;
+  }));
 };
 
 /**
diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js
index ca874b40..4d03e045 100644
--- a/src/crypto/public_key/elliptic/curves.js
+++ b/src/crypto/public_key/elliptic/curves.js
@@ -27,8 +27,6 @@
 
 'use strict';
 
-import ASN1 from 'asn1.js';
-
 import {ec as EC} from 'elliptic';
 import {KeyPair} from './key.js';
 import BigInteger from '../jsbn.js';
@@ -40,14 +38,6 @@ import base64 from '../../../encoding/base64.js';
 const webCrypto = util.getWebCrypto();
 const nodeCrypto = util.getNodeCrypto();
 
-
-var ECPrivateKey = ASN1.define('ECPrivateKey', function() {
-  this.seq().obj(
-    this.key('r').int(),  // FIXME int or BN?
-    this.key('s').int()   // FIXME int or BN?
-  );
-});
-
 var webCurves = [], nodeCurves = [];
 if (webCrypto && config.use_native) {
   // see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms
@@ -60,6 +50,7 @@ if (webCrypto && config.use_native) {
 const curves = {
   p256: {
     oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
+    pointSize: 66, // FIXME
     namedCurve: 'P-256',
     opensslCurve: 'prime256v1',
     hashName: 'SHA-256',
@@ -70,6 +61,7 @@ const curves = {
   },
   p384: {
     oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]),
+    pointSize: 48,
     namedCurve: 'P-384',
     opensslCurve: 'secp384r1', // FIXME
     hashName: 'SHA-384',
@@ -80,6 +72,7 @@ const curves = {
   },
   p521: {
     oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]),
+    pointSize: 66,
     namedCurve: 'P-521',
     opensslCurve: 'secp521r1', // FIXME
     hashName: 'SHA-512',
@@ -90,6 +83,7 @@ const curves = {
   },
   secp256k1: {
     oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
+    pointSize: 66, // FIXME
     namedCurve: 'SECP-256K1',
     opensslCurve: 'secp256k1',
     hashName: 'SHA-256',
@@ -103,10 +97,11 @@ const curves = {
   ed25519 : {}
 };
 
-function Curve(name, {oid, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) {
+function Curve(name, {oid, pointSize, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) {
   this.curve = new EC(name);
   this.name = name;
   this.oid = oid;
+  this.pointSize = pointSize;
   this.hash = hash;
   this.cipher = cipher;
   this.namedCurve= namedCurve;
@@ -177,41 +172,33 @@ module.exports = {
 
 
 async function webGenKeyPair(namedCurve, algorithm) {
-  try {
-    var webCryptoKey = await webCrypto.generateKey(
-      {
-        name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
-        namedCurve: namedCurve
-      },
-      true,
-      algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
-    );
+  var webCryptoKey = await webCrypto.generateKey(
+    {
+      name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
+      namedCurve: namedCurve
+    },
+    true,
+    algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
+  );
 
-    var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey);
-    var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey);
+  var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey);
+  var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey);
 
-    return {
-      pub: {
-        x: base64.decode(publicKey.x, 'base64url'),
-        y: base64.decode(publicKey.y, 'base64url')
-      },
-      priv: base64.decode(privateKey.d, 'base64url')
-    };
-  } catch(err) {
-    throw new Error(err);
-  }
+  return {
+    pub: {
+      x: base64.decode(publicKey.x, 'base64url'),
+      y: base64.decode(publicKey.y, 'base64url')
+    },
+    priv: base64.decode(privateKey.d, 'base64url')
+  };
 }
 
 async function nodeGenKeyPair(opensslCurve) {
-  try {
-    var ecdh = nodeCrypto.createECDH(opensslCurve);
-    await ecdh.generateKeys();
+  var ecdh = nodeCrypto.createECDH(opensslCurve);
+  await ecdh.generateKeys();
 
-    return {
-      pub: ecdh.getPublicKey().toJSON().data,
-      priv: ecdh.getPrivateKey().toJSON().data
-    };
-  } catch(err) {
-    throw new Error(err);
-  }
+  return {
+    pub: ecdh.getPublicKey().toJSON().data,
+    priv: ecdh.getPrivateKey().toJSON().data
+  };
 }
diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js
index e14163a2..dd75150b 100644
--- a/src/crypto/public_key/elliptic/ecdsa.js
+++ b/src/crypto/public_key/elliptic/ecdsa.js
@@ -57,6 +57,7 @@ var ECDSASignature = ASN1.define('ECDSASignature', function() {
 async function sign(oid, hash_algo, m, d) {
   var signature;
   const curve = curves.get(oid);
+  hash_algo = hash_algo ? hash_algo : curve.hash;
   const key = curve.keyFromPrivate(d.toByteArray());
   if (webCrypto && config.use_native && curve.web) {
     signature = await webSign(curve, hash_algo, m, key.keyPair);
@@ -83,6 +84,7 @@ async function sign(oid, hash_algo, m, d) {
 async function verify(oid, hash_algo, signature, m, Q) {
   var result;
   const curve = curves.get(oid);
+  hash_algo = hash_algo ? hash_algo : curve.hash;  // FIXME is this according to the RFC?
   const key = curve.keyFromPublic(Q.toByteArray());
   if (webCrypto && config.use_native && curve.web) {
     result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
@@ -109,76 +111,83 @@ module.exports = {
 //////////////////////////
 
 
-async function webSign(curve, hash_algo, m, keyPair) {
-  try {
-    const key = await webCrypto.importKey(
-      "jwk",
-      {
-        "kty": "EC",
-        "crv": curve.namedCurve,
-        "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray()), null, 'base64url'),
-        "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray()), null, 'base64url'),
-        "d": base64.encode(new Uint8Array(keyPair.getPrivate()), null, 'base64url'),
-        "use": "sig",
-        "kid": "ECDSA Private Key"
-      },
-      {
-        "name": "ECDSA",
-        "namedCurve": curve.namedCurve,
-        "hash": { name: enums.read(enums.hash, hash_algo) }
-      },
-      false,
-      ["sign"]
-    );
-
-    return await webCrypto.sign(
-      {
-        "name": 'ECDSA',
-        "namedCurve": curve.namedCurve,
-        "hash": { name: enums.read(enums.hash, hash_algo) }
-      },
-      key,
-      m
-    );
-  } catch(err) {
-    throw new Error(err);
+async function webSign(curve, hash_algo, message, keyPair) {
+  var l = curve.pointSize;
+  if (typeof message === 'string') {
+    message = util.str2Uint8Array(message);
   }
+  const key = await webCrypto.importKey(
+    "jwk",
+    {
+      "kty": "EC",
+      "crv": curve.namedCurve,
+      "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), null, 'base64url'),
+      "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), null, 'base64url'),
+      "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), null, 'base64url'),
+      "use": "sig",
+      "kid": "ECDSA Private Key"
+    },
+    {
+      "name": "ECDSA",
+      "namedCurve": curve.namedCurve,
+      "hash": { name: curve.hashName }
+    },
+    false,
+    ["sign"]
+  );
+
+  const signature = new Uint8Array(await webCrypto.sign(
+    {
+      "name": 'ECDSA',
+      "namedCurve": curve.namedCurve,
+      "hash": { name: enums.read(enums.webHash, hash_algo) }
+    },
+    key,
+    message
+  ));
+  return {
+    r: signature.slice(0, l),
+    s: signature.slice(l, 2 * l)
+  };
 }
 
-async function webVerify(curve, hash_algo, signature, m, publicKey) {
-  try {
-    const key = await webCrypto.importKey(
-      "jwk",
-      {
-        "kty": "EC",
-        "crv": curve.namedCurve,
-        "x": base64.encode(new Uint8Array(publicKey.getX().toArray()), null, 'base64url'),
-        "y": base64.encode(new Uint8Array(publicKey.getY().toArray()), null, 'base64url'),
-        "use": "sig",
-        "kid": "ECDSA Public Key"
-      },
-      {
-        "name": "ECDSA",
-        "namedCurve": curve.namedCurve,
-        "hash": { name: enums.read(enums.hash, hash_algo) }
-      },
-      false,
-      ["verify"]
-    );
-
-    return await webCrypto.verify(
-      {
-        "name": 'ECDSA',
-        "namedCurve": curve.namedCurve,
-        "hash": { name: enums.read(enums.hash, hash_algo) }
-      },
-      key,
-      signature,
-      m
-    );
-  } catch(err) {
-    throw new Error(err);
+async function webVerify(curve, hash_algo, signature, message, publicKey) {
+  var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.pointSize;
+  r = (r.length === l) ? r : [0].concat(r);
+  s = (s.length === l) ? s : [0].concat(s);
+  signature = new Uint8Array(r.concat(s)).buffer;
+  if (typeof message === 'string') {
+    message = util.str2Uint8Array(message);
   }
+  const key = await webCrypto.importKey(
+    "jwk",
+    {
+      "kty": "EC",
+      "crv": curve.namedCurve,
+      "x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), null, 'base64url'),
+      "y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), null, 'base64url'),
+      "use": "sig",
+      "kid": "ECDSA Public Key"
+    },
+    {
+      "name": "ECDSA",
+      "namedCurve": curve.namedCurve,
+      "hash": { name: curve.hashName }
+    },
+    false,
+    ["verify"]
+  );
+
+  return webCrypto.verify(
+    {
+      "name": 'ECDSA',
+      "namedCurve": curve.namedCurve,
+      "hash": { name: enums.read(enums.webHash, hash_algo) }
+    },
+    key,
+    signature,
+    message
+  );
 }
 
 
@@ -199,7 +208,6 @@ async function nodeSign(curve, hash_algo, message, keyPair) {
     { private: true }
   );
 
-  // FIXME what happens when hash_algo = undefined?
   const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
   sign.write(message);
   sign.end();
diff --git a/src/enums.js b/src/enums.js
index edaecb5d..a043429b 100644
--- a/src/enums.js
+++ b/src/enums.js
@@ -75,6 +75,17 @@ export default {
     sha224: 11
   },
 
+  /** A list of hash names as accepted by webCrypto functions.
+   * {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest|Parameters, algo}
+   * @enum {String}
+   */
+  webHash: {
+    'SHA-1': 2,
+    'SHA-256': 8,
+    'SHA-384': 9,
+    'SHA-512': 10
+  },
+
   /** A list of packet types and numeric tags associated with them.
    * @enum {Integer}
    * @readonly
diff --git a/src/message.js b/src/message.js
index a73d4215..311dbcd9 100644
--- a/src/message.js
+++ b/src/message.js
@@ -124,23 +124,21 @@ Message.prototype.decrypt = async function(privateKey, sessionKey, password) {
  *                               { data:Uint8Array, algorithm:String }
  */
 Message.prototype.decryptSessionKey = function(privateKey, password) {
-  var keyPacket, results, error;
+  var keyPacket;
   return Promise.resolve().then(async () => {
     if (password) {
       var symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
-      // FIXME need a circuit breaker here
       if (!symESKeyPacketlist) {
         throw new Error('No symmetrically encrypted session key packet found.');
       }
-      results = await Promise.all(symESKeyPacketlist.map(async function(packet) {
+      // TODO replace when Promise.some or Promise.any are implemented
+      await symESKeyPacketlist.some(async function(packet) {
         try {
           await packet.decrypt(password);
-          return packet;
-        } catch (err) {
-          error = err;
-        }
-      }));
-      keyPacket = results.find(result => result !== undefined);
+          keyPacket = packet;
+          return true;
+        } catch (err) {}
+      });
 
     } else if (privateKey) {
       var encryptionKeyIds = this.getEncryptionKeyIds();
@@ -156,19 +154,17 @@ Message.prototype.decryptSessionKey = function(privateKey, password) {
       if (!pkESKeyPacketlist) {
         throw new Error('No public key encrypted session key packet found.');
       }
-      // FIXME need a circuit breaker here
-      results = await Promise.all(pkESKeyPacketlist.map(async function(packet) {
+      // TODO replace when Promise.some or Promise.any are implemented
+      // eslint-disable-next-line no-await-in-loop
+      await pkESKeyPacketlist.some(async function(packet) {
         if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) {
           try {
-            await packet.decrypt(privateKeyPacket)
-            return packet;
-          } catch (err) {
-            error = err;
-          }
+            await packet.decrypt(privateKeyPacket);
+            keyPacket = packet;
+            return true;
+          } catch (err) {}
         }
-      }));
-      keyPacket = results.find(result => result !== undefined);
-
+      });
     } else {
       throw new Error('No key or password specified.');
     }
@@ -225,14 +221,6 @@ Message.prototype.getText = function() {
 Message.prototype.encrypt = function(keys, passwords, sessionKey) {
   let symAlgo, msg, symEncryptedPacket;
   return Promise.resolve().then(async () => {
-    if (keys) {
-      symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys));
-    } else if (passwords) {
-      symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
-    } else {
-      throw new Error('No keys or passwords');
-    }
-
     if (sessionKey) {
       if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) {
         throw new Error('Invalid session key for encryption.');
@@ -291,6 +279,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
   return Promise.resolve().then(async () => {
     if (publicKeys) {
       results = await Promise.all(publicKeys.map(async function(key) {
+        await key.verifyPrimaryUser();
         var encryptionKeyPacket = key.getEncryptionKeyPacket();
         if (!encryptionKeyPacket) {
           throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
@@ -338,62 +327,67 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) {
     throw new Error('No literal data packet to sign.');
   }
 
+  var i;
   var literalFormat = enums.write(enums.literal, literalDataPacket.format);
   var signatureType = literalFormat === enums.literal.binary ?
     enums.signature.binary : enums.signature.text;
-  var i, signingKeyPacket, existingSigPacketlist, onePassSig;
 
   if (signature) {
-    existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
-    if (existingSigPacketlist.length) {
-      for (i = existingSigPacketlist.length - 1; i >= 0; i--) {
-        var sigPacket = existingSigPacketlist[i];
-        onePassSig = new packet.OnePassSignature();
-        onePassSig.type = signatureType;
-        onePassSig.hashAlgorithm = config.prefer_hash_algorithm;
-        onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm;
-        onePassSig.signingKeyId = sigPacket.issuerKeyId;
-        if (!privateKeys.length && i === 0) {
-          onePassSig.flags = 1;
-        }
-        packetlist.push(onePassSig);
+    var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
+    for (i = existingSigPacketlist.length - 1; i >= 0; i--) {
+      var signaturePacket = existingSigPacketlist[i];
+      var onePassSig = new packet.OnePassSignature();
+      onePassSig.type = signatureType;
+      onePassSig.hashAlgorithm = signaturePacket.hashAlgorithm;
+      onePassSig.publicKeyAlgorithm = signaturePacket.publicKeyAlgorithm;
+      onePassSig.signingKeyId = signaturePacket.issuerKeyId;
+      if (!privateKeys.length && i === 0) {
+        onePassSig.flags = 1;
       }
+      packetlist.push(onePassSig);
     }
   }
-  for (i = 0; i < privateKeys.length; i++) {
-    if (privateKeys[i].isPublic()) {
+
+  await Promise.all(privateKeys.map(async function (privateKey, i) {
+    if (privateKey.isPublic()) {
       throw new Error('Need private key for signing');
     }
+    await privateKey.verifyPrimaryUser();
+    var signingKeyPacket = privateKey.getSigningKeyPacket();
+    if (!signingKeyPacket) {
+      throw new Error('Could not find valid key packet for signing in key ' +
+                      privateKey.primaryKey.getKeyId().toHex());
+    }
     onePassSig = new packet.OnePassSignature();
     onePassSig.type = signatureType;
     //TODO get preferred hash algo from key signature
-    onePassSig.hashAlgorithm = config.prefer_hash_algorithm;
-    signingKeyPacket = privateKeys[i].getSigningKeyPacket();
-    if (!signingKeyPacket) {
-      throw new Error('Could not find valid key packet for signing in key ' + privateKeys[i].primaryKey.getKeyId().toHex());
-    }
+    onePassSig.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
     onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
     onePassSig.signingKeyId = signingKeyPacket.getKeyId();
     if (i === privateKeys.length - 1) {
       onePassSig.flags = 1;
     }
-    packetlist.push(onePassSig);
-  }
+    return onePassSig;
+  })).then(onePassSignatureList => {
+    onePassSignatureList.forEach(onePassSig => packetlist.push(onePassSig));
+  });
 
   packetlist.push(literalDataPacket);
 
   await Promise.all(privateKeys.reverse().map(async function(privateKey) {
-    var signingKeyPacket = privateKey.getSigningKeyPacket();
     var signaturePacket = new packet.Signature();
-    signaturePacket.signatureType = signatureType;
-    signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
-    signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
+    var signingKeyPacket = privateKey.getSigningKeyPacket();
     if (!signingKeyPacket.isDecrypted) {
       throw new Error('Private key is not decrypted.');
     }
+    signaturePacket.signatureType = signatureType;
+    signaturePacket.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
+    signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
     await signaturePacket.sign(signingKeyPacket, literalDataPacket);
-    packetlist.push(signaturePacket);
-  }));
+    return signaturePacket;
+  })).then(signatureList => {
+    signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
+  });
 
   if (signature) {
     packetlist.concat(existingSigPacketlist);
@@ -422,17 +416,20 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null)
     enums.signature.binary : enums.signature.text;
 
   await Promise.all(privateKeys.map(async function(privateKey) {
-    var signingKeyPacket = privateKey.getSigningKeyPacket();
     var signaturePacket = new packet.Signature();
-    signaturePacket.signatureType = signatureType;
-    signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
-    signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
+    await privateKey.verifyPrimaryUser();
+    var signingKeyPacket = privateKey.getSigningKeyPacket();
     if (!signingKeyPacket.isDecrypted) {
       throw new Error('Private key is not decrypted.');
     }
+    signaturePacket.signatureType = signatureType;
+    signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
+    signaturePacket.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
     await signaturePacket.sign(signingKeyPacket, literalDataPacket);
-    packetlist.push(signaturePacket);
-  }));
+    return signaturePacket;
+  })).then(signatureList => {
+    signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
+  });
 
   if (signature) {
     var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
@@ -481,34 +478,33 @@ Message.prototype.verifyDetached = function(signature, keys) {
  * @param {Array<module:key~Key>} keys array of keys to verify signatures
  * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
  */
-function createVerificationObjects(signatureList, literalDataList, keys) {
-  var result = [];
-  for (var i = 0; i < signatureList.length; i++) {
+async function createVerificationObjects(signatureList, literalDataList, keys) {
+  return Promise.all(signatureList.map(async function(signature) {
     var keyPacket = null;
-    for (var j = 0; j < keys.length; j++) {
-      keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId, config.verify_expired_keys);
-      if (keyPacket) {
-        break;
+    await Promise.all(keys.map(async function(key) {
+      await key.verifyPrimaryUser();
+      var result = key.getSigningKeyPacket(signature.issuerKeyId, config.verify_expired_keys);
+      if (result) {
+        keyPacket = result;
       }
-    }
+    }));
 
     var verifiedSig = {};
     if (keyPacket) {
       //found a key packet that matches keyId of signature
-      verifiedSig.keyid = signatureList[i].issuerKeyId;
-      verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]);
+      verifiedSig.keyid = signature.issuerKeyId;
+      verifiedSig.valid = await signature.verify(keyPacket, literalDataList[0]);
     } else {
-      verifiedSig.keyid = signatureList[i].issuerKeyId;
+      verifiedSig.keyid = signature.issuerKeyId;
       verifiedSig.valid = null;
     }
 
     var packetlist = new packet.List();
-    packetlist.push(signatureList[i]);
+    packetlist.push(signature);
     verifiedSig.signature = new sigModule.Signature(packetlist);
 
-    result.push(verifiedSig);
-  }
-  return Promise.all(result);
+    return verifiedSig;
+  }));
 }
 
 /**
diff --git a/src/worker/worker.js b/src/worker/worker.js
index 2b25f319..1e411d18 100644
--- a/src/worker/worker.js
+++ b/src/worker/worker.js
@@ -19,7 +19,7 @@
 
 self.window = {}; // to make UMD bundles work
 
-importScripts('openpgp.js');
+importScripts('openpgp_browser.js'); // FIXME
 var openpgp = window.openpgp;
 
 var MIN_SIZE_RANDOM_BUFFER = 40000;
diff --git a/test/general/ecc.js b/test/general/ecc.js
index 59e19fe5..92bb7c52 100644
--- a/test/general/ecc.js
+++ b/test/general/ecc.js
@@ -165,7 +165,7 @@ describe('Elliptic Curve Cryptography', function () {
       expect(result).to.exist;
       expect(result.data.trim()).to.equal(data.juliet.message);
       expect(result.signatures).to.have.length(1);
-      expect(result.signatures[0].valid).to.eventually.be.true;
+      expect(result.signatures[0].valid).to.be.true;
     });
   });
   // FIXME is this pattern correct?
@@ -178,7 +178,7 @@ describe('Elliptic Curve Cryptography', function () {
         expect(result).to.exist;
         expect(result.data.trim()).to.equal(data.romeo.message);
         expect(result.signatures).to.have.length(1);
-        expect(result.signatures[0].valid).to.eventually.be.true;
+        expect(result.signatures[0].valid).to.be.true;
       });
     });
   });
@@ -193,7 +193,7 @@ describe('Elliptic Curve Cryptography', function () {
       // trim required because https://github.com/openpgpjs/openpgpjs/issues/311
       expect(result.data.trim()).to.equal(data.juliet.message);
       expect(result.signatures).to.have.length(1);
-      expect(result.signatures[0].valid).to.eventually.be.true;
+      expect(result.signatures[0].valid).to.be.true;
     });
   });
   it('Encrypt and sign message', function () {
@@ -212,7 +212,7 @@ describe('Elliptic Curve Cryptography', function () {
         expect(result).to.exist;
         expect(result.data.trim()).to.equal(data.romeo.message);
         expect(result.signatures).to.have.length(1);
-        expect(result.signatures[0].valid).to.eventually.be.true;
+        expect(result.signatures[0].valid).to.be.true;
       });
     });
   });
diff --git a/test/general/key.js b/test/general/key.js
index 4eab2195..2722b07d 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -1172,7 +1172,7 @@ describe('Key', function() {
         return openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) {
           return openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) {
             expect(decrypted.data).to.equal('hello');
-            expect(decrypted.signatures[0].valid).to.eventually.be.true;
+            expect(decrypted.signatures[0].valid).to.be.true;
           });
         });
       });
diff --git a/test/general/openpgp.js b/test/general/openpgp.js
index 6070b3a2..09b4eee1 100644
--- a/test/general/openpgp.js
+++ b/test/general/openpgp.js
@@ -712,7 +712,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.decrypt(decOpt);
           }).then(function(decrypted) {
             expect(decrypted.data).to.equal(plaintext);
-            expect(decrypted.signatures[0].valid).to.eventually.be.true;
+            expect(decrypted.signatures[0].valid).to.be.true;
             expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -756,7 +756,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.decrypt(decOpt);
           }).then(function(decrypted) {
             expect(decrypted.data).to.equal(plaintext);
-            expect(decrypted.signatures[0].valid).to.eventually.be.true;
+            expect(decrypted.signatures[0].valid).to.be.true;
             expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -789,7 +789,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.decrypt(decOpt);
           }).then(function(decrypted) {
             expect(decrypted.data).to.equal(plaintext);
-            expect(decrypted.signatures[0].valid).to.eventually.be.true;
+            expect(decrypted.signatures[0].valid).to.be.true;
             expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -826,10 +826,10 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.decrypt(decOpt);
           }).then(function(decrypted) {
             expect(decrypted.data).to.equal(plaintext);
-            expect(decrypted.signatures[0].valid).to.eventually.be.true;
+            expect(decrypted.signatures[0].valid).to.be.true;
             expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
-            expect(decrypted.signatures[1].valid).to.eventually.be.true;
+            expect(decrypted.signatures[1].valid).to.be.true;
             expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
             expect(decrypted.signatures[1].signature.packets.length).to.equal(1);
           });
@@ -998,7 +998,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.verify(verifyOpt);
           }).then(function(verified) {
             expect(verified.data).to.equal(plaintext);
-            expect(verified.signatures[0].valid).to.eventually.be.true;
+            expect(verified.signatures[0].valid).to.be.true;
             expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(verified.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -1019,7 +1019,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.verify(verifyOpt);
           }).then(function(verified) {
             expect(verified.data).to.equal(plaintext);
-            expect(verified.signatures[0].valid).to.eventually.be.true;
+            expect(verified.signatures[0].valid).to.be.true;
             expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(verified.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -1079,7 +1079,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.verify(verifyOpt);
           }).then(function(verified) {
             expect(verified.data).to.equal(plaintext);
-            expect(verified.signatures[0].valid).to.eventually.be.true;
+            expect(verified.signatures[0].valid).to.be.true;
             expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(verified.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -1101,7 +1101,7 @@ describe('OpenPGP.js public api tests', function() {
             return openpgp.verify(verifyOpt);
           }).then(function(verified) {
             expect(verified.data).to.equal(plaintext);
-            expect(verified.signatures[0].valid).to.eventually.be.true;
+            expect(verified.signatures[0].valid).to.be.true;
             expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
             expect(verified.signatures[0].signature.packets.length).to.equal(1);
           });
@@ -1126,7 +1126,7 @@ describe('OpenPGP.js public api tests', function() {
           }).then(function(encrypted) {
             expect(encrypted.data).to.exist;
             expect(encrypted.data).to.equal(plaintext);
-            expect(encrypted.signatures[0].valid).to.eventually.be.true;
+            expect(encrypted.signatures[0].valid).to.be.true;
             expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
             expect(encrypted.signatures[0].signature.packets.length).to.equal(1);
           });
diff --git a/test/general/signature.js b/test/general/signature.js
index 57c0eff2..efe2db14 100644
--- a/test/general/signature.js
+++ b/test/general/signature.js
@@ -342,7 +342,7 @@ describe("Signature", function() {
     priv_key.decrypt("abcd");
     return openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) {
       expect(decrypted.data).to.exist;
-      expect(decrypted.signatures[0].valid).to.eventually.be.true;
+      expect(decrypted.signatures[0].valid).to.be.true;
       expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
     });
   });
@@ -386,7 +386,7 @@ describe("Signature", function() {
       return msg.verify([pub_key]).then(verified => {
         expect(verified).to.exist;
         expect(verified).to.have.length(1);
-        expect(verified[0].valid).to.eventually.be.true;
+        expect(verified[0].valid).to.be.true;
         expect(verified[0].signature.packets.length).to.equal(1);
       });
     });
@@ -411,7 +411,7 @@ describe("Signature", function() {
     return sMsg.verify([pub_key]).then(verified => {
       expect(verified).to.exist;
       expect(verified).to.have.length(1);
-      expect(verified[0].valid).to.eventually.be.true;
+      expect(verified[0].valid).to.be.true;
       expect(verified[0].signature.packets.length).to.equal(1);
     });
   });
@@ -435,7 +435,7 @@ describe("Signature", function() {
     sMsg.verify([pub_key]).then(verified => {
       expect(verified).to.exist;
       expect(verified).to.have.length(1);
-      expect(verified[0].valid).to.eventually.be.true;
+      expect(verified[0].valid).to.be.true;
       expect(verified[0].signature.packets.length).to.equal(1);
     });
   });
@@ -470,7 +470,7 @@ describe("Signature", function() {
       expect(decrypted.data).to.exist;
       expect(decrypted.data).to.equal(plaintext);
       expect(decrypted.signatures).to.have.length(1);
-      expect(decrypted.signatures[0].valid).to.eventually.be.true;
+      expect(decrypted.signatures[0].valid).to.be.true;
       expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
     });
   });
@@ -506,7 +506,7 @@ describe("Signature", function() {
       expect(decrypted.data).to.exist;
       expect(decrypted.data).to.equal(plaintext);
       expect(decrypted.signatures).to.have.length(1);
-      expect(decrypted.signatures[0].valid).to.eventually.be.true;
+      expect(decrypted.signatures[0].valid).to.be.true;
       expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
     });
 
@@ -547,8 +547,8 @@ describe("Signature", function() {
     sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => {
       expect(verifiedSig).to.exist;
       expect(verifiedSig).to.have.length(2);
-      expect(verifiedSig[0].valid).to.eventually.be.true;
-      expect(verifiedSig[1].valid).to.eventually.be.true;
+      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);
     });
@@ -593,8 +593,8 @@ describe("Signature", function() {
       expect(cleartextSig).to.exist;
       expect(cleartextSig.data).to.equal(plaintext);
       expect(cleartextSig.signatures).to.have.length(2);
-      expect(cleartextSig.signatures[0].valid).to.eventually.be.true;
-      expect(cleartextSig.signatures[1].valid).to.eventually.be.true;
+      expect(cleartextSig.signatures[0].valid).to.be.true;
+      expect(cleartextSig.signatures[1].valid).to.be.true;
       expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
       expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1);
     });
@@ -615,7 +615,7 @@ describe("Signature", function() {
       expect(cleartextSig).to.exist;
       expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,''));
       expect(cleartextSig.signatures).to.have.length(1);
-      expect(cleartextSig.signatures[0].valid).to.eventually.be.true;
+      expect(cleartextSig.signatures[0].valid).to.be.true;
       expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
     });
 
@@ -636,7 +636,7 @@ describe("Signature", function() {
       expect(cleartextSig).to.exist;
       expect(cleartextSig.data).to.deep.equal(plaintext);
       expect(cleartextSig.signatures).to.have.length(1);
-      expect(cleartextSig.signatures[0].valid).to.eventually.be.true;
+      expect(cleartextSig.signatures[0].valid).to.be.true;
       expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
     });
 
@@ -657,7 +657,7 @@ describe("Signature", function() {
       expect(cleartextSig).to.exist;
       expect(cleartextSig.data).to.deep.equal(plaintext);
       expect(cleartextSig.signatures).to.have.length(1);
-      expect(cleartextSig.signatures[0].valid).to.eventually.be.true;
+      expect(cleartextSig.signatures[0].valid).to.be.true;
       expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
     });
 
@@ -781,7 +781,7 @@ describe("Signature", function() {
 
     var msg = openpgp.message.readSignedContent(content, detachedSig);
     return msg.verify(publicKeys).then(result => {
-      expect(result[0].valid).to.eventually.be.true;
+      expect(result[0].valid).to.be.true;
     });
   });
 
@@ -797,8 +797,8 @@ describe("Signature", function() {
       var generatedKey = gen.key;
       return msg.signDetached([generatedKey, privKey2]).then(detachedSig => {
         return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(result => {
-          expect(result[0].valid).to.eventually.be.true;
-          expect(result[1].valid).to.eventually.be.true;
+          expect(result[0].valid).to.be.true;
+          expect(result[1].valid).to.be.true;
         });
       });
     });
diff --git a/test/unittests.html b/test/unittests.html
index 8f860f19..3ee2b4a6 100644
--- a/test/unittests.html
+++ b/test/unittests.html
@@ -14,7 +14,7 @@
     <script src="lib/chai.js"></script>
     <script src="lib/mocha.js"></script>
     <script src="lib/fetch.js"></script>
-]    <script>
+    <script>
         mocha.setup('bdd');
         mocha.timeout(240000);
     </script>