Browse Source

crypto: introduce ECDH

v0.11.14-release
Fedor Indutny 10 years ago
parent
commit
6e453fad87
  1. 79
      doc/api/crypto.markdown
  2. 47
      lib/crypto.js
  3. 8
      src/node_constants.cc
  4. 219
      src/node_crypto.cc
  5. 38
      src/node_crypto.h
  6. 35
      test/simple/test-crypto.js

79
doc/api/crypto.markdown

@ -517,6 +517,85 @@ Example (obtaining a shared secret):
/* alice_secret and bob_secret should be the same */ /* alice_secret and bob_secret should be the same */
console.log(alice_secret == bob_secret); 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) ## crypto.pbkdf2(password, salt, iterations, keylen, [digest], callback)
Asynchronous PBKDF2 function. Applies the selected HMAC digest function Asynchronous PBKDF2 function. Applies the selected HMAC digest function

47
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, exports.pbkdf2 = function(password,
salt, salt,

8
src/node_constants.cc

@ -33,6 +33,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#if HAVE_OPENSSL #if HAVE_OPENSSL
# include <openssl/ec.h>
# include <openssl/ssl.h> # include <openssl/ssl.h>
# ifndef OPENSSL_NO_ENGINE # ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h> # include <openssl/engine.h>
@ -974,6 +975,13 @@ void DefineOpenSSLConstants(Handle<Object> target) {
#ifdef RSA_PKCS1_PSS_PADDING #ifdef RSA_PKCS1_PSS_PADDING
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING); NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
#endif #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) { void DefineSystemConstants(Handle<Object> target) {

219
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 { class PBKDF2Request : public AsyncWrap {
public: public:
PBKDF2Request(Environment* env, PBKDF2Request(Environment* env,
@ -4855,6 +5073,7 @@ void InitCrypto(Handle<Object> target,
Connection::Initialize(env, target); Connection::Initialize(env, target);
CipherBase::Initialize(env, target); CipherBase::Initialize(env, target);
DiffieHellman::Initialize(env, target); DiffieHellman::Initialize(env, target);
ECDH::Initialize(env, target);
Hmac::Initialize(env, target); Hmac::Initialize(env, target);
Hash::Initialize(env, target); Hash::Initialize(env, target);
Sign::Initialize(env, target); Sign::Initialize(env, target);

38
src/node_crypto.h

@ -39,6 +39,8 @@
#include "v8.h" #include "v8.h"
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#ifndef OPENSSL_NO_ENGINE #ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h> # include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE #endif // !OPENSSL_NO_ENGINE
@ -635,6 +637,42 @@ class DiffieHellman : public BaseObject {
DH* dh; 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 { class Certificate : public AsyncWrap {
public: public:
static void Initialize(Environment* env, v8::Handle<v8::Object> target); static void Initialize(Environment* env, v8::Handle<v8::Object> target);

35
test/simple/test-crypto.js

@ -1167,3 +1167,38 @@ assert.throws(function() {
// Make sure memory isn't released before being returned // Make sure memory isn't released before being returned
console.log(crypto.randomBytes(16)); 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());
});

Loading…
Cancel
Save