summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFedor Indutny <fedor@indutny.com>2014-08-27 18:01:01 +0400
committerFedor Indutny <fedor@indutny.com>2014-08-29 00:27:09 +0400
commit6e453fad87c51dc15327628aa75886d3fbb3fa1c (patch)
treea750ea46af04a3107132a3e21eb6047675a15178
parentf7d6147e43b8a80a0d627f2034271239db500d9f (diff)
downloadandroid-node-v8-6e453fad87c51dc15327628aa75886d3fbb3fa1c.tar.gz
android-node-v8-6e453fad87c51dc15327628aa75886d3fbb3fa1c.tar.bz2
android-node-v8-6e453fad87c51dc15327628aa75886d3fbb3fa1c.zip
crypto: introduce ECDH
-rw-r--r--doc/api/crypto.markdown79
-rw-r--r--lib/crypto.js47
-rw-r--r--src/node_constants.cc8
-rw-r--r--src/node_crypto.cc219
-rw-r--r--src/node_crypto.h38
-rw-r--r--test/simple/test-crypto.js35
6 files changed, 426 insertions, 0 deletions
diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown
index cc13acacc3..a414d9b922 100644
--- a/doc/api/crypto.markdown
+++ b/doc/api/crypto.markdown
@@ -517,6 +517,85 @@ Example (obtaining a shared secret):
/* alice_secret and bob_secret should be the same */
console.log(alice_secret == bob_secret);
+## crypto.createECDH(curve_name)
+
+Creates a Elliptic Curve (EC) Diffie-Hellman key exchange object using a
+predefined curve specified by `curve_name` string.
+
+## Class: ECDH
+
+The class for creating EC Diffie-Hellman key exchanges.
+
+Returned by `crypto.createECDH`.
+
+### ECDH.generateKeys([encoding[, format]])
+
+Generates private and public EC Diffie-Hellman key values, and returns
+the public key in the specified format and encoding. This key should be
+transferred to the other party.
+
+Format specifies point encoding and can be `'compressed'`, `'uncompressed'`, or
+`'hybrid'`. If no format is provided - the point will be returned in
+`'uncompressed'` format.
+
+Encoding can be `'binary'`, `'hex'`, or `'base64'`. If no encoding is provided,
+then a buffer is returned.
+
+### ECDH.computeSecret(other_public_key, [input_encoding], [output_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 the input encoding is not
+provided, then a buffer is expected.
+
+If no output encoding is given, then a buffer is returned.
+
+### ECDH.getPublicKey([encoding[, format]])
+
+Returns the EC Diffie-Hellman public key in the specified encoding and format.
+
+Format specifies point encoding and can be `'compressed'`, `'uncompressed'`, or
+`'hybrid'`. If no format is provided - the point will be returned in
+`'uncompressed'` format.
+
+Encoding can be `'binary'`, `'hex'`, or `'base64'`. If no encoding is provided,
+then a buffer is returned.
+
+### ECDH.getPrivateKey([encoding])
+
+Returns the EC Diffie-Hellman private key in the specified encoding,
+which can be `'binary'`, `'hex'`, or `'base64'`. If no encoding is
+provided, then a buffer is returned.
+
+### ECDH.setPublicKey(public_key, [encoding])
+
+Sets the EC Diffie-Hellman public key. Key encoding can be `'binary'`,
+`'hex'` or `'base64'`. If no encoding is provided, then a buffer is
+expected.
+
+### ECDH.setPrivateKey(private_key, [encoding])
+
+Sets the EC Diffie-Hellman private key. Key encoding can be `'binary'`,
+`'hex'` or `'base64'`. If no encoding is provided, then a buffer is
+expected.
+
+Example (obtaining a shared secret):
+
+ var crypto = require('crypto');
+ var alice = crypto.createECDH('secp256k1');
+ var bob = crypto.createECDH('secp256k1');
+
+ alice.generateKeys();
+ bob.generateKeys();
+
+ var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');
+ var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');
+
+ /* alice_secret and bob_secret should be the same */
+ console.log(alice_secret == bob_secret);
+
## crypto.pbkdf2(password, salt, iterations, keylen, [digest], callback)
Asynchronous PBKDF2 function. Applies the selected HMAC digest function
diff --git a/lib/crypto.js b/lib/crypto.js
index 828c0f4310..a38ccb77c1 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -514,6 +514,53 @@ DiffieHellman.prototype.setPrivateKey = function(key, encoding) {
};
+function ECDH(curve) {
+ if (!util.isString(curve))
+ throw new TypeError('curve should be a string');
+
+ this._handle = new binding.ECDH(curve);
+}
+
+exports.createECDH = function createECDH(curve) {
+ return new ECDH(curve);
+};
+
+ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret;
+ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey;
+ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey;
+ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey;
+
+ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
+ this._handle.generateKeys();
+
+ return this.getPublicKey(encoding, format);
+};
+
+ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
+ var f;
+ if (format) {
+ if (typeof format === 'number')
+ f = format;
+ if (format === 'compressed')
+ f = constants.POINT_CONVERSION_COMPRESSED;
+ else if (format === 'hybrid')
+ f = constants.POINT_CONVERSION_HYBRID;
+ // Default
+ else if (format === 'uncompressed')
+ f = constants.POINT_CONVERSION_UNCOMPRESSED;
+ else
+ throw TypeError('Bad format: ' + format);
+ } else {
+ f = constants.POINT_CONVERSION_UNCOMPRESSED;
+ }
+ var key = this._handle.getPublicKey(f);
+ encoding = encoding || exports.DEFAULT_ENCODING;
+ if (encoding && encoding !== 'buffer')
+ key = key.toString(encoding);
+ return key;
+};
+
+
exports.pbkdf2 = function(password,
salt,
diff --git a/src/node_constants.cc b/src/node_constants.cc
index 430a09c685..118824e95d 100644
--- a/src/node_constants.cc
+++ b/src/node_constants.cc
@@ -33,6 +33,7 @@
#include <sys/stat.h>
#if HAVE_OPENSSL
+# include <openssl/ec.h>
# include <openssl/ssl.h>
# ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
@@ -974,6 +975,13 @@ void DefineOpenSSLConstants(Handle<Object> target) {
#ifdef RSA_PKCS1_PSS_PADDING
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
#endif
+
+ // NOTE: These are not defines
+ NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED);
+
+ NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_UNCOMPRESSED);
+
+ NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_HYBRID);
}
void DefineSystemConstants(Handle<Object> target) {
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 6085a18a4d..6adedeeb77 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -4085,6 +4085,224 @@ bool DiffieHellman::VerifyContext() {
}
+void ECDH::Initialize(Environment* env, Handle<Object> target) {
+ HandleScope scope(env->isolate());
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(env->isolate(), New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+
+ NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
+ NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
+ 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(FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"),
+ t->GetFunction());
+}
+
+
+void ECDH::New(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ // TODO(indutny): Support raw curves?
+ CHECK(args[0]->IsString());
+ node::Utf8Value curve(args[0]);
+
+ int nid = OBJ_sn2nid(*curve);
+ if (nid == NID_undef)
+ return env->ThrowTypeError("First argument should be a valid curve name");
+
+ EC_KEY* key = EC_KEY_new_by_curve_name(nid);
+ if (key == NULL)
+ return env->ThrowError("Failed to create EC_KEY using curve name");
+
+ new ECDH(env, args.This(), key);
+}
+
+
+void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ if (!EC_KEY_generate_key(ecdh->key_))
+ return env->ThrowError("Failed to generate EC_KEY");
+
+ ecdh->generated_ = true;
+}
+
+
+EC_POINT* ECDH::BufferToPoint(char* data, size_t len) {
+ EC_POINT* pub;
+ int r;
+
+ pub = EC_POINT_new(group_);
+ if (pub == NULL) {
+ env()->ThrowError("Failed to allocate EC_POINT for a public key");
+ return NULL;
+ }
+
+ r = EC_POINT_oct2point(
+ group_,
+ pub,
+ reinterpret_cast<unsigned char*>(data),
+ len,
+ NULL);
+ if (!r) {
+ env()->ThrowError("Failed to translate Buffer to a EC_POINT");
+ goto fatal;
+ }
+
+ return pub;
+
+ fatal:
+ EC_POINT_free(pub);
+ return NULL;
+}
+
+
+void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ ASSERT_IS_BUFFER(args[0]);
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0]),
+ Buffer::Length(args[0]));
+ if (pub == NULL)
+ return;
+
+ // NOTE: field_size is in bits
+ int field_size = EC_GROUP_get_degree(ecdh->group_);
+ size_t out_len = (field_size + 7) / 8;
+ char* out = static_cast<char*>(malloc(out_len));
+ CHECK_NE(out, NULL);
+
+ int r = ECDH_compute_key(out, out_len, pub, ecdh->key_, NULL);
+ EC_POINT_free(pub);
+ if (!r) {
+ free(out);
+ return env->ThrowError("Failed to compute ECDH key");
+ }
+
+ args.GetReturnValue().Set(Buffer::Use(env, out, out_len));
+}
+
+
+void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ // Conversion form
+ CHECK_EQ(args.Length(), 1);
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ if (!ecdh->generated_)
+ return env->ThrowError("You should generate ECDH keys first");
+
+ const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_);
+ if (pub == NULL)
+ return env->ThrowError("Failed to get ECDH public key");
+
+ int size;
+ point_conversion_form_t form =
+ static_cast<point_conversion_form_t>(args[0]->Uint32Value());
+
+ size = EC_POINT_point2oct(ecdh->group_, pub, form, NULL, 0, NULL);
+ if (size == 0)
+ return env->ThrowError("Failed to get public key length");
+
+ unsigned char* out = static_cast<unsigned char*>(malloc(size));
+ CHECK_NE(out, NULL);
+
+ int r = EC_POINT_point2oct(ecdh->group_, pub, form, out, size, NULL);
+ if (r != size) {
+ free(out);
+ return env->ThrowError("Failed to get public key");
+ }
+
+ args.GetReturnValue().Set(Buffer::Use(env,
+ reinterpret_cast<char*>(out),
+ size));
+}
+
+
+void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ if (!ecdh->generated_)
+ return env->ThrowError("You should generate ECDH keys first");
+
+ const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_);
+ if (b == NULL)
+ return env->ThrowError("Failed to get ECDH private key");
+
+ int size = BN_num_bytes(b);
+ unsigned char* out = static_cast<unsigned char*>(malloc(size));
+ CHECK_NE(out, NULL);
+
+ if (size != BN_bn2bin(b, out)) {
+ free(out);
+ return env->ThrowError("Failed to convert ECDH private key to Buffer");
+ }
+
+ args.GetReturnValue().Set(Buffer::Use(env,
+ reinterpret_cast<char*>(out),
+ size));
+}
+
+
+void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ ASSERT_IS_BUFFER(args[0]);
+
+ BIGNUM* priv = BN_bin2bn(
+ reinterpret_cast<unsigned char*>(Buffer::Data(args[0].As<Object>())),
+ Buffer::Length(args[0].As<Object>()),
+ NULL);
+ if (priv == NULL)
+ return env->ThrowError("Failed to convert Buffer to BN");
+
+ if (!EC_KEY_set_private_key(ecdh->key_, priv))
+ return env->ThrowError("Failed to convert BN to a private key");
+}
+
+
+void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope scope(env->isolate());
+
+ ECDH* ecdh = Unwrap<ECDH>(args.Holder());
+
+ ASSERT_IS_BUFFER(args[0]);
+
+ EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0].As<Object>()),
+ Buffer::Length(args[0].As<Object>()));
+ if (pub == NULL)
+ return;
+
+ int r = EC_KEY_set_public_key(ecdh->key_, pub);
+ EC_POINT_free(pub);
+ if (!r)
+ return env->ThrowError("Failed to convert BN to a private key");
+}
+
+
class PBKDF2Request : public AsyncWrap {
public:
PBKDF2Request(Environment* env,
@@ -4855,6 +5073,7 @@ void InitCrypto(Handle<Object> target,
Connection::Initialize(env, target);
CipherBase::Initialize(env, target);
DiffieHellman::Initialize(env, target);
+ ECDH::Initialize(env, target);
Hmac::Initialize(env, target);
Hash::Initialize(env, target);
Sign::Initialize(env, target);
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 2a02c89bc2..178afc80ea 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -39,6 +39,8 @@
#include "v8.h"
#include <openssl/ssl.h>
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
#ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
@@ -635,6 +637,42 @@ class DiffieHellman : public BaseObject {
DH* dh;
};
+class ECDH : public BaseObject {
+ public:
+ ~ECDH() {
+ if (key_ != NULL)
+ EC_KEY_free(key_);
+ key_ = NULL;
+ group_ = NULL;
+ }
+
+ static void Initialize(Environment* env, v8::Handle<v8::Object> target);
+
+ protected:
+ ECDH(Environment* env, v8::Local<v8::Object> wrap, EC_KEY* key)
+ : BaseObject(env, wrap),
+ generated_(false),
+ key_(key),
+ group_(EC_KEY_get0_group(key_)) {
+ MakeWeak<ECDH>(this);
+ ASSERT(group_ != NULL);
+ }
+
+ static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ EC_POINT* BufferToPoint(char* data, size_t len);
+
+ bool generated_;
+ EC_KEY* key_;
+ const EC_GROUP* group_;
+};
+
class Certificate : public AsyncWrap {
public:
static void Initialize(Environment* env, v8::Handle<v8::Object> target);
diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js
index 74baaa71a0..4b623380b4 100644
--- a/test/simple/test-crypto.js
+++ b/test/simple/test-crypto.js
@@ -1167,3 +1167,38 @@ assert.throws(function() {
// Make sure memory isn't released before being returned
console.log(crypto.randomBytes(16));
+
+// Test ECDH
+var ecdh1 = crypto.createECDH('prime256v1');
+var ecdh2 = crypto.createECDH('prime256v1');
+var key1 = ecdh1.generateKeys();
+var key2 = ecdh2.generateKeys('hex');
+var secret1 = ecdh1.computeSecret(key2, 'hex', 'base64');
+var secret2 = ecdh2.computeSecret(key1, 'binary', 'buffer');
+
+assert.equal(secret1, secret2.toString('base64'));
+
+// Point formats
+assert.equal(ecdh1.getPublicKey('buffer', 'uncompressed')[0], 4);
+var firstByte = ecdh1.getPublicKey('buffer', 'compressed')[0];
+assert(firstByte === 2 || firstByte === 3);
+var firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0];
+assert(firstByte === 6 || firstByte === 7);
+
+// ECDH should check that point is on curve
+var ecdh3 = crypto.createECDH('secp256k1');
+var key3 = ecdh3.generateKeys();
+
+assert.throws(function() {
+ var secret3 = ecdh2.computeSecret(key3, 'binary', 'buffer');
+});
+
+// ECDH should allow .setPrivateKey()/.setPublicKey()
+var ecdh4 = crypto.createECDH('prime256v1');
+
+ecdh4.setPrivateKey(ecdh1.getPrivateKey());
+ecdh4.setPublicKey(ecdh1.getPublicKey());
+
+assert.throws(function() {
+ ecdh4.setPublicKey(ecdh3.getPublicKey());
+});