CVE-2020-28498: Use of a Broken or Risky Cryptographic Algorithm (CWE-327)
value: The package elliptic before 6.5.4 are vulnerable to Cryptographic Issues via the secp256k1 implementation in elliptic/ec/key.js. There is no check to confirm that the public key point passed into the derive function actually exists on the secp256k1 curve. This results in the potential for the private key used in this implementation to be revealed after a number of ECDH operations are performed.
CWE description: The product uses a broken or risky cryptographic algorithm or protocol.
CVSS v3 base score: 6.8
Published: 2021-02-02T19:15Z
Repository: https://github.com/indutny/elliptic
Language: JavaScript
Files changed: lib/elliptic/ec/key.js, test/ecdh-test.js
Commit date: 2021-02-02 10:19:43 -0800
Commit message: ec: validate that a point before deriving keys

This update checks to make sure that the public key passed in to
ECDH is a point that actually exists on the curve. This is
important to prevent a twist attack that can be used to reveal
the private key of a party in an ECDH operation over a number of
occurances.

For more details on the attack see this blog post:
https://github.com/christianlundkvist/blog/blob/master/2020_05_26_secp256k1_twist_attacks/secp256k1_twist_attacks.md

CVE: CVE-2020-28498

Diff: diff --git a/lib/elliptic/ec/key.js b/lib/elliptic/ec/key.js
index 8aa7451..55bf299 100644
--- a/lib/elliptic/ec/key.js
+++ b/lib/elliptic/ec/key.js
@@ -98,10 +98,13 @@ KeyPair.prototype._importPublic = function _importPublic(key, enc) {
   this.pub = this.ec.curve.decodePoint(key, enc);
 };
 
 // ECDH
 KeyPair.prototype.derive = function derive(pub) {
+  if(!pub.validate()) {
+    assert(pub.validate(), 'public point not validated');
+  }
   return pub.mul(this.priv).getX();
 };
 
 // ECDSA
 KeyPair.prototype.sign = function sign(msg, enc, options) {
diff --git a/test/ecdh-test.js b/test/ecdh-test.js
index aa28b75..47543b6 100644
--- a/test/ecdh-test.js
+++ b/test/ecdh-test.js
@@ -25,5 +25,19 @@ describe('ECDH', function() {
 
   test('curve25519');
   test('ed25519');
   test('secp256k1');
 });
+
+describe('ECDH twist attack', () => {
+  it('should be able to prevent a twist attack for secp256k1', () => {
+    var bobEcdh = new elliptic.ec('secp256k1');
+    var malloryEcdh = new elliptic.ec('secp256k1');
+    var bob = bobEcdh.genKeyPair();
+    // This is a bad point that shouldn't be able to be passed to derive.
+    // If a bad point can be passed it's possible to perform a twist attack.
+    var mallory = malloryEcdh.keyFromPublic({ x: 14, y: 16 });
+    assert.throws(function () {
+      bob.derive(mallory.getPublic());
+    });
+  });
+});

Fixed code:   if(!pub.validate()) {
    assert(pub.validate(), 'public point not validated');
  }

describe('ECDH twist attack', () => {
  it('should be able to prevent a twist attack for secp256k1', () => {
    var bobEcdh = new elliptic.ec('secp256k1');
    var malloryEcdh = new elliptic.ec('secp256k1');
    var bob = bobEcdh.genKeyPair();
    // This is a bad point that shouldn't be able to be passed to derive.
    // If a bad point can be passed it's possible to perform a twist attack.
    var mallory = malloryEcdh.keyFromPublic({ x: 14, y: 16 });
    assert.throws(function () {
      bob.derive(mallory.getPublic());
    });
  });
});