crypto: introduce ECDH
This commit is contained in:
parent
f7d6147e43
commit
6e453fad87
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user