diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index 78284b35ac..91fc3eab1a 100644 --- a/doc/api/crypto.markdown +++ b/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'`. + diff --git a/lib/crypto.js b/lib/crypto.js index 6931fbcc35..14d26a409c 100644 --- a/lib/crypto.js +++ b/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); + } + +} diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 2d82ff063e..c202678db8 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3053,7 +3053,517 @@ class Verify : public ObjectWrap { }; +class DiffieHellman : public ObjectWrap { + public: + static void Initialize(v8::Handle target) { + HandleScope scope; + + Local 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 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(buf), len); + delete[] buf; + initialized = true; + } + } else if (Buffer::HasInstance(args[0])) { + Local buffer = args[0]->ToObject(); + diffieHellman->Init( + reinterpret_cast(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 GenerateKeys(const Arguments& args) { + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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 outString; + + int dataSize = BN_num_bytes(diffieHellman->dh->pub_key); + char* data = new char[dataSize]; + BN_bn2bin(diffieHellman->dh->pub_key, + reinterpret_cast(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 GetPrime(const Arguments& args) { + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(data)); + + Local 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 GetGenerator(const Arguments& args) { + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(data)); + + Local 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 GetPublicKey(const Arguments& args) { + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(data)); + + Local 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 GetPrivateKey(const Arguments& args) { + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(data)); + + Local 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 ComputeSecret(const Arguments& args) { + HandleScope scope; + + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(buf), len, 0); + delete[] buf; + } else if (Buffer::HasInstance(args[0])) { + Local buffer = args[0]->ToObject(); + key = BN_bin2bn( + reinterpret_cast(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(data), + key, diffieHellman->dh); + BN_free(key); + + Local 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 SetPublicKey(const Arguments& args) { + HandleScope scope; + + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(buf), len, 0); + delete[] buf; + } else if (Buffer::HasInstance(args[0])) { + Local buffer = args[0]->ToObject(); + diffieHellman->dh->pub_key = + BN_bin2bn( + reinterpret_cast(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 SetPrivateKey(const Arguments& args) { + HandleScope scope; + + DiffieHellman* diffieHellman = + ObjectWrap::Unwrap(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(buf), len, 0); + delete[] buf; + } else if (Buffer::HasInstance(args[0])) { + Local buffer = args[0]->ToObject(); + diffieHellman->dh->priv_key = + BN_bin2bn( + reinterpret_cast(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 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 str, Handle 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 EncodeWithEncoding(Handle enc, char* buf, + int len) { + HandleScope scope; + + Local outString; + String::Utf8Value encoding(enc->ToString()); + char* retbuf; + int retlen; + if (strcasecmp(*encoding, "hex") == 0) { + // Hex encoding + HexEncode(reinterpret_cast(buf), len, &retbuf, &retlen); + outString = Encode(retbuf, retlen, BINARY); + delete [] retbuf; + } else if (strcasecmp(*encoding, "base64") == 0) { + base64(reinterpret_cast(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 target) { Connection::Initialize(target); Cipher::Initialize(target); Decipher::Initialize(target); + DiffieHellman::Initialize(target); Hmac::Initialize(target); Hash::Initialize(target); Sign::Initialize(target); diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index 647ba2060e..aefdc233d9 100644 --- a/test/simple/test-crypto.js +++ b/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);