From 5cb89f4f257fe7bbbc88c8a6b02345b8e4b8ebe0 Mon Sep 17 00:00:00 2001
From: Mahrud Sayrafi <mahrud@berkeley.edu>
Date: Thu, 18 Jan 2018 00:34:03 -0800
Subject: [PATCH] Addresses various review comments by @sanjanarajan

 * Various FIXME tags are removed
 * In curve.js:
  - webCrypto/nodeCrypto fallback bug is fixed
  - Curve25519 has keyType ecdsa (won't be used for signing, but technically can be)
  - webGenKeyPair is simplifed
 * In base64.js:
  - documentation added and arguments simplified
 * In ecdsa.js and eddsa.js:
  - hash_algo is now at least as strong as the default curve hash
  - simplified the code by moving webSign/nodeSign and webVerify/nodeVerify to live in key.js (ht @ismaelbej)
 * In message.js:
  - in decryptSessionKey, loops break once a key packet is decrypted
 * In key.js:
  - getPreferredHashAlgorithm returns the best hash algorithm
  - enums are used for curve selection
---
 src/crypto/public_key/elliptic/curves.js |  82 +++++-----
 src/crypto/public_key/elliptic/ecdsa.js  | 191 +----------------------
 src/crypto/public_key/elliptic/eddsa.js  |   9 +-
 src/crypto/public_key/elliptic/index.js  |   5 +-
 src/crypto/public_key/elliptic/key.js    | 171 +++++++++++++++++++-
 src/encoding/base64.js                   |  22 ++-
 src/enums.js                             |  17 +-
 src/packet/packetlist.js                 |  16 +-
 src/type/oid.js                          |   8 +
 test/crypto/elliptic.js                  |  14 +-
 test/general/ecc.js                      |   2 +-
 11 files changed, 278 insertions(+), 259 deletions(-)

diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js
index d21d8121..3fa52cf5 100644
--- a/src/crypto/public_key/elliptic/curves.js
+++ b/src/crypto/public_key/elliptic/curves.js
@@ -34,19 +34,19 @@ import random from '../../random';
 import config from '../../../config';
 import enums from '../../../enums';
 import util from '../../../util';
+import OID from '../../../type/oid';
 import base64 from '../../../encoding/base64';
 
 const webCrypto = util.getWebCrypto();
 const nodeCrypto = util.getNodeCrypto();
 
 var webCurves = {}, nodeCurves = {};
-if (webCrypto && config.use_native) {
-  webCurves = {
-    'p256': 'P-256',
-    'p384': 'P-384',
-    'p521': 'P-521'
-  };
-} else if (nodeCrypto && config.use_native) {
+webCurves = {
+  'p256': 'P-256',
+  'p384': 'P-384',
+  'p521': 'P-521'
+};
+if (nodeCrypto && config.use_native) {
   var knownCurves = nodeCrypto.getCurves();
   nodeCurves = {
     'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined,
@@ -63,8 +63,8 @@ const curves = {
     keyType: enums.publicKey.ecdsa,
     hash: enums.hash.sha256,
     cipher: enums.symmetric.aes128,
-    node: nodeCurves.secp256r1,
-    web: webCurves.secp256r1,
+    node: nodeCurves.p256,
+    web: webCurves.p256,
     payloadSize: 32
   },
   p384: {
@@ -72,8 +72,8 @@ const curves = {
     keyType: enums.publicKey.ecdsa,
     hash: enums.hash.sha384,
     cipher: enums.symmetric.aes192,
-    node: nodeCurves.secp384r1,
-    web: webCurves.secp384r1,
+    node: nodeCurves.p384,
+    web: webCurves.p384,
     payloadSize: 48
   },
   p521: {
@@ -81,8 +81,8 @@ const curves = {
     keyType: enums.publicKey.ecdsa,
     hash: enums.hash.sha512,
     cipher: enums.symmetric.aes256,
-    node: nodeCurves.secp521r1,
-    web: webCurves.secp521r1,
+    node: nodeCurves.p521,
+    web: webCurves.p521,
     payloadSize: 66
   },
   secp256k1: {
@@ -99,13 +99,16 @@ const curves = {
   },
   curve25519: {
     oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]),
-    keyType: enums.publicKey.ecdh,
+    keyType: enums.publicKey.ecdsa,
     hash: enums.hash.sha256,
     cipher: enums.symmetric.aes128
   },
   brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7
     oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07])
   },
+  brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11
+    oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B])
+  },
   brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13
     oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D])
   }
@@ -118,12 +121,12 @@ function Curve(name, params) {
       this.curve = new EdDSA(name);
       break;
     case enums.publicKey.ecdsa:
-    case enums.publicKey.ecdh:
       this.curve = new EC(name);
       break;
     default:
       throw new Error('Unknown elliptic key type;');
   }
+  this.name = name;
   this.oid = curves[name].oid;
   this.hash = params.hash;
   this.cipher = params.cipher;
@@ -145,14 +148,14 @@ Curve.prototype.keyFromPublic = function (pub) {
 };
 
 Curve.prototype.genKeyPair = async function () {
-  var r, keyPair;
+  var keyPair;
   if (webCrypto && config.use_native && this.web) {
-    keyPair = await webGenKeyPair(this.name, "ECDSA"); // FIXME is ECDH different?
+    keyPair = await webGenKeyPair(this.name);
   } else if (nodeCrypto && config.use_native && this.node) {
     keyPair = await nodeGenKeyPair(this.name);
   } else {
     var compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
-    r = await this.curve.genKeyPair();
+    var r = await this.curve.genKeyPair();
     if (this.keyType === enums.publicKey.eddsa) {
       keyPair = { secret: r.getSecret() };
     } else {
@@ -162,17 +165,18 @@ Curve.prototype.genKeyPair = async function () {
   return new KeyPair(this.curve, keyPair);
 };
 
-
 function get(oid_or_name) {
   var name;
-  if (enums.curve[oid_or_name]) {
-    name = enums.write(enums.curve, oid_or_name);
+  if (OID.prototype.isPrototypeOf(oid_or_name) &&
+      enums.curve[oid_or_name.toHex()]) {
+    name = enums.write(enums.curve, oid_or_name.toHex()); // by curve OID
+    return new Curve(name, curves[name]);
+  } else if (enums.curve[oid_or_name]) {
+    name = enums.write(enums.curve, oid_or_name); // by curve name
+    return new Curve(name, curves[name]);
+  } else if (enums.curve[util.hexstrdump(oid_or_name)]) {
+    name = enums.write(enums.curve, util.hexstrdump(oid_or_name)); // by oid string
     return new Curve(name, curves[name]);
-  }
-  for (name in curves) {
-    if (curves[name].oid === oid_or_name) {
-      return new Curve(name, curves[name]);
-    }
   }
   throw new Error('Not valid curve');
 }
@@ -189,8 +193,16 @@ async function generate(curve) {
   };
 }
 
+function getPreferredHashAlgorithm(oid) {
+  return curves[enums.write(enums.curve, oid.toHex())].hash;
+}
+
 module.exports = {
   Curve: Curve,
+  curves: curves,
+  webCurves: webCurves,
+  nodeCurves: nodeCurves,
+  getPreferredHashAlgorithm: getPreferredHashAlgorithm,
   generate: generate,
   get: get
 };
@@ -203,14 +215,10 @@ module.exports = {
 //////////////////////////
 
 
-async function webGenKeyPair(name, algorithm) {
+async function webGenKeyPair(name) {
+  // Note: keys generated with ECDSA and ECDH are structurally equivalent
   var webCryptoKey = await webCrypto.generateKey(
-    {
-      name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
-      namedCurve: webCurves[name]
-    },
-    true,
-    algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
+    { name: "ECDSA", namedCurve: webCurves[name] }, true, ["sign", "verify"]
   );
 
   var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey);
@@ -218,15 +226,15 @@ async function webGenKeyPair(name, algorithm) {
 
   return {
     pub: {
-      x: base64.decode(publicKey.x, 'base64url'),
-      y: base64.decode(publicKey.y, 'base64url')
+      x: base64.decode(publicKey.x, true),
+      y: base64.decode(publicKey.y, true)
     },
-    priv: base64.decode(privateKey.d, 'base64url')
+    priv: base64.decode(privateKey.d, true)
   };
 }
 
 async function nodeGenKeyPair(name) {
-  var ecdh = nodeCrypto.createECDH(name === "secp256r1" ? "prime256v1" : name);
+  var ecdh = nodeCrypto.createECDH(nodeCurves[name]);
   await ecdh.generateKeys();
 
   return {
diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js
index 23c60860..0493c55d 100644
--- a/src/crypto/public_key/elliptic/ecdsa.js
+++ b/src/crypto/public_key/elliptic/ecdsa.js
@@ -25,28 +25,9 @@
 
 'use strict';
 
-import BN from 'bn.js';
-import ASN1 from 'asn1.js';
-import jwkToPem from 'jwk-to-pem';
-
+import hash from '../../hash';
 import curves from './curves.js';
 import BigInteger from '../jsbn.js';
-import config from '../../../config';
-import enums from '../../../enums.js';
-import util from '../../../util.js';
-import base64 from '../../../encoding/base64.js';
-
-const webCrypto = util.getWebCrypto();
-const webCurves = curves.webCurves;
-const nodeCrypto = util.getNodeCrypto();
-const nodeCurves = curves.nodeCurves;
-
-var ECDSASignature = ASN1.define('ECDSASignature', function() {
-  this.seq().obj(
-    this.key('r').int(),  // FIXME int or BN?
-    this.key('s').int()   // FIXME int or BN?
-  );
-});
 
 /**
  * Sign a message using the provided key
@@ -57,17 +38,9 @@ var ECDSASignature = ASN1.define('ECDSASignature', function() {
  * @return {{r: BigInteger, s: BigInteger}}  Signature of the message
  */
 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);
-  } else if (nodeCrypto && config.use_native && curve.node) {
-    signature = await nodeSign(curve, hash_algo, m, key.keyPair);
-  } else {
-    signature = await key.sign(m, hash_algo);
-  }
+  const signature = await key.sign(m, hash_algo);
   return {
     r: new BigInteger(signature.r.toArray()),
     s: new BigInteger(signature.s.toArray())
@@ -84,168 +57,14 @@ async function sign(oid, hash_algo, m, d) {
  * @return {Boolean}
  */
 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());
-  } else if (nodeCrypto && config.use_native && curve.node) {
-    result = await nodeVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
-  } else {
-    result = await key.verify(
-      m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo
-    );
-  }
-  return result;
+  return key.verify(
+    m, { r: signature.r.toByteArray(), s: signature.s.toByteArray() }, hash_algo
+  );
 }
 
 module.exports = {
   sign: sign,
   verify: verify
 };
-
-
-//////////////////////////
-//                      //
-//   Helper functions   //
-//                      //
-//////////////////////////
-
-
-async function webSign(curve, hash_algo, message, keyPair) {
-  var l = curve.payloadSize;
-  if (typeof message === 'string') {
-    message = util.str2Uint8Array(message);
-  }
-  const key = await webCrypto.importKey(
-    "jwk",
-    {
-      "kty": "EC",
-      "crv": webCurves[curve.name],
-      "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": webCurves[curve.name],
-      "hash": { name: enums.read(enums.webHash, curve.hash) }
-    },
-    false,
-    ["sign"]
-  );
-
-  const signature = new Uint8Array(await webCrypto.sign(
-    {
-      "name": 'ECDSA',
-      "namedCurve": webCurves[curve.name],
-      "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, message, publicKey) {
-  var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.payloadSize;
-  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": webCurves[curve.name],
-      "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": webCurves[curve.name],
-      "hash": { name: enums.read(enums.webHash, curve.hash) }
-    },
-    false,
-    ["verify"]
-  );
-
-  return webCrypto.verify(
-    {
-      "name": 'ECDSA',
-      "namedCurve": webCurves[curve.name],
-      "hash": { name: enums.read(enums.webHash, hash_algo) }
-    },
-    key,
-    signature,
-    message
-  );
-}
-
-
-async function nodeSign(curve, hash_algo, message, keyPair) {
-  if (typeof message === 'string') {
-    message = util.str2Uint8Array(message);
-  }
-  const key = jwkToPem(
-    {
-      "kty": "EC",
-      "crv": nodeCurves[curve.name],
-      "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
-      "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
-      "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
-      "use": "sig",
-      "kid": "ECDSA Private Key"
-    },
-    { private: true }
-  );
-
-  const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
-  sign.write(message);
-  sign.end();
-  const signature = await ECDSASignature.decode(sign.sign(key), 'der');
-  return {
-    r: signature.r.toArray(),
-    s: signature.s.toArray()
-  };
-}
-
-async function nodeVerify(curve, hash_algo, signature, message, publicKey) {
-  signature = ECDSASignature.encode(
-    {
-      r: new BN(signature.r.toByteArray()),
-      s: new BN(signature.s.toByteArray())
-    },
-    'der');
-  if (typeof message === 'string') {
-    message = util.str2Uint8Array(message);
-  }
-  const key = jwkToPem(
-    {
-      "kty": "EC",
-      "crv": nodeCurves[curve.name],
-      "x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
-      "y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
-      "use": "sig",
-      "kid": "ECDSA Public Key"
-    },
-    { private: false }
-  );
-
-  // FIXME what happens when hash_algo = undefined?
-  const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
-  verify.write(message);
-  verify.end();
-  const result = await verify.verify(key, signature);
-  return result;
-}
diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js
index 3309979e..cccb23dd 100644
--- a/src/crypto/public_key/elliptic/eddsa.js
+++ b/src/crypto/public_key/elliptic/eddsa.js
@@ -2,6 +2,7 @@
 
 'use strict';
 
+import hash from '../../hash';
 import curves from './curves.js';
 import BigInteger from '../jsbn.js';
 
@@ -14,11 +15,9 @@ import BigInteger from '../jsbn.js';
  * @return {{r: BigInteger, s: BigInteger}}  Signature of the message
  */
 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.keyFromSecret(d.toByteArray());
-  signature = await key.sign(m, hash_algo);
+  const signature = await key.sign(m, hash_algo);
   return {
     r: new BigInteger(signature.Rencoded()),
     s: new BigInteger(signature.Sencoded())
@@ -35,12 +34,10 @@ async function sign(oid, hash_algo, m, d) {
  * @return {Boolean}
  */
 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());
   return key.verify(
-    m, {R: signature.r.toByteArray(), S: signature.s.toByteArray()}, hash_algo
+    m, { R: signature.r.toByteArray(), S: signature.s.toByteArray() }, hash_algo
   );
 }
 
diff --git a/src/crypto/public_key/elliptic/index.js b/src/crypto/public_key/elliptic/index.js
index f4a5cc47..a171c3cb 100644
--- a/src/crypto/public_key/elliptic/index.js
+++ b/src/crypto/public_key/elliptic/index.js
@@ -27,7 +27,7 @@
 
 'use strict';
 
-import {get, generate} from './curves';
+import {get, generate, getPreferredHashAlgorithm} from './curves';
 import ecdsa from './ecdsa';
 import eddsa from './eddsa';
 import ecdh from './ecdh';
@@ -37,5 +37,6 @@ module.exports = {
   eddsa: eddsa,
   ecdh: ecdh,
   get: get,
-  generate: generate
+  generate: generate,
+  getPreferredHashAlgorithm: getPreferredHashAlgorithm
 };
diff --git a/src/crypto/public_key/elliptic/key.js b/src/crypto/public_key/elliptic/key.js
index f8e5b0e1..22131762 100644
--- a/src/crypto/public_key/elliptic/key.js
+++ b/src/crypto/public_key/elliptic/key.js
@@ -25,9 +25,28 @@
 
 'use strict';
 
+import BN from 'bn.js';
+import ASN1 from 'asn1.js';
+import jwkToPem from 'jwk-to-pem';
+
+import curves from './curves';
 import hash from '../../hash';
 import util from '../../../util';
 import enums from '../../../enums';
+import config from '../../../config';
+import base64 from '../../../encoding/base64';
+
+const webCrypto = util.getWebCrypto();
+const webCurves = curves.webCurves;
+const nodeCrypto = util.getNodeCrypto();
+const nodeCurves = curves.nodeCurves;
+
+var ECDSASignature = ASN1.define('ECDSASignature', function() {
+  this.seq().obj(
+    this.key('r').int(),
+    this.key('s').int()
+  );
+});
 
 function KeyPair(curve, options) {
   this.curve = curve;
@@ -35,20 +54,32 @@ function KeyPair(curve, options) {
   this.keyPair = this.curve.keyPair(options);
 }
 
-KeyPair.prototype.sign = function (message, hash_algo) {
+KeyPair.prototype.sign = async function (message, hash_algo) {
   if (typeof message === 'string') {
     message = util.str2Uint8Array(message);
   }
-  const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
-  return this.keyPair.sign(digest);
+  if (webCrypto && config.use_native && this.curve.web) {
+    return webSign(this.curve, hash_algo, message, this.keyPair);
+  } else if (nodeCrypto && config.use_native && this.curve.node) {
+    return nodeSign(this.curve, hash_algo, message, this.keyPair);
+  } else {
+    const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
+    return this.keyPair.sign(digest);
+  }
 };
 
-KeyPair.prototype.verify = function (message, signature, hash_algo) {
+KeyPair.prototype.verify = async function (message, signature, hash_algo) {
   if (typeof message === 'string') {
     message = util.str2Uint8Array(message);
   }
-  const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
-  return this.keyPair.verify(digest, signature);
+  if (webCrypto && config.use_native && this.curve.web) {
+    return webVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
+  } else if (nodeCrypto && config.use_native && this.curve.node) {
+    return nodeVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
+  } else {
+    const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
+    return this.keyPair.verify(digest, signature);
+  }
 };
 
 KeyPair.prototype.derive = function (pub) {
@@ -81,3 +112,131 @@ KeyPair.prototype.isValid = function () {
 module.exports = {
   KeyPair: KeyPair
 };
+
+
+//////////////////////////
+//                      //
+//   Helper functions   //
+//                      //
+//////////////////////////
+
+
+async function webSign(curve, hash_algo, message, keyPair) {
+  var l = curve.payloadSize;
+  const key = await webCrypto.importKey(
+    "jwk",
+    {
+      "kty": "EC",
+      "crv": webCurves[curve.name],
+      "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), true),
+      "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), true),
+      "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), true),
+      "use": "sig",
+      "kid": "ECDSA Private Key"
+    },
+    {
+      "name": "ECDSA",
+      "namedCurve": webCurves[curve.name],
+      "hash": { name: enums.read(enums.webHash, curve.hash) }
+    },
+    false,
+    ["sign"]
+  );
+
+  const signature = new Uint8Array(await webCrypto.sign(
+    {
+      "name": 'ECDSA',
+      "namedCurve": webCurves[curve.name],
+      "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, {r, s}, message, publicKey) {
+  var l = curve.payloadSize;
+  r = (r.length === l) ? r : [0].concat(r);
+  s = (s.length === l) ? s : [0].concat(s);
+  var signature = new Uint8Array(r.concat(s)).buffer;
+  const key = await webCrypto.importKey(
+    "jwk",
+    {
+      "kty": "EC",
+      "crv": webCurves[curve.name],
+      "x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), true),
+      "y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), true),
+      "use": "sig",
+      "kid": "ECDSA Public Key"
+    },
+    {
+      "name": "ECDSA",
+      "namedCurve": webCurves[curve.name],
+      "hash": { name: enums.read(enums.webHash, curve.hash) }
+    },
+    false,
+    ["verify"]
+  );
+
+  return webCrypto.verify(
+    {
+      "name": 'ECDSA',
+      "namedCurve": webCurves[curve.name],
+      "hash": { name: enums.read(enums.webHash, hash_algo) }
+    },
+    key,
+    signature,
+    message
+  );
+}
+
+
+async function nodeSign(curve, hash_algo, message, keyPair) {
+  const key = jwkToPem(
+    {
+      "kty": "EC",
+      "crv": webCurves[curve.name],
+      "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
+      "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
+      "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
+      "use": "sig",
+      "kid": "ECDSA Private Key"
+    },
+    { private: true }
+  );
+
+  const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
+  sign.write(message);
+  sign.end();
+  const signature = await ECDSASignature.decode(sign.sign(key), 'der');
+  return {
+    r: signature.r.toArray(),
+    s: signature.s.toArray()
+  };
+}
+
+async function nodeVerify(curve, hash_algo, {r, s}, message, publicKey) {
+  var signature = ECDSASignature.encode({ r: new BN(r), s: new BN(s) }, 'der');
+  const key = jwkToPem(
+    {
+      "kty": "EC",
+      "crv": webCurves[curve.name],
+      "x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
+      "y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
+      "use": "sig",
+      "kid": "ECDSA Public Key"
+    },
+    { private: false }
+  );
+
+  // FIXME what happens when hash_algo = undefined?
+  const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
+  verify.write(message);
+  verify.end();
+  const result = await verify.verify(key, signature);
+  return result;
+}
diff --git a/src/encoding/base64.js b/src/encoding/base64.js
index 403a73d5..e4d22cfe 100644
--- a/src/encoding/base64.js
+++ b/src/encoding/base64.js
@@ -17,20 +17,21 @@
 
 'use strict';
 
-var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
+var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64
+var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64
 
 /**
  * Convert binary array to radix-64
  * @param {Uint8Array} t Uint8Array to convert
+ * @param {bool} u if true, output is URL-safe
  * @returns {string} radix-64 version of input string
  * @static
  */
-function s2r(t, o, u) {
+function s2r(t, u = false) {
   // TODO check btoa alternative
-  var b64 = (u === "base64url") ? b64u : b64s;
+  var b64 = u ? b64u : b64s;
   var a, c, n;
-  var r = o ? o : [],
+  var r = [],
       l = 0,
       s = 0;
   var tl = t.length;
@@ -67,33 +68,30 @@ function s2r(t, o, u) {
     if ((l % 60) === 0 && !u) {
       r.push("\n");
     }
-    if (u !== 'base64url') {
+    if (!u) {
       r.push('=');
       l += 1;
     }
   }
-  if (s === 1 && u !== 'base64url') {
+  if (s === 1 && !u) {
     if ((l % 60) === 0 && !u) {
       r.push("\n");
     }
     r.push('=');
   }
-  if (o)
-  {
-    return;
-  }
   return r.join('');
 }
 
 /**
  * Convert radix-64 to binary array
  * @param {String} t radix-64 string to convert
+ * @param {bool} u if true, input is interpreted as URL-safe
  * @returns {Uint8Array} binary array version of input string
  * @static
  */
 function r2s(t, u) {
   // TODO check atob alternative
-  var b64 = (u === "base64url") ? b64u : b64s;
+  var b64 = u ? b64u : b64s;
   var c, n;
   var r = [],
     s = 0,
diff --git a/src/enums.js b/src/enums.js
index c4bf3651..66929a01 100644
--- a/src/enums.js
+++ b/src/enums.js
@@ -16,25 +16,40 @@ export default {
     "secp256r1":           "p256",
     "prime256v1":          "p256",
     "1.2.840.10045.3.1.7": "p256",
+    "2a8648ce3d030107":  "p256",
+    "2A8648CE3D030107":  "p256",
 
     "p384":         "p384",
     "P-384":        "p384",
     "secp384r1":    "p384",
     "1.3.132.0.34": "p384",
+    "2b81040022": "p384",
+    "2B81040022": "p384",
 
     "p521":         "p521",
     "P-521":        "p521",
     "secp521r1":    "p521",
     "1.3.132.0.35": "p521",
+    "2b81040023": "p521",
+    "2B81040023": "p521",
 
     "secp256k1":    "secp256k1",
     "1.3.132.0.10": "secp256k1",
+    "2b8104000a": "secp256k1",
+    "2B8104000A": "secp256k1",
 
     "ed25519":                "ed25519",
+    "Ed25519":                "ed25519",
     "1.3.6.1.4.1.11591.15.1": "ed25519",
+    "2b06010401da470f01":   "ed25519",
+    "2B06010401DA470F01":   "ed25519",
 
+    "cv25519":                "curve25519",
     "curve25519":             "curve25519",
-    "1.3.6.1.4.1.3029.1.5.1": "curve25519"
+    "Curve25519":             "curve25519",
+    "1.3.6.1.4.1.3029.1.5.1": "curve25519",
+    "2b060104019755010501":   "curve25519",
+    "2B060104019755010501":   "curve25519"
   },
 
   /** A string to key specifier type
diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js
index 7f096fba..752cc1b5 100644
--- a/src/packet/packetlist.js
+++ b/src/packet/packetlist.js
@@ -145,7 +145,7 @@ Packetlist.prototype.filterByTag = function () {
 */
 Packetlist.prototype.forEach = function (callback) {
   for (var i = 0; i < this.length; i++) {
-    callback(this[i]);
+    callback(this[i], i, this);
   }
 };
 
@@ -163,6 +163,20 @@ Packetlist.prototype.map = function (callback) {
   return packetArray;
 };
 
+/**
+* Executes the callback function once for each element
+* until it finds one where callback returns a truthy value
+*/
+Packetlist.prototype.some = async function (callback) {
+  for (var i = 0; i < this.length; i++) {
+    // eslint-disable-next-line no-await-in-loop
+    if (await callback(this[i], i, this)) {
+      return true;
+    }
+  }
+  return false;
+};
+
 /**
  * Traverses packet tree and returns first matching packet
  * @param  {module:enums.packet} type The packet type
diff --git a/src/type/oid.js b/src/type/oid.js
index e6cbad0b..be633321 100644
--- a/src/type/oid.js
+++ b/src/type/oid.js
@@ -68,6 +68,14 @@ OID.prototype.write = function () {
     String.fromCharCode(this.oid.length)+this.oid);
 };
 
+/**
+ * Serialize an OID object as a hex string
+ * @return {string} String with the hex value of the OID
+ */
+OID.prototype.toHex = function() {
+  return util.hexstrdump(this.oid);
+};
+
 OID.fromClone = function (clone) {
   var oid = new OID(clone.oid);
   return oid;
diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js
index 6a7b63a5..48a84fc5 100644
--- a/test/crypto/elliptic.js
+++ b/test/crypto/elliptic.js
@@ -175,22 +175,22 @@ describe('Elliptic Curve Cryptography', function () {
     it('Signature verification', function (done) {
       var curve = elliptic_curves.get('p256');
       var key = curve.keyFromPublic(signature_data.pub);
-      expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.true;
+      expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.true;
       done();
     });
     it('Invalid signature', function (done) {
       var curve = elliptic_curves.get('p256');
       var key = curve.keyFromPublic(key_data.p256.pub);
-      expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.false;
+      expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.false;
       done();
     });
-    it('Signature generation', function (done) {
+    it('Signature generation', function () {
       var curve = elliptic_curves.get('p256');
       var key = curve.keyFromPrivate(key_data.p256.priv);
-      var signature = key.sign(signature_data.message, 8);
-      key = curve.keyFromPublic(key_data.p256.pub);
-      expect(key.verify(signature_data.message, signature, 8)).to.be.true;
-      done();
+      return key.sign(signature_data.message, 8).then(signature => {
+        key = curve.keyFromPublic(key_data.p256.pub);
+        expect(key.verify(signature_data.message, signature, 8)).to.eventually.be.true;
+      });
     });
     it('Shared secret generation', function (done) {
       var curve = elliptic_curves.get('p256');
diff --git a/test/general/ecc.js b/test/general/ecc.js
index 92bb7c52..0213b349 100644
--- a/test/general/ecc.js
+++ b/test/general/ecc.js
@@ -200,7 +200,7 @@ describe('Elliptic Curve Cryptography', function () {
     var romeo = load_priv_key('romeo');
     var juliet = load_pub_key('juliet');
     expect(romeo.decrypt(data['romeo'].pass)).to.be.true;
-    openpgp.encrypt(
+    return openpgp.encrypt(
       {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"}
     ).then(function (encrypted) {
       var message = openpgp.message.readArmored(encrypted.data);