crypto: don't disable TLS 1.3 without suites

In the manual page, there is a statement that ciphersuites contain
explicit default settings - all TLS 1.3 ciphersuites enabled.
In node, we assume that an empty setting mean no ciphersuites and
we disable TLS 1.3. A correct approach to disabling TLS 1.3 is to
disable TLS 1.3 and by not override the default ciphersuits
with an empty string.

So, only override OpenSSL's TLS 1.3 ciphersuites with an explicit
list of ciphers. If none are acceptable, the correct approach is
to disable TLS 1.3 instead elsewhere.

Fixes: https://github.com/nodejs/node/issues/43419

PR-URL: https://github.com/nodejs/node/pull/43427
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: James M Snell <jasnell@gmail.com>
pull/43590/head
Adam Majer 2022-06-27 10:47:13 +02:00 committed by GitHub
parent 4ecc6a400f
commit 9cde7a033e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 54 additions and 23 deletions

View File

@ -25,6 +25,7 @@ function main({ dur, size, securing }) {
isServer: true, isServer: true,
requestCert: true, requestCert: true,
rejectUnauthorized: true, rejectUnauthorized: true,
maxVersion: 'TLSv1.2',
}; };
const server = net.createServer(onRedirectConnection); const server = net.createServer(onRedirectConnection);
@ -38,6 +39,7 @@ function main({ dur, size, securing }) {
cert: options.cert, cert: options.cert,
isServer: false, isServer: false,
rejectUnauthorized: false, rejectUnauthorized: false,
maxVersion: options.maxVersion,
}; };
const network = securing === 'clear' ? net : tls; const network = securing === 'clear' ? net : tls;
const conn = network.connect(clientOptions, () => { const conn = network.connect(clientOptions, () => {

View File

@ -33,7 +33,8 @@ function main({ dur, type, size }) {
key: fixtures.readKey('rsa_private.pem'), key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'), cert: fixtures.readKey('rsa_cert.crt'),
ca: fixtures.readKey('rsa_ca.crt'), ca: fixtures.readKey('rsa_ca.crt'),
ciphers: 'AES256-GCM-SHA384' ciphers: 'AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
}; };
const server = tls.createServer(options, onConnection); const server = tls.createServer(options, onConnection);

View File

@ -40,7 +40,8 @@ function main({ dur, type, sendchunklen, recvbuflen, recvbufgenfn }) {
key: fixtures.readKey('rsa_private.pem'), key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'), cert: fixtures.readKey('rsa_cert.crt'),
ca: fixtures.readKey('rsa_ca.crt'), ca: fixtures.readKey('rsa_ca.crt'),
ciphers: 'AES256-GCM-SHA384' ciphers: 'AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
}; };
let socketOpts; let socketOpts;

View File

@ -21,7 +21,8 @@ function main(conf) {
key: fixtures.readKey('rsa_private.pem'), key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'), cert: fixtures.readKey('rsa_cert.crt'),
ca: fixtures.readKey('rsa_ca.crt'), ca: fixtures.readKey('rsa_ca.crt'),
ciphers: 'AES256-GCM-SHA384' ciphers: 'AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
}; };
const server = tls.createServer(options, onConnection); const server = tls.createServer(options, onConnection);

View File

@ -225,15 +225,10 @@ function configSecureContext(context, options = kEmptyObject, name = 'options')
cipherSuites, cipherSuites,
} = processCiphers(ciphers, `${name}.ciphers`); } = processCiphers(ciphers, `${name}.ciphers`);
context.setCipherSuites(cipherSuites); if (cipherSuites !== '')
context.setCipherSuites(cipherSuites);
context.setCiphers(cipherList); context.setCiphers(cipherList);
if (cipherSuites === '' &&
context.getMaxProto() > TLS1_2_VERSION &&
context.getMinProto() < TLS1_3_VERSION) {
context.setMaxProto(TLS1_2_VERSION);
}
if (cipherList === '' && if (cipherList === '' &&
context.getMinProto() < TLS1_3_VERSION && context.getMinProto() < TLS1_3_VERSION &&
context.getMaxProto() > TLS1_2_VERSION) { context.getMaxProto() > TLS1_2_VERSION) {

View File

@ -23,7 +23,8 @@ function test(size, type, name, cipher) {
const options = { const options = {
key: key, key: key,
cert: cert, cert: cert,
ciphers: cipher ciphers: cipher,
maxVersion: 'TLSv1.2',
}; };
if (name) options.ecdhCurve = name; if (name) options.ecdhCurve = name;

View File

@ -41,7 +41,8 @@ function test(size, err, next) {
const client = tls.connect({ const client = tls.connect({
minDHSize: 2048, minDHSize: 2048,
port: this.address().port, port: this.address().port,
rejectUnauthorized: false rejectUnauthorized: false,
maxVersion: 'TLSv1.2',
}, function() { }, function() {
nsuccess++; nsuccess++;
server.close(); server.close();

View File

@ -53,7 +53,8 @@ function test(keylen, expectedCipher, cb) {
key: key, key: key,
cert: cert, cert: cert,
ciphers: ciphers, ciphers: ciphers,
dhparam: loadDHParam(keylen) dhparam: loadDHParam(keylen),
maxVersion: 'TLSv1.2',
}; };
const server = tls.createServer(options, function(conn) { const server = tls.createServer(options, function(conn) {

View File

@ -23,7 +23,8 @@ const options = {
key: loadPEM('agent2-key'), key: loadPEM('agent2-key'),
cert: loadPEM('agent2-cert'), cert: loadPEM('agent2-cert'),
ciphers: '-ALL:ECDHE-RSA-AES128-SHA256', ciphers: '-ALL:ECDHE-RSA-AES128-SHA256',
ecdhCurve: 'auto' ecdhCurve: 'auto',
maxVersion: 'TLSv1.2',
}; };
const reply = 'I AM THE WALRUS'; // Something recognizable const reply = 'I AM THE WALRUS'; // Something recognizable

View File

@ -23,7 +23,8 @@ const options = {
key: loadPEM('agent2-key'), key: loadPEM('agent2-key'),
cert: loadPEM('agent2-cert'), cert: loadPEM('agent2-cert'),
ciphers: '-ALL:ECDHE-RSA-AES128-SHA256', ciphers: '-ALL:ECDHE-RSA-AES128-SHA256',
ecdhCurve: 'secp256k1:prime256v1:secp521r1' ecdhCurve: 'secp256k1:prime256v1:secp521r1',
maxVersion: 'TLSv1.2',
}; };
const reply = 'I AM THE WALRUS'; // Something recognizable const reply = 'I AM THE WALRUS'; // Something recognizable

View File

@ -38,7 +38,8 @@ const options = {
key: fixtures.readKey('agent2-key.pem'), key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'), cert: fixtures.readKey('agent2-cert.pem'),
ciphers: '-ALL:ECDHE-RSA-AES128-SHA256', ciphers: '-ALL:ECDHE-RSA-AES128-SHA256',
ecdhCurve: 'prime256v1' ecdhCurve: 'prime256v1',
maxVersion: 'TLSv1.2'
}; };
const reply = 'I AM THE WALRUS'; // Something recognizable const reply = 'I AM THE WALRUS'; // Something recognizable

View File

@ -48,7 +48,8 @@ server.listen(0, '127.0.0.1', common.mustCall(function() {
host: '127.0.0.1', host: '127.0.0.1',
port: this.address().port, port: this.address().port,
ciphers: 'AES128-SHA256', ciphers: 'AES128-SHA256',
rejectUnauthorized: false rejectUnauthorized: false,
maxVersion: 'TLSv1.2',
}, common.mustCall(function() { }, common.mustCall(function() {
const cipher = this.getCipher(); const cipher = this.getCipher();
assert.strictEqual(cipher.name, 'AES128-SHA256'); assert.strictEqual(cipher.name, 'AES128-SHA256');
@ -62,7 +63,8 @@ server.listen(0, '127.0.0.1', common.mustCall(function() {
host: '127.0.0.1', host: '127.0.0.1',
port: this.address().port, port: this.address().port,
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256', ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
rejectUnauthorized: false rejectUnauthorized: false,
maxVersion: 'TLSv1.2',
}, common.mustCall(function() { }, common.mustCall(function() {
const cipher = this.getCipher(); const cipher = this.getCipher();
assert.strictEqual(cipher.name, 'ECDHE-RSA-AES128-GCM-SHA256'); assert.strictEqual(cipher.name, 'ECDHE-RSA-AES128-GCM-SHA256');

View File

@ -154,11 +154,12 @@ function test(options) {
rejectUnauthorized: true, rejectUnauthorized: true,
ca: clientTrustRoots, ca: clientTrustRoots,
checkServerIdentity: (_, c) => assert.strictEqual(c.subject.CN, eccCN), checkServerIdentity: (_, c) => assert.strictEqual(c.subject.CN, eccCN),
maxVersion: 'TLSv1.2'
}, common.mustCall(function() { }, common.mustCall(function() {
assert.deepStrictEqual(ecdsa.getCipher(), { assert.deepStrictEqual(ecdsa.getCipher(), {
name: 'ECDHE-ECDSA-AES256-GCM-SHA384', name: 'ECDHE-ECDSA-AES256-GCM-SHA384',
standardName: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', standardName: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
version: 'TLSv1.2' version: 'TLSv1.2',
}); });
assert.strictEqual(ecdsa.getPeerCertificate().subject.CN, eccCN); assert.strictEqual(ecdsa.getPeerCertificate().subject.CN, eccCN);
assert.strictEqual(ecdsa.getPeerCertificate().asn1Curve, 'prime256v1'); assert.strictEqual(ecdsa.getPeerCertificate().asn1Curve, 'prime256v1');
@ -173,11 +174,12 @@ function test(options) {
rejectUnauthorized: true, rejectUnauthorized: true,
ca: clientTrustRoots, ca: clientTrustRoots,
checkServerIdentity: (_, c) => assert.strictEqual(c.subject.CN, rsaCN), checkServerIdentity: (_, c) => assert.strictEqual(c.subject.CN, rsaCN),
maxVersion: 'TLSv1.2',
}, common.mustCall(function() { }, common.mustCall(function() {
assert.deepStrictEqual(rsa.getCipher(), { assert.deepStrictEqual(rsa.getCipher(), {
name: 'ECDHE-RSA-AES256-GCM-SHA384', name: 'ECDHE-RSA-AES256-GCM-SHA384',
standardName: 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', standardName: 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
version: 'TLSv1.2' version: 'TLSv1.2',
}); });
assert.strictEqual(rsa.getPeerCertificate().subject.CN, rsaCN); assert.strictEqual(rsa.getPeerCertificate().subject.CN, rsaCN);
assert(rsa.getPeerCertificate().exponent, 'cert for an RSA key'); assert(rsa.getPeerCertificate().exponent, 'cert for an RSA key');

View File

@ -24,12 +24,14 @@ const server = tls.createServer(options, function(conn) {
}).listen(0, function() { }).listen(0, function() {
const ecdsa = tls.connect(this.address().port, { const ecdsa = tls.connect(this.address().port, {
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384', ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
rejectUnauthorized: false maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustCall(function() { }, common.mustCall(function() {
ciphers.push(ecdsa.getCipher()); ciphers.push(ecdsa.getCipher());
const rsa = tls.connect(server.address().port, { const rsa = tls.connect(server.address().port, {
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
rejectUnauthorized: false maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustCall(function() { }, common.mustCall(function() {
ciphers.push(rsa.getCipher()); ciphers.push(rsa.getCipher());
ecdsa.end(); ecdsa.end();

View File

@ -13,19 +13,31 @@ const {
} = require(fixtures.path('tls-connect')); } = require(fixtures.path('tls-connect'));
function test(cciphers, sciphers, cipher, cerr, serr) { function test(cciphers, sciphers, cipher, cerr, serr, options) {
assert(cipher || cerr || serr, 'test missing any expectations'); assert(cipher || cerr || serr, 'test missing any expectations');
const where = inspect(new Error()).split('\n')[2].replace(/[^(]*/, ''); const where = inspect(new Error()).split('\n')[2].replace(/[^(]*/, '');
const max_tls_ver = (ciphers, options) => {
if (options instanceof Object && Object.hasOwn(options, 'maxVersion'))
return options.maxVersion;
if ((typeof ciphers === 'string' || ciphers instanceof String) && ciphers.length > 0 && !ciphers.includes('TLS_'))
return 'TLSv1.2';
return 'TLSv1.3';
};
connect({ connect({
client: { client: {
checkServerIdentity: (servername, cert) => { }, checkServerIdentity: (servername, cert) => { },
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`, ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
ciphers: cciphers, ciphers: cciphers,
maxVersion: max_tls_ver(cciphers, options),
}, },
server: { server: {
cert: keys.agent6.cert, cert: keys.agent6.cert,
key: keys.agent6.key, key: keys.agent6.key,
ciphers: sciphers, ciphers: sciphers,
maxVersion: max_tls_ver(sciphers, options),
}, },
}, common.mustCall((err, pair, cleanup) => { }, common.mustCall((err, pair, cleanup) => {
function u(_) { return _ === undefined ? 'U' : _; } function u(_) { return _ === undefined ? 'U' : _; }
@ -87,6 +99,13 @@ test('AES128-SHA:TLS_AES_256_GCM_SHA384',
test('AES256-SHA:TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); test('AES256-SHA:TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384');
test(U, 'AES256-SHA:TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); test(U, 'AES256-SHA:TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384');
// Cipher order ignored, TLS1.3 before TLS1.2 and
// cipher suites are not disabled if TLS ciphers are set only
// TODO: maybe these tests should be reworked so maxVersion clamping
// is done explicitly and not implicitly in the test() function
test('AES256-SHA', U, 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' });
test(U, 'AES256-SHA', 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' });
// TLS_AES_128_CCM_8_SHA256 & TLS_AES_128_CCM_SHA256 are not enabled by // TLS_AES_128_CCM_8_SHA256 & TLS_AES_128_CCM_SHA256 are not enabled by
// default, but work. // default, but work.
test('TLS_AES_128_CCM_8_SHA256', U, test('TLS_AES_128_CCM_8_SHA256', U,