From 6d92ebac11ba8a9988f08dda16c020d92e6e42a8 Mon Sep 17 00:00:00 2001 From: Shigeki Ohtsu Date: Fri, 22 May 2015 18:20:26 +0900 Subject: [PATCH] tls: add TLSSocket.getEphemeralKeyInfo() Returns an object representing a type, name and size of an ephemeral key exchange in a client connection. Currently only DHE and ECHE are supported. This api only works on on a client connection. When it is called on a server connection, null is returned. When its key exchange is not ephemeral, an empty object is returned. PR-URL: https://github.com/nodejs/node/pull/1831 Reviewed-By: indutny - Fedor Indutny Reviewed-By: bnoordhuis - Ben Noordhuis --- doc/api/tls.markdown | 14 +++ lib/_tls_wrap.js | 7 ++ src/node_crypto.cc | 45 +++++++++ src/node_crypto.h | 2 + .../test-tls-client-getephemeralkeyinfo.js | 98 +++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 test/parallel/test-tls-client-getephemeralkeyinfo.js diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 9fb0e095c02e7b..e4827dbd1b2f33 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -809,6 +809,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 supported types are 'DH' and +'ECDH'. 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 @@ -887,6 +900,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 ae88bf6c18ebf6..da2de9814de936 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -628,6 +628,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 6d5403b563118f..6e4bf9e69f7106 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1134,6 +1134,7 @@ void SSLWrap::AddMethods(Environment* env, Local 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); @@ -1744,6 +1745,50 @@ void SSLWrap::RequestOCSP( } +template +void SSLWrap::GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args) { + Base* w = Unwrap(args.Holder()); + Environment* env = Environment::GetCurrent(args); + + 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 3bec02c38ebdec..c276df04748942 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -236,6 +236,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); +});