Skip to content

Commit

Permalink
crypto: introduce ECDH
Browse files Browse the repository at this point in the history
  • Loading branch information
indutny committed Aug 28, 2014
1 parent f7d6147 commit 6e453fa
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 0 deletions.
79 changes: 79 additions & 0 deletions doc/api/crypto.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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) {
Expand Down
219 changes: 219 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 6e453fa

Please sign in to comment.