Add Diffie-Hellman support to crypto module
authorHåvard Stranden <havard.stranden@gmail.com>
Wed, 19 Jan 2011 01:00:38 +0000 (02:00 +0100)
committerRyan Dahl <ry@tinyclouds.org>
Fri, 6 May 2011 21:36:04 +0000 (14:36 -0700)
Fixes #573

doc/api/crypto.markdown
lib/crypto.js
src/node_crypto.cc
test/simple/test-crypto.js

index 78284b3..91fc3ea 100644 (file)
@@ -146,3 +146,58 @@ the PEM encoded certificate, and `signature`, which is the previously calculates
 signature for the data, in the `signature_format` which can be `'binary'`, `'hex'` or `'base64'`.
 
 Returns true or false depending on the validity of the signature for the data and public key.
+
+### crypto.createDiffieHellman(prime_length)
+
+Creates a Diffie-Hellman key exchange object and generates a prime of the
+given bit length. The generator used is `2`.
+
+### crypto.createDiffieHellman(prime, encoding='binary')
+
+Creates a Diffie-Hellman key exchange object using the supplied prime. The
+generator used is `2`. Encoding can be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.generateKeys(encoding='binary')
+
+Generates private and public Diffie-Hellman key values, and returns the
+public key in the specified encoding. This key should be transferred to the
+other party. Encoding can be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.computeSecret(other_public_key, input_encoding='binary', output_encoding=input_encoding)
+
+Computes the shared secret using `other_public_key` as the other party's
+public key and returns the computed shared secret. Supplied key is
+interpreted using specified `input_encoding`, and secret is encoded using
+specified `output_encoding`. Encodings can be `'binary'`, `'hex'`, or
+`'base64'`. If no output encoding is given, the input encoding is used as
+output encoding.
+
+### diffieHellman.getPrime(encoding='binary')
+
+Returns the Diffie-Hellman prime in the specified encoding, which can be
+`'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getGenerator(encoding='binary')
+
+Returns the Diffie-Hellman prime in the specified encoding, which can be
+`'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getPublicKey(encoding='binary')
+
+Returns the Diffie-Hellman public key in the specified encoding, which can
+be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getPrivateKey(encoding='binary')
+
+Returns the Diffie-Hellman private key in the specified encoding, which can
+be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.setPublicKey(public_key, encoding='binary')
+
+Sets the Diffie-Hellman public key. Key encoding can be `'binary'`, `'hex'`,
+or `'base64'`.
+
+### diffieHellman.setPrivateKey(public_key, encoding='binary')
+
+Sets the Diffie-Hellman private key. Key encoding can be `'binary'`, `'hex'`, or `'base64'`.
+
index 6931fbc..14d26a4 100644 (file)
@@ -29,6 +29,7 @@ try {
   var Decipher = binding.Decipher;
   var Sign = binding.Sign;
   var Verify = binding.Verify;
+  var DiffieHellman = binding.DiffieHellman;
   var crypto = true;
 } catch (e) {
 
@@ -139,3 +140,15 @@ exports.Verify = Verify;
 exports.createVerify = function(algorithm) {
   return (new Verify).init(algorithm);
 };
+
+exports.DiffieHellman = DiffieHellman;
+exports.createDiffieHellman = function(size_or_key, enc) {
+  if (!size_or_key) {
+    return new DiffieHellman();
+  } else if (!enc) {
+    return new DiffieHellman(size_or_key);
+  } else {
+    return new DiffieHellman(size_or_key, enc);
+  }
+
+}
index 2d82ff0..c202678 100644 (file)
@@ -3053,7 +3053,517 @@ class Verify : public ObjectWrap {
 
 };
 
+class DiffieHellman : public ObjectWrap {
+ public:
+  static void Initialize(v8::Handle<v8::Object> target) {
+    HandleScope scope;
+
+    Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+    t->InstanceTemplate()->SetInternalFieldCount(1);
+
+    NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
+    NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
+    NODE_SET_PROTOTYPE_METHOD(t, "getPrime", GetPrime);
+    NODE_SET_PROTOTYPE_METHOD(t, "getGenerator", GetGenerator);
+    NODE_SET_PROTOTYPE_METHOD(t, "getPublicKey", GetPublicKey);
+    NODE_SET_PROTOTYPE_METHOD(t, "getPrivateKey", GetPrivateKey);
+    NODE_SET_PROTOTYPE_METHOD(t, "setPublicKey", SetPublicKey);
+    NODE_SET_PROTOTYPE_METHOD(t, "setPrivateKey", SetPrivateKey);
+
+    target->Set(String::NewSymbol("DiffieHellman"), t->GetFunction());
+  }
+
+  bool Init(int primeLength) {
+    dh = DH_new();
+    DH_generate_parameters_ex(dh, primeLength, DH_GENERATOR_2, 0);
+    bool result = VerifyContext();
+    if (!result) return false;
+    initialised_ = true;
+    return true;
+  }
+
+  bool Init(unsigned char* p, int p_len) {
+    dh = DH_new();
+    dh->p = BN_bin2bn(p, p_len, 0);
+    dh->g = BN_new();
+    if (!BN_set_word(dh->g, 2)) return false;
+    bool result = VerifyContext();
+    if (!result) return false;
+    initialised_ = true;
+    return true;
+  }
+
+ protected:
+  static Handle<Value> New(const Arguments& args) {
+    HandleScope scope;
+
+    DiffieHellman* diffieHellman = new DiffieHellman();
+    bool initialized = false;
+
+    if (args.Length() > 0) {
+      if (args[0]->IsInt32()) {
+        diffieHellman->Init(args[0]->Int32Value());
+        initialized = true;
+      } else {
+        if (args[0]->IsString()) {
+          char* buf;
+          int len;
+          if (args.Length() > 1 && args[1]->IsString()) {
+            len = DecodeWithEncoding(args[0], args[1], &buf);
+          } else {
+            len = DecodeBinary(args[0], &buf);
+          }
+
+          if (len == -1) {
+            delete[] buf;
+            return ThrowException(Exception::Error(
+                  String::New("Invalid argument")));
+          } else {
+            diffieHellman->Init(reinterpret_cast<unsigned char*>(buf), len);
+            delete[] buf;
+            initialized = true;
+          }
+        } else if (Buffer::HasInstance(args[0])) {
+          Local<Object> buffer = args[0]->ToObject();
+          diffieHellman->Init(
+                  reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+                  Buffer::Length(buffer));
+          initialized = true;
+        }
+      }
+    }
+
+    if (!initialized) {
+      return ThrowException(Exception::Error(
+            String::New("Initialization failed")));
+    }
+
+    diffieHellman->Wrap(args.This());
+
+    return args.This();
+  }
+
+  static Handle<Value> GenerateKeys(const Arguments& args) {
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    HandleScope scope;
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(
+            String::New("Not initialized")));
+    }
+
+    if (!DH_generate_key(diffieHellman->dh)) {
+      return ThrowException(Exception::Error(
+            String::New("Key generation failed")));
+    }
+
+    Local<Value> outString;
+
+    int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
+    char* data = new char[dataSize];
+    BN_bn2bin(diffieHellman->dh->pub_key,
+        reinterpret_cast<unsigned char*>(data));
+
+    if (args.Length() > 0 && args[0]->IsString()) {
+      outString = EncodeWithEncoding(args[0], data, dataSize);
+    } else {
+      outString = Encode(data, dataSize, BINARY);
+    }
+    delete[] data;
+
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> GetPrime(const Arguments& args) {
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    HandleScope scope;
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    int dataSize = BN_num_bytes(diffieHellman->dh->p);
+    char* data = new char[dataSize];
+    BN_bn2bin(diffieHellman->dh->p, reinterpret_cast<unsigned char*>(data));
+
+    Local<Value> outString;
+
+    if (args.Length() > 0 && args[0]->IsString()) {
+      outString = EncodeWithEncoding(args[0], data, dataSize);
+    } else {
+      outString = Encode(data, dataSize, BINARY);
+    }
+
+    delete[] data;
+
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> GetGenerator(const Arguments& args) {
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    HandleScope scope;
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    int dataSize = BN_num_bytes(diffieHellman->dh->g);
+    char* data = new char[dataSize];
+    BN_bn2bin(diffieHellman->dh->g, reinterpret_cast<unsigned char*>(data));
+
+    Local<Value> outString;
+
+    if (args.Length() > 0 && args[0]->IsString()) {
+      outString = EncodeWithEncoding(args[0], data, dataSize);
+    } else {
+      outString = Encode(data, dataSize, BINARY);
+    }
+
+    delete[] data;
+
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> GetPublicKey(const Arguments& args) {
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    HandleScope scope;
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    if (diffieHellman->dh->pub_key == NULL) {
+      return ThrowException(Exception::Error(
+            String::New("No public key - did you forget to generate one?")));
+    }
+
+    int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
+    char* data = new char[dataSize];
+    BN_bn2bin(diffieHellman->dh->pub_key,
+        reinterpret_cast<unsigned char*>(data));
+
+    Local<Value> outString;
+
+    if (args.Length() > 0 && args[0]->IsString()) {
+      outString = EncodeWithEncoding(args[0], data, dataSize);
+    } else {
+      outString = Encode(data, dataSize, BINARY);
+    }
+
+    delete[] data;
+
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> GetPrivateKey(const Arguments& args) {
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    HandleScope scope;
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    if (diffieHellman->dh->priv_key == NULL) {
+      return ThrowException(Exception::Error(
+            String::New("No private key - did you forget to generate one?")));
+    }
 
+    int dataSize = BN_num_bytes(diffieHellman->dh->priv_key);
+    char* data = new char[dataSize];
+    BN_bn2bin(diffieHellman->dh->priv_key,
+        reinterpret_cast<unsigned char*>(data));
+
+    Local<Value> outString;
+
+    if (args.Length() > 0 && args[0]->IsString()) {
+      outString = EncodeWithEncoding(args[0], data, dataSize);
+    } else {
+      outString = Encode(data, dataSize, BINARY);
+    }
+
+    delete[] data;
+
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> ComputeSecret(const Arguments& args) {
+    HandleScope scope;
+
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    BIGNUM* key = 0;
+
+    if (args.Length() == 0) {
+      return ThrowException(Exception::Error(
+            String::New("First argument must be other party's public key")));
+    } else {
+      if (args[0]->IsString()) {
+        char* buf;
+        int len;
+        if (args.Length() > 1) {
+          len = DecodeWithEncoding(args[0], args[1], &buf);
+        } else {
+          len = DecodeBinary(args[0], &buf);
+        }
+        if (len == -1) {
+          delete[] buf;
+          return ThrowException(Exception::Error(
+                String::New("Invalid argument")));
+        }
+        key = BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+        delete[] buf;
+      } else if (Buffer::HasInstance(args[0])) {
+        Local<Object> buffer = args[0]->ToObject();
+        key = BN_bin2bn(
+          reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+          Buffer::Length(buffer), 0);
+      } else {
+        return ThrowException(Exception::Error(
+              String::New("First argument must be other party's public key")));
+      }
+    }
+
+    int dataSize = DH_size(diffieHellman->dh);
+    char* data = new char[dataSize];
+
+    int size = DH_compute_key(reinterpret_cast<unsigned char*>(data),
+      key, diffieHellman->dh);
+    BN_free(key);
+
+    Local<Value> outString;
+
+    if (size == -1) {
+      int checkResult;
+      if (!DH_check_pub_key(diffieHellman->dh, key, &checkResult)) {
+        return ThrowException(Exception::Error(String::New("Invalid key")));
+      } else if (checkResult) {
+        if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
+          return ThrowException(Exception::Error(
+                String::New("Supplied key is too small")));
+        } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
+          return ThrowException(Exception::Error(
+                String::New("Supplied key is too large")));
+        } else {
+          return ThrowException(Exception::Error(String::New("Invalid key")));
+        }
+      } else {
+        return ThrowException(Exception::Error(String::New("Invalid key")));
+      }
+    } else {
+      if (args.Length() > 2 && args[2]->IsString()) {
+        outString = EncodeWithEncoding(args[2], data, dataSize);
+      } else if (args.Length() > 1 && args[1]->IsString()) {
+        outString = EncodeWithEncoding(args[1], data, dataSize);
+      } else {
+        outString = Encode(data, dataSize, BINARY);
+      }
+    }
+
+    delete[] data;
+    return scope.Close(outString);
+  }
+
+  static Handle<Value> SetPublicKey(const Arguments& args) {
+    HandleScope scope;
+
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(String::New("Not initialized")));
+    }
+
+    if (args.Length() == 0) {
+      return ThrowException(Exception::Error(
+            String::New("First argument must be public key")));
+    } else {
+      if (args[0]->IsString()) {
+        char* buf;
+        int len;
+        if (args.Length() > 1) {
+          len = DecodeWithEncoding(args[0], args[1], &buf);
+        } else {
+          len = DecodeBinary(args[0], &buf);
+        }
+        if (len == -1) {
+          delete[] buf;
+          return ThrowException(Exception::Error(
+                String::New("Invalid argument")));
+        }
+        diffieHellman->dh->pub_key =
+          BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+        delete[] buf;
+      } else if (Buffer::HasInstance(args[0])) {
+        Local<Object> buffer = args[0]->ToObject();
+        diffieHellman->dh->pub_key =
+          BN_bin2bn(
+            reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+            Buffer::Length(buffer), 0);
+      } else {
+        return ThrowException(Exception::Error(
+              String::New("First argument must be public key")));
+      }
+    }
+
+    return args.This();
+  }
+
+  static Handle<Value> SetPrivateKey(const Arguments& args) {
+    HandleScope scope;
+
+    DiffieHellman* diffieHellman =
+      ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+    if (!diffieHellman->initialised_) {
+      return ThrowException(Exception::Error(
+            String::New("Not initialized")));
+    }
+
+    if (args.Length() == 0) {
+      return ThrowException(Exception::Error(
+            String::New("First argument must be private key")));
+    } else {
+      if (args[0]->IsString()) {
+        char* buf;
+        int len;
+        if (args.Length() > 1) {
+          len = DecodeWithEncoding(args[0], args[1], &buf);
+        } else {
+          len = DecodeBinary(args[0], &buf);
+        }
+        if (len == -1) {
+          delete[] buf;
+          return ThrowException(Exception::Error(
+                String::New("Invalid argument")));
+        }
+        diffieHellman->dh->priv_key =
+          BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+        delete[] buf;
+      } else if (Buffer::HasInstance(args[0])) {
+        Local<Object> buffer = args[0]->ToObject();
+        diffieHellman->dh->priv_key =
+          BN_bin2bn(
+            reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+            Buffer::Length(buffer), 0);
+      } else {
+        return ThrowException(Exception::Error(
+              String::New("First argument must be private key")));
+      }
+    }
+
+    return args.This();
+  }
+
+  DiffieHellman() : ObjectWrap() {
+    initialised_ = false;
+    dh = NULL;
+  }
+
+  ~DiffieHellman() {
+    if (dh != NULL) {
+      DH_free(dh);
+    }
+  }
+
+ private:
+  bool VerifyContext() {
+    int codes;
+    if (!DH_check(dh, &codes)) return false;
+    if (codes & DH_CHECK_P_NOT_SAFE_PRIME) return false;
+    if (codes & DH_CHECK_P_NOT_PRIME) return false;
+    if (codes & DH_UNABLE_TO_CHECK_GENERATOR) return false;
+    if (codes & DH_NOT_SUITABLE_GENERATOR) return false;
+    return true;
+  }
+
+  static int DecodeBinary(Handle<Value> str, char** buf) {
+    int len = DecodeBytes(str);
+    *buf = new char[len];
+    int written = DecodeWrite(*buf, len, str, BINARY);
+    if (written != len) {
+      return -1;
+    }
+    return len;
+  }
+
+  static int DecodeWithEncoding(Handle<Value> str, Handle<Value> enc,
+      char** buf) {
+    int len = DecodeBinary(str, buf);
+    if (len == -1) {
+      return len;
+    }
+    String::Utf8Value encoding(enc->ToString());
+    char* retbuf = 0;
+    int retlen;
+
+    if (strcasecmp(*encoding, "hex") == 0) {
+      HexDecode((unsigned char*)*buf, len, &retbuf, &retlen);
+
+    } else if (strcasecmp(*encoding, "base64") == 0) {
+      unbase64((unsigned char*)*buf, len, &retbuf, &retlen);
+
+    } else if (strcasecmp(*encoding, "binary") == 0) {
+      // Binary - do nothing
+    } else {
+      fprintf(stderr, "node-crypto : Diffie-Hellman parameter encoding "
+                      "can be binary, hex or base64\n");
+    }
+
+    if (retbuf != 0) {
+      delete [] *buf;
+      *buf = retbuf;
+      len = retlen;
+    }
+
+    return len;
+  }
+
+  static Local<Value> EncodeWithEncoding(Handle<Value> enc, char* buf,
+      int len) {
+    HandleScope scope;
+
+    Local<Value> outString;
+    String::Utf8Value encoding(enc->ToString());
+    char* retbuf;
+    int retlen;
+    if (strcasecmp(*encoding, "hex") == 0) {
+      // Hex encoding
+      HexEncode(reinterpret_cast<unsigned char*>(buf), len, &retbuf, &retlen);
+      outString = Encode(retbuf, retlen, BINARY);
+      delete [] retbuf;
+    } else if (strcasecmp(*encoding, "base64") == 0) {
+      base64(reinterpret_cast<unsigned char*>(buf), len, &retbuf, &retlen);
+      outString = Encode(retbuf, retlen, BINARY);
+      delete [] retbuf;
+    } else if (strcasecmp(*encoding, "binary") == 0) {
+      outString = Encode(buf, len, BINARY);
+    } else {
+      fprintf(stderr, "node-crypto : Diffie-Hellman parameter encoding "
+                      "can be binary, hex or base64\n");
+    }
+
+    return scope.Close(outString);
+  }
+
+  bool initialised_;
+  DH* dh;
+};
 
 
 
@@ -3070,6 +3580,7 @@ void InitCrypto(Handle<Object> target) {
   Connection::Initialize(target);
   Cipher::Initialize(target);
   Decipher::Initialize(target);
+  DiffieHellman::Initialize(target);
   Hmac::Initialize(target);
   Hash::Initialize(target);
   Sign::Initialize(target);
index 647ba20..aefdc23 100644 (file)
@@ -144,3 +144,31 @@ assert.equal(txt, plaintext, 'encryption and decryption with key and iv');
 assert.throws(function() {
   crypto.createHash('sha1').update({foo: 'bar'});
 }, /string or buffer/);
+
+// Test Diffie-Hellman with two parties sharing a secret,
+// using various encodings as we go along
+var dh1 = crypto.createDiffieHellman(256);
+var p1 = dh1.getPrime('base64');
+var dh2 = crypto.createDiffieHellman(p1, 'base64');
+var key1 = dh1.generateKeys();
+var key2 = dh2.generateKeys('hex');
+var secret1 = dh1.computeSecret(key2, 'hex', 'base64');
+var secret2 = dh2.computeSecret(key1, 'binary', 'base64');
+
+assert.equal(secret1, secret2);
+
+// Create "another dh1" using generated keys from dh1,
+// and compute secret again
+var dh3 = crypto.createDiffieHellman(p1, 'base64');
+var privkey1 = dh1.getPrivateKey();
+dh3.setPublicKey(key1);
+dh3.setPrivateKey(privkey1);
+
+assert.equal(dh1.getPrime(), dh3.getPrime());
+assert.equal(dh1.getGenerator(), dh3.getGenerator());
+assert.equal(dh1.getPublicKey(), dh3.getPublicKey());
+assert.equal(dh1.getPrivateKey(), dh3.getPrivateKey());
+
+var secret3 = dh3.computeSecret(key2, 'hex', 'base64');
+
+assert.equal(secret1, secret3);