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'`. 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. 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 Decipher = binding.Decipher;
var Sign = binding.Sign; var Sign = binding.Sign;
var Verify = binding.Verify; var Verify = binding.Verify;
var DiffieHellman = binding.DiffieHellman;
var crypto = true; var crypto = true;
} catch (e) { } catch (e) {
@ -139,3 +140,15 @@ exports.Verify = Verify;
exports.createVerify = function(algorithm) { exports.createVerify = function(algorithm) {
return (new Verify).init(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); Connection::Initialize(target);
Cipher::Initialize(target); Cipher::Initialize(target);
Decipher::Initialize(target); Decipher::Initialize(target);
DiffieHellman::Initialize(target);
Hmac::Initialize(target); Hmac::Initialize(target);
Hash::Initialize(target); Hash::Initialize(target);
Sign::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() { assert.throws(function() {
crypto.createHash('sha1').update({foo: 'bar'}); crypto.createHash('sha1').update({foo: 'bar'});
}, /string or buffer/); }, /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