diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index a00b27dab91c94..27cb8d84468f94 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -743,6 +743,19 @@ See SSL_CIPHER_get_name() and SSL_CIPHER_get_version() in http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS for more information. +### tlsSocket.getEphemeralKeyInfo() + +Returns an object representing a type, name and size of parameter of +an ephemeral key exchange in [Perfect forward Secrecy][] on a client +connection. It returns an empty object when the key exchange is not +ephemeral. As it is only supported on a client socket, it returns null +if this is called on a server socket. The type of 'DH' and 'ECDH' are +supported. The name property is only available in 'ECDH'. + +Example: + + { type: 'ECDH', name: 'prime256v1', size: 256 } + ### tlsSocket.renegotiate(options, callback) Initiate TLS renegotiation process. The `options` may contain the following @@ -820,6 +833,7 @@ The numeric representation of the local port. [net.Server.address()]: net.html#net_server_address ['secureConnect']: #tls_event_secureconnect [secureConnection]: #tls_event_secureconnection +[Perfect Forward Secrecy]: #tls_perfect_forward_secrecy [Stream]: stream.html#stream_stream [SSL_METHODS]: http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS [tls.Server]: #tls_class_tls_server diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 9c934b9ffbbe3f..073ae36df70d3d 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -573,6 +573,13 @@ TLSSocket.prototype.getCipher = function(err) { } }; +TLSSocket.prototype.getEphemeralKeyInfo = function() { + if (this._handle) + return this._handle.getEphemeralKeyInfo(); + + return null; +}; + // TODO: support anonymous (nocert) and PSK diff --git a/src/node_crypto.cc b/src/node_crypto.cc index e2c478a510be84..fb48d05f3a7057 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1003,6 +1003,7 @@ void SSLWrap::AddMethods(Environment* env, Handle t) { env->SetProtoMethod(t, "newSessionDone", NewSessionDone); env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); env->SetProtoMethod(t, "requestOCSP", RequestOCSP); + env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo); #ifdef SSL_set_max_send_fragment env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); @@ -1608,6 +1609,51 @@ void SSLWrap::RequestOCSP( } +template +void SSLWrap::GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args) { + Base* w = Unwrap(args.Holder()); + Environment* env = Environment::GetCurrent(args); + HandleScope handle_scope(env->isolate()); + + CHECK_NE(w->ssl_, nullptr); + + // tmp key is available on only client + if (w->is_server()) + return args.GetReturnValue().SetNull(); + + Local info = Object::New(env->isolate()); + + EVP_PKEY* key; + + if (SSL_get_server_tmp_key(w->ssl_, &key)) { + switch (EVP_PKEY_id(key)) { + case EVP_PKEY_DH: + info->Set(env->type_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "DH")); + info->Set(env->size_string(), + Integer::New(env->isolate(), EVP_PKEY_bits(key))); + break; + case EVP_PKEY_EC: + { + EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + EC_KEY_free(ec); + info->Set(env->type_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH")); + info->Set(env->name_string(), + OneByteString(args.GetIsolate(), OBJ_nid2sn(nid))); + info->Set(env->size_string(), + Integer::New(env->isolate(), EVP_PKEY_bits(key))); + } + } + EVP_PKEY_free(key); + } + + return args.GetReturnValue().Set(info); +} + + #ifdef SSL_set_max_send_fragment template void SSLWrap::SetMaxSendFragment( diff --git a/src/node_crypto.h b/src/node_crypto.h index 179543bd50124a..58644f045964ca 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -215,6 +215,8 @@ class SSLWrap { static void NewSessionDone(const v8::FunctionCallbackInfo& args); static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); static void RequestOCSP(const v8::FunctionCallbackInfo& args); + static void GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args); #ifdef SSL_set_max_send_fragment static void SetMaxSendFragment( diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js new file mode 100644 index 00000000000000..8932a4fc8a8f93 --- /dev/null +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -0,0 +1,98 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); + +if (!common.hasCrypto) { + console.log('1..0 # Skipped: missing crypto'); + process.exit(); +} +var tls = require('tls'); + +var fs = require('fs'); +var key = fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'); +var cert = fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem'); + +var ntests = 0; +var nsuccess = 0; + +function loadDHParam(n) { + var path = common.fixturesDir; + if (n !== 'error') path += '/keys'; + return fs.readFileSync(path + '/dh' + n + '.pem'); +} + +var cipherlist = { + 'NOT_PFS': 'AES128-SHA256', + 'DH': 'DHE-RSA-AES128-GCM-SHA256', + 'ECDH': 'ECDHE-RSA-AES128-GCM-SHA256' +}; + +function test(size, type, name, next) { + var cipher = type ? cipherlist[type] : cipherlist['NOT_PFS']; + + if (name) tls.DEFAULT_ECDH_CURVE = name; + + var options = { + key: key, + cert: cert, + ciphers: cipher + }; + + if (type === 'DH') options.dhparam = loadDHParam(size); + + var server = tls.createServer(options, function(conn) { + assert.strictEqual(conn.getEphemeralKeyInfo(), null); + conn.end(); + }); + + server.on('close', function(err) { + assert(!err); + if (next) next(); + }); + + server.listen(common.PORT, '127.0.0.1', function() { + var client = tls.connect({ + port: common.PORT, + rejectUnauthorized: false + }, function() { + var ekeyinfo = client.getEphemeralKeyInfo(); + assert.strictEqual(ekeyinfo.type, type); + assert.strictEqual(ekeyinfo.size, size); + assert.strictEqual(ekeyinfo.name, name); + nsuccess++; + server.close(); + }); + }); +} + +function testNOT_PFS() { + test(undefined, undefined, undefined, testDHE1024); + ntests++; +} + +function testDHE1024() { + test(1024, 'DH', undefined, testDHE2048); + ntests++; +} + +function testDHE2048() { + test(2048, 'DH', undefined, testECDHE256); + ntests++; +} + +function testECDHE256() { + test(256, 'ECDH', tls.DEFAULT_ECDH_CURVE, testECDHE512); + ntests++; +} + +function testECDHE512() { + test(521, 'ECDH', 'secp521r1', null); + ntests++; +} + +testNOT_PFS(); + +process.on('exit', function() { + assert.equal(ntests, nsuccess); + assert.equal(ntests, 5); +});