Add a new option to limit DH key size in tls connect by shigeki · Pull Request #1831 · nodejs/node · GitHub
Skip to content
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
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