Browse Source

Add Diffie-Hellman support to crypto module

Fixes #573
v0.7.4-release
Håvard Stranden 14 years ago
committed by Ryan Dahl
parent
commit
9f0b1a9bc6
  1. 55
      doc/api/crypto.markdown
  2. 13
      lib/crypto.js
  3. 511
      src/node_crypto.cc
  4. 28
      test/simple/test-crypto.js

55
doc/api/crypto.markdown

@ -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'`.

13
lib/crypto.js

@ -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);
}
}

511
src/node_crypto.cc

@ -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);

28
test/simple/test-crypto.js

@ -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);

Loading…
Cancel
Save