Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new option to limit DH key size in tls connect #1831

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,11 @@ Creates a new client connection to the given `port` and `host` (old API) or

- `session`: A `Buffer` instance, containing TLS session.

- `minDHSize`: Minimum size of DH parameter in bits to accept a TLS
connection. When a server offers DH parameter with a size less
than this, the TLS connection is destroyed and throws an
error. Default: 1024.

The `callback` parameter will be added as a listener for the
['secureConnect'][] event.

Expand Down Expand Up @@ -799,6 +804,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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline between the title and the content.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


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
Expand Down Expand Up @@ -877,6 +895,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
Expand Down
6 changes: 5 additions & 1 deletion lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ exports.createSecureContext = function createSecureContext(options, context) {
else if (options.ecdhCurve)
c.context.setECDHCurve(options.ecdhCurve);

if (options.dhparam) c.context.setDHParam(options.dhparam);
if (options.dhparam) {
var warning = c.context.setDHParam(options.dhparam);
if (warning)
console.trace(warning);
}

if (options.crl) {
if (Array.isArray(options.crl)) {
Expand Down
26 changes: 25 additions & 1 deletion lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -938,14 +945,20 @@ exports.connect = function(/* [port, host], options, cb */) {
var defaults = {
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED,
ciphers: tls.DEFAULT_CIPHERS,
checkServerIdentity: tls.checkServerIdentity
checkServerIdentity: tls.checkServerIdentity,
minDHSize: 1024
};

options = util._extend(defaults, options || {});
if (!options.keepAlive)
options.singleUse = true;

assert(typeof options.checkServerIdentity === 'function');
assert(typeof options.minDHSize === 'number',
'options.minDHSize is not a number: ' + options.minDHSize);
assert(options.minDHSize > 0,
'options.minDHSize is not a posivie number: ' +
options.minDHSize);

var hostname = options.servername ||
options.host ||
Expand Down Expand Up @@ -997,6 +1010,17 @@ exports.connect = function(/* [port, host], options, cb */) {
socket._start();

socket.on('secure', function() {
// Check the size of DHE parameter above minimum requirement
// specified in options.
var ekeyinfo = socket.getEphemeralKeyInfo();
if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) {
var err = new Error('DH parameter size ' + ekeyinfo.size +
' is less than ' + options.minDHSize);
socket.emit('error', err);
socket.destroy();
return;
}

var verifyError = socket._handle.verifyError();

// Verify that server's identity matches it's certificate's names
Expand Down
55 changes: 50 additions & 5 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -804,12 +804,12 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
if (dh == nullptr)
return;

const int keylen = BN_num_bits(dh->p);
if (keylen < 1024) {
DH_free(dh);
const int size = BN_num_bits(dh->p);
if (size < 1024) {
return env->ThrowError("DH parameter is less than 1024 bits");
} else if (keylen < 2048) {
fprintf(stderr, "WARNING: DH parameter is less than 2048 bits\n");
} else if (size < 2048) {
args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(
env->isolate(), "WARNING: DH parameter is less than 2048 bits"));
}

SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_DH_USE);
Expand Down Expand Up @@ -1141,6 +1141,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> 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);
Expand Down Expand Up @@ -1751,6 +1752,50 @@ void SSLWrap<Base>::RequestOCSP(
}


template <class Base>
void SSLWrap<Base>::GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Base* w = Unwrap<Base>(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<Object> 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 <class Base>
void SSLWrap<Base>::SetMaxSendFragment(
Expand Down
2 changes: 2 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ class SSLWrap {
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);

#ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment(
Expand Down
98 changes: 98 additions & 0 deletions test/parallel/test-tls-client-getephemeralkeyinfo.js
Original file line number Diff line number Diff line change
@@ -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);
});
81 changes: 81 additions & 0 deletions test/parallel/test-tls-client-mindhsize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'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 nsuccess = 0;
var nerror = 0;

function loadDHParam(n) {
var path = common.fixturesDir;
if (n !== 'error') path += '/keys';
return fs.readFileSync(path + '/dh' + n + '.pem');
}

function test(size, err, next) {
var options = {
key: key,
cert: cert,
dhparam: loadDHParam(size),
ciphers: 'DHE-RSA-AES128-GCM-SHA256'
};

var server = tls.createServer(options, function(conn) {
conn.end();
});

server.on('close', function(isException) {
assert(!isException);
if (next) next();
});

server.listen(common.PORT, '127.0.0.1', function() {
// client set minimum DH parameter size to 2048 bits so that
// it fails when it make a connection to the tls server where
// dhparams is 1024 bits
var client = tls.connect({
minDHSize: 2048,
port: common.PORT,
rejectUnauthorized: false
}, function() {
nsuccess++;
server.close();
});
if (err) {
client.on('error', function(e) {
nerror++;
assert.strictEqual(e.message, 'DH parameter size 1024 is less'
+ ' than 2048');
server.close();
});
}
});
}

// A client connection fails with an error when a client has an
// 2048 bits minDHSize option and a server has 1024 bits dhparam
function testDHE1024() {
test(1024, true, testDHE2048);
}

// A client connection successes when a client has an
// 2048 bits minDHSize option and a server has 2048 bits dhparam
function testDHE2048() {
test(2048, false, null);
}

testDHE1024();

process.on('exit', function() {
assert.equal(nsuccess, 1);
assert.equal(nerror, 1);
});