Skip to content

Commit 6d92eba

Browse files
author
Shigeki Ohtsu
committedOct 16, 2015
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: #1831 Reviewed-By: indutny - Fedor Indutny <[email protected]> Reviewed-By: bnoordhuis - Ben Noordhuis <[email protected]>
1 parent 503f279 commit 6d92eba

File tree

5 files changed

+166
-0
lines changed

5 files changed

+166
-0
lines changed
 

‎doc/api/tls.markdown

+14
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,19 @@ See SSL_CIPHER_get_name() and SSL_CIPHER_get_version() in
809809
http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS for more
810810
information.
811811

812+
### tlsSocket.getEphemeralKeyInfo()
813+
814+
Returns an object representing a type, name and size of parameter of
815+
an ephemeral key exchange in [Perfect forward Secrecy][] on a client
816+
connection. It returns an empty object when the key exchange is not
817+
ephemeral. As it is only supported on a client socket, it returns null
818+
if this is called on a server socket. The supported types are 'DH' and
819+
'ECDH'. The `name` property is only available in 'ECDH'.
820+
821+
Example:
822+
823+
{ type: 'ECDH', name: 'prime256v1', size: 256 }
824+
812825
### tlsSocket.renegotiate(options, callback)
813826

814827
Initiate TLS renegotiation process. The `options` may contain the following
@@ -887,6 +900,7 @@ The numeric representation of the local port.
887900
[net.Server.address()]: net.html#net_server_address
888901
['secureConnect']: #tls_event_secureconnect
889902
[secureConnection]: #tls_event_secureconnection
903+
[Perfect Forward Secrecy]: #tls_perfect_forward_secrecy
890904
[Stream]: stream.html#stream_stream
891905
[SSL_METHODS]: http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS
892906
[tls.Server]: #tls_class_tls_server

‎lib/_tls_wrap.js

+7
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,13 @@ TLSSocket.prototype.getCipher = function(err) {
628628
}
629629
};
630630

631+
TLSSocket.prototype.getEphemeralKeyInfo = function() {
632+
if (this._handle)
633+
return this._handle.getEphemeralKeyInfo();
634+
635+
return null;
636+
};
637+
631638
// TODO: support anonymous (nocert) and PSK
632639

633640

‎src/node_crypto.cc

+45
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
11341134
env->SetProtoMethod(t, "newSessionDone", NewSessionDone);
11351135
env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse);
11361136
env->SetProtoMethod(t, "requestOCSP", RequestOCSP);
1137+
env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo);
11371138

11381139
#ifdef SSL_set_max_send_fragment
11391140
env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment);
@@ -1744,6 +1745,50 @@ void SSLWrap<Base>::RequestOCSP(
17441745
}
17451746

17461747

1748+
template <class Base>
1749+
void SSLWrap<Base>::GetEphemeralKeyInfo(
1750+
const v8::FunctionCallbackInfo<v8::Value>& args) {
1751+
Base* w = Unwrap<Base>(args.Holder());
1752+
Environment* env = Environment::GetCurrent(args);
1753+
1754+
CHECK_NE(w->ssl_, nullptr);
1755+
1756+
// tmp key is available on only client
1757+
if (w->is_server())
1758+
return args.GetReturnValue().SetNull();
1759+
1760+
Local<Object> info = Object::New(env->isolate());
1761+
1762+
EVP_PKEY* key;
1763+
1764+
if (SSL_get_server_tmp_key(w->ssl_, &key)) {
1765+
switch (EVP_PKEY_id(key)) {
1766+
case EVP_PKEY_DH:
1767+
info->Set(env->type_string(),
1768+
FIXED_ONE_BYTE_STRING(env->isolate(), "DH"));
1769+
info->Set(env->size_string(),
1770+
Integer::New(env->isolate(), EVP_PKEY_bits(key)));
1771+
break;
1772+
case EVP_PKEY_EC:
1773+
{
1774+
EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key);
1775+
int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
1776+
EC_KEY_free(ec);
1777+
info->Set(env->type_string(),
1778+
FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"));
1779+
info->Set(env->name_string(),
1780+
OneByteString(args.GetIsolate(), OBJ_nid2sn(nid)));
1781+
info->Set(env->size_string(),
1782+
Integer::New(env->isolate(), EVP_PKEY_bits(key)));
1783+
}
1784+
}
1785+
EVP_PKEY_free(key);
1786+
}
1787+
1788+
return args.GetReturnValue().Set(info);
1789+
}
1790+
1791+
17471792
#ifdef SSL_set_max_send_fragment
17481793
template <class Base>
17491794
void SSLWrap<Base>::SetMaxSendFragment(

‎src/node_crypto.h

+2
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ class SSLWrap {
236236
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
237237
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
238238
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
239+
static void GetEphemeralKeyInfo(
240+
const v8::FunctionCallbackInfo<v8::Value>& args);
239241

240242
#ifdef SSL_set_max_send_fragment
241243
static void SetMaxSendFragment(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use strict';
2+
var common = require('../common');
3+
var assert = require('assert');
4+
5+
if (!common.hasCrypto) {
6+
console.log('1..0 # Skipped: missing crypto');
7+
process.exit();
8+
}
9+
var tls = require('tls');
10+
11+
var fs = require('fs');
12+
var key = fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem');
13+
var cert = fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem');
14+
15+
var ntests = 0;
16+
var nsuccess = 0;
17+
18+
function loadDHParam(n) {
19+
var path = common.fixturesDir;
20+
if (n !== 'error') path += '/keys';
21+
return fs.readFileSync(path + '/dh' + n + '.pem');
22+
}
23+
24+
var cipherlist = {
25+
'NOT_PFS': 'AES128-SHA256',
26+
'DH': 'DHE-RSA-AES128-GCM-SHA256',
27+
'ECDH': 'ECDHE-RSA-AES128-GCM-SHA256'
28+
};
29+
30+
function test(size, type, name, next) {
31+
var cipher = type ? cipherlist[type] : cipherlist['NOT_PFS'];
32+
33+
if (name) tls.DEFAULT_ECDH_CURVE = name;
34+
35+
var options = {
36+
key: key,
37+
cert: cert,
38+
ciphers: cipher
39+
};
40+
41+
if (type === 'DH') options.dhparam = loadDHParam(size);
42+
43+
var server = tls.createServer(options, function(conn) {
44+
assert.strictEqual(conn.getEphemeralKeyInfo(), null);
45+
conn.end();
46+
});
47+
48+
server.on('close', function(err) {
49+
assert(!err);
50+
if (next) next();
51+
});
52+
53+
server.listen(common.PORT, '127.0.0.1', function() {
54+
var client = tls.connect({
55+
port: common.PORT,
56+
rejectUnauthorized: false
57+
}, function() {
58+
var ekeyinfo = client.getEphemeralKeyInfo();
59+
assert.strictEqual(ekeyinfo.type, type);
60+
assert.strictEqual(ekeyinfo.size, size);
61+
assert.strictEqual(ekeyinfo.name, name);
62+
nsuccess++;
63+
server.close();
64+
});
65+
});
66+
}
67+
68+
function testNOT_PFS() {
69+
test(undefined, undefined, undefined, testDHE1024);
70+
ntests++;
71+
}
72+
73+
function testDHE1024() {
74+
test(1024, 'DH', undefined, testDHE2048);
75+
ntests++;
76+
}
77+
78+
function testDHE2048() {
79+
test(2048, 'DH', undefined, testECDHE256);
80+
ntests++;
81+
}
82+
83+
function testECDHE256() {
84+
test(256, 'ECDH', tls.DEFAULT_ECDH_CURVE, testECDHE512);
85+
ntests++;
86+
}
87+
88+
function testECDHE512() {
89+
test(521, 'ECDH', 'secp521r1', null);
90+
ntests++;
91+
}
92+
93+
testNOT_PFS();
94+
95+
process.on('exit', function() {
96+
assert.equal(ntests, nsuccess);
97+
assert.equal(ntests, 5);
98+
});

0 commit comments

Comments
 (0)