diff --git a/lib/crypto.js b/lib/crypto.js index 0033267ceeea..9d1ed44edb51 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -93,6 +93,9 @@ exports.createCredentials = function(options, context) { if (context) return c; + if (options.ecdheCurves) + c.context.setECDHECurves.apply(c.context, options.ecdheCurves); + if (options.key) { if (options.passphrase) { c.context.setKey(options.key, options.passphrase); diff --git a/lib/tls.js b/lib/tls.js index 0222fa9bee24..60f385a16669 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -1127,6 +1127,7 @@ function Server(/* [options], listener */) { cert: self.cert, ca: self.ca, ciphers: self.ciphers || DEFAULT_CIPHERS, + ecdheCurves: self.ecdheCurves, secureProtocol: self.secureProtocol, secureOptions: self.secureOptions, crl: self.crl, @@ -1231,6 +1232,7 @@ Server.prototype.setOptions = function(options) { if (options.secureProtocol) this.secureProtocol = options.secureProtocol; if (options.crl) this.crl = options.crl; if (options.ciphers) this.ciphers = options.ciphers; + if (options.ecdheCurves) this.ecdheCurves = options.ecdheCurves; var secureOptions = options.secureOptions || 0; if (options.honorCipherOrder) { secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 9cbc577b52f5..46c2eadf2d59 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -131,6 +131,10 @@ void SecureContext::Initialize(Handle target) { t->SetClassName(String::NewSymbol("SecureContext")); NODE_SET_PROTOTYPE_METHOD(t, "init", SecureContext::Init); + NODE_SET_PROTOTYPE_METHOD(t, "setECDHECurves", + SecureContext::SetECDHECurves); + NODE_SET_PROTOTYPE_METHOD(t, "clearECDHECurves", + SecureContext::ClearECDHECurves); NODE_SET_PROTOTYPE_METHOD(t, "setKey", SecureContext::SetKey); NODE_SET_PROTOTYPE_METHOD(t, "setCert", SecureContext::SetCert); NODE_SET_PROTOTYPE_METHOD(t, "addCACert", SecureContext::AddCACert); @@ -201,6 +205,18 @@ Handle SecureContext::Init(const Arguments& args) { method = TLSv1_server_method(); } else if (strcmp(*sslmethod, "TLSv1_client_method") == 0) { method = TLSv1_client_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_method") == 0) { + method = TLSv1_1_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_server_method") == 0) { + method = TLSv1_1_server_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_client_method") == 0) { + method = TLSv1_1_client_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_method") == 0) { + method = TLSv1_2_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_server_method") == 0) { + method = TLSv1_2_server_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_client_method") == 0) { + method = TLSv1_2_client_method(); } else { return ThrowException(Exception::Error(String::New("Unknown method"))); } @@ -208,6 +224,8 @@ Handle SecureContext::Init(const Arguments& args) { sc->ctx_ = SSL_CTX_new(method); + SSL_CTX_set_app_data(sc->ctx_, sc); + // SSL session cache configuration SSL_CTX_set_session_cache_mode(sc->ctx_, SSL_SESS_CACHE_SERVER | @@ -220,6 +238,100 @@ Handle SecureContext::Init(const Arguments& args) { return True(node_isolate); } +static EC_KEY *SecureContext_ecdhe_key_cb(SSL *ssl, int is_ex, int bit_len) { + SecureContext *sc = (SecureContext *)SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl)); + + assert(NULL != sc->ecdhe_keys); + + for (int i = 0; i < sc->ecdhe_keys_len; i++) + if (sc->ecdhe_keys[i].bit_len >= bit_len) + return sc->ecdhe_keys[i].key; + + if (0 < sc->ecdhe_keys_len) + return sc->ecdhe_keys[sc->ecdhe_keys_len - 1].key; + + return NULL; +} + +Handle SecureContext::SetECDHECurves(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + sc->_ClearECDHECurves(); + + char err[80]; + int i = 0; + + sc->ecdhe_keys_len = args.Length(); + sc->ecdhe_keys = new BitLenKey[sc->ecdhe_keys_len]; + + for (; i < args.Length(); i++) { + String::Utf8Value _curvesn(args[i]); + const char *curvesn = *_curvesn; + + int curvenid = OBJ_sn2nid(curvesn); + if (NID_undef == curvenid) { + snprintf(err, 80, "Invalid curve: %s", curvesn); + goto err_obj_sn2nid; + } + + EC_KEY *key = EC_KEY_new_by_curve_name(curvenid); + if (NULL == key) { + snprintf(err, 80, "Failed to generate key for curve: %s", curvesn); + goto err_ec_key_new; + } + + const EC_GROUP *grp = EC_KEY_get0_group(key); + if (NULL == grp) { + snprintf(err, 80, "Failed to get group for curve: %s", curvesn); + goto err_ec_key_get_group; + } + + sc->ecdhe_keys[i].bit_len = EC_GROUP_get_degree(grp); + sc->ecdhe_keys[i].key = key; + } + + SSL_CTX_set_tmp_ecdh_callback(sc->ctx_, SecureContext_ecdhe_key_cb); + + return True(); + +err_obj_sn2nid: +err_ec_key_new: + i--; + +err_ec_key_get_group: + for (; i >= 0; i--) + EC_KEY_free(sc->ecdhe_keys[i].key); + + delete sc->ecdhe_keys; + sc->ecdhe_keys = NULL; + + return ThrowException(Exception::Error(String::New(err))); +} + +Handle SecureContext::ClearECDHECurves(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + sc->_ClearECDHECurves(); + + return True(); +} + +void SecureContext::_ClearECDHECurves(void) { + if (NULL == ecdhe_keys) + return; + + for (int i = 0; i < ecdhe_keys_len; i++) + EC_KEY_free(ecdhe_keys[i].key); + + delete ecdhe_keys; + ecdhe_keys = NULL; + + SSL_CTX_set_tmp_ecdh_callback(ctx_, NULL); +} + SSL_SESSION* SecureContext::GetSessionCallback(SSL* s, unsigned char* key, @@ -1520,17 +1632,18 @@ Handle Connection::GetPeerCertificate(const Arguments& args) { EVP_PKEY *pkey = NULL; RSA *rsa = NULL; - if( NULL != (pkey = X509_get_pubkey(peer_cert)) - && NULL != (rsa = EVP_PKEY_get1_RSA(pkey)) ) { - BN_print(bio, rsa->n); - BIO_get_mem_ptr(bio, &mem); - info->Set(modulus_symbol, String::New(mem->data, mem->length) ); - (void) BIO_reset(bio); - - BN_print(bio, rsa->e); - BIO_get_mem_ptr(bio, &mem); - info->Set(exponent_symbol, String::New(mem->data, mem->length) ); - (void) BIO_reset(bio); + if (NULL != X509_get_pubkey(peer_cert) && + EVP_PKEY_RSA == EVP_PKEY_type(pkey->type) && + NULL != (rsa = EVP_PKEY_get1_RSA(pkey)) ) { + BN_print(bio, rsa->n); + BIO_get_mem_ptr(bio, &mem); + info->Set(modulus_symbol, String::New(mem->data, mem->length) ); + (void) BIO_reset(bio); + + BN_print(bio, rsa->e); + BIO_get_mem_ptr(bio, &mem); + info->Set(exponent_symbol, String::New(mem->data, mem->length) ); + (void) BIO_reset(bio); } if (pkey != NULL) { diff --git a/src/node_crypto.h b/src/node_crypto.h index ee3cf93ba003..5bdeb6f16e99 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -27,6 +27,7 @@ #include "node_object_wrap.h" #include "v8.h" +#include #include #include #include @@ -52,6 +53,11 @@ static X509_STORE* root_cert_store; // Forward declaration class Connection; +template struct BitLenKey { + int bit_len; + KeyType *key; +}; + class SecureContext : ObjectWrap { public: static void Initialize(v8::Handle target); @@ -60,11 +66,16 @@ class SecureContext : ObjectWrap { // TODO: ca_store_ should probably be removed, it's not used anywhere. X509_STORE *ca_store_; + int ecdhe_keys_len; + BitLenKey *ecdhe_keys; + protected: static const int kMaxSessionSize = 10 * 1024; static v8::Handle New(const v8::Arguments& args); static v8::Handle Init(const v8::Arguments& args); + static v8::Handle SetECDHECurves(const v8::Arguments& args); + static v8::Handle ClearECDHECurves(const v8::Arguments& args); static v8::Handle SetKey(const v8::Arguments& args); static v8::Handle SetCert(const v8::Arguments& args); static v8::Handle AddCACert(const v8::Arguments& args); @@ -85,8 +96,11 @@ class SecureContext : ObjectWrap { SecureContext() : ObjectWrap() { ctx_ = NULL; ca_store_ = NULL; + ecdhe_keys = NULL; } + void _ClearECDHECurves(void); + void FreeCTXMem() { if (ctx_) { if (ctx_->cert_store == root_cert_store) { @@ -106,6 +120,7 @@ class SecureContext : ObjectWrap { ~SecureContext() { FreeCTXMem(); + _ClearECDHECurves(); } private: