From 18b8e0c393d8785654ac3f0ad1585ae503a278eb 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 key length of an ephemeral key exchange(PFS) in a client connection. This api is only supported not on a server connection but on a client. When it is called on a server connection or its key exchange is not ephemeral, an empty object is returned. --- doc/api/tls.markdown | 11 +++ lib/_tls_wrap.js | 7 ++ src/env.h | 1 + src/tls_wrap.cc | 45 +++++++++ src/tls_wrap.h | 3 + .../test-tls-client-getephemeralkeyinfo.js | 97 +++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 test/parallel/test-tls-client-getephemeralkeyinfo.js diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index a00b27dab91c94..d9e32698dabb88 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -743,6 +743,17 @@ 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 key length of an +ephemeral key exchange(PFS) in a client connection. It is only +supported on a client connection not a server. If this is called in a +server socket or its key exchange is not ephemeral, it returns an +empty object. The type of 'DH' and 'ECDH' are supported. The name +property is only available in 'ECDH'. + +Example: +{ type: 'ECDH', name: 'prime256v1', keylen: 256 } + ### tlsSocket.renegotiate(options, callback) Initiate TLS renegotiation process. The `options` may contain the following 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/env.h b/src/env.h index b53ff87fb13114..6ba6788eab67d7 100644 --- a/src/env.h +++ b/src/env.h @@ -113,6 +113,7 @@ namespace node { V(isclosing_string, "isClosing") \ V(issuer_string, "issuer") \ V(issuercert_string, "issuerCertificate") \ + V(keylen_string, "keylen") \ V(kill_signal_string, "killSignal") \ V(mac_string, "mac") \ V(mark_sweep_compact_string, "mark-sweep-compact") \ diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 30768640eb99e2..5860139cda9c40 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -816,6 +816,49 @@ void TLSWrap::SetServername(const FunctionCallbackInfo& args) { } +void TLSWrap::GetEphemeralKeyInfo(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap = Unwrap(args.Holder()); + + CHECK_NE(wrap->ssl_, nullptr); + + Local info = Object::New(env->isolate()); + + // tmp key is available on only client + if (wrap->is_server()) + return args.GetReturnValue().Set(info); + + EVP_PKEY *key; + + if (SSL_get_server_tmp_key(wrap->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->keylen_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->keylen_string(), + Integer::New(env->isolate(), EVP_PKEY_bits(key))); + } + } + EVP_PKEY_free(key); + } + + return args.GetReturnValue().Set(info); +} + + int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { TLSWrap* p = static_cast(SSL_get_app_data(s)); Environment* env = p->env(); @@ -878,6 +921,8 @@ void TLSWrap::Initialize(Handle target, env->SetProtoMethod(t, "setServername", SetServername); #endif // SSL_CRT_SET_TLSEXT_SERVERNAME_CB + env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo); + env->set_tls_wrap_constructor_template(t); env->set_tls_wrap_constructor_function(t->GetFunction()); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index a30447519083c2..a9db81d955faa0 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -140,6 +140,9 @@ class TLSWrap : public crypto::SSLWrap, static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + static void GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args); + crypto::SecureContext* sc_; StreamBase* stream_; BIO* enc_in_; diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js new file mode 100644 index 00000000000000..8dd61d20d1569b --- /dev/null +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -0,0 +1,97 @@ +'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(keylen, type, name, cb) { + 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(keylen); + + var server = tls.createServer(options, function(conn) { + conn.end(); + }); + + server.on('close', function(err) { + assert(!err); + if (cb) cb(); + }); + + 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.keylen, keylen); + 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); +});