Skip to content

Commit

Permalink
tls: add TLSSocket.getEphemeralKeyInfo()
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Shigeki Ohtsu committed May 29, 2015
1 parent 4e90c82 commit 18b8e0c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 0 deletions.
11 changes: 11 additions & 0 deletions doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -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") \
Expand Down
45 changes: 45 additions & 0 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,49 @@ void TLSWrap::SetServername(const FunctionCallbackInfo<Value>& args) {
}


void TLSWrap::GetEphemeralKeyInfo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());

CHECK_NE(wrap->ssl_, nullptr);

Local<Object> 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<TLSWrap*>(SSL_get_app_data(s));
Environment* env = p->env();
Expand Down Expand Up @@ -878,6 +921,8 @@ void TLSWrap::Initialize(Handle<Object> 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());

Expand Down
3 changes: 3 additions & 0 deletions src/tls_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ class TLSWrap : public crypto::SSLWrap<TLSWrap>,
static int SelectSNIContextCallback(SSL* s, int* ad, void* arg);
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB

static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);

crypto::SecureContext* sc_;
StreamBase* stream_;
BIO* enc_in_;
Expand Down
97 changes: 97 additions & 0 deletions test/parallel/test-tls-client-getephemeralkeyinfo.js
Original file line number Diff line number Diff line change
@@ -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);
});

0 comments on commit 18b8e0c

Please sign in to comment.