Skip to content

Commit 3e630c5

Browse files
authored
bpo-33570: TLS 1.3 ciphers for OpenSSL 1.1.1 (GH-6976) (GH-8760)
Change TLS 1.3 cipher suite settings for compatibility with OpenSSL 1.1.1-pre6 and newer. OpenSSL 1.1.1 will have TLS 1.3 cipers enabled by default. Also update multissltests to test with latest OpenSSL. Signed-off-by: Christian Heimes <[email protected]>
1 parent 2a4ee8a commit 3e630c5

File tree

4 files changed

+134
-89
lines changed

4 files changed

+134
-89
lines changed

Doc/library/ssl.rst

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,6 @@ purposes.
300300

301301
3DES was dropped from the default cipher string.
302302

303-
.. versionchanged:: 3.6.3
304-
305-
TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
306-
and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
307-
308303

309304
Random generation
310305
^^^^^^^^^^^^^^^^^
@@ -1483,6 +1478,9 @@ to speed up repeated connections from the same clients.
14831478
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
14841479
give the currently selected cipher.
14851480

1481+
OpenSSL 1.1.1 has TLS 1.3 cipher suites enabled by default. The suites
1482+
cannot be disabled with :meth:`~SSLContext.set_ciphers`.
1483+
14861484
.. method:: SSLContext.set_alpn_protocols(protocols)
14871485

14881486
Specify which protocols the socket should advertise during the SSL/TLS

Lib/test/test_ssl.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3089,17 +3089,22 @@ def test_do_handshake_enotconn(self):
30893089
sock.do_handshake()
30903090
self.assertEqual(cm.exception.errno, errno.ENOTCONN)
30913091

3092-
def test_default_ciphers(self):
3093-
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
3094-
try:
3095-
# Force a set of weak ciphers on our client context
3096-
context.set_ciphers("DES")
3097-
except ssl.SSLError:
3098-
self.skipTest("no DES cipher available")
3099-
with ThreadedEchoServer(CERTFILE,
3100-
ssl_version=ssl.PROTOCOL_SSLv23,
3101-
chatty=False) as server:
3102-
with context.wrap_socket(socket.socket()) as s:
3092+
def test_no_shared_ciphers(self):
3093+
server_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
3094+
server_context.load_cert_chain(SIGNED_CERTFILE)
3095+
client_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
3096+
client_context.verify_mode = ssl.CERT_REQUIRED
3097+
client_context.check_hostname = True
3098+
3099+
# OpenSSL enables all TLS 1.3 ciphers, enforce TLS 1.2 for test
3100+
client_context.options |= ssl.OP_NO_TLSv1_3
3101+
# Force different suites on client and master
3102+
client_context.set_ciphers("AES128")
3103+
server_context.set_ciphers("AES256")
3104+
with ThreadedEchoServer(context=server_context) as server:
3105+
with client_context.wrap_socket(
3106+
socket.socket(),
3107+
server_hostname="localhost") as s:
31033108
with self.assertRaises(OSError):
31043109
s.connect((HOST, server.port))
31053110
self.assertIn("no shared cipher", server.conn_errors[0])
@@ -3132,9 +3137,9 @@ def test_tls1_3(self):
31323137
with context.wrap_socket(socket.socket()) as s:
31333138
s.connect((HOST, server.port))
31343139
self.assertIn(s.cipher()[0], [
3135-
'TLS13-AES-256-GCM-SHA384',
3136-
'TLS13-CHACHA20-POLY1305-SHA256',
3137-
'TLS13-AES-128-GCM-SHA256',
3140+
'TLS_AES_256_GCM_SHA384',
3141+
'TLS_CHACHA20_POLY1305_SHA256',
3142+
'TLS_AES_128_GCM_SHA256',
31383143
])
31393144

31403145
@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
@@ -3460,19 +3465,25 @@ def test_shared_ciphers(self):
34603465
if ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
34613466
client_context.set_ciphers("AES128:AES256")
34623467
server_context.set_ciphers("AES256")
3463-
alg1 = "AES256"
3464-
alg2 = "AES-256"
3468+
expected_algs = [
3469+
"AES256", "AES-256"
3470+
]
34653471
else:
34663472
client_context.set_ciphers("AES:3DES")
34673473
server_context.set_ciphers("3DES")
3468-
alg1 = "3DES"
3469-
alg2 = "DES-CBC3"
3474+
expected_algs = [
3475+
"3DES", "DES-CBC3"
3476+
]
3477+
3478+
if ssl.HAS_TLSv1_3:
3479+
# TLS 1.3 ciphers are always enabled
3480+
expected_algs.extend(["TLS_CHACHA20", "TLS_AES"])
34703481

34713482
stats = server_params_test(client_context, server_context)
34723483
ciphers = stats['server_shared_ciphers'][0]
34733484
self.assertGreater(len(ciphers), 0)
34743485
for name, tls_version, bits in ciphers:
3475-
if not alg1 in name.split("-") and alg2 not in name:
3486+
if not any(alg in name for alg in expected_algs):
34763487
self.fail(name)
34773488

34783489
def test_read_write_after_close_raises_valuerror(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change TLS 1.3 cipher suite settings for compatibility with OpenSSL
2+
1.1.1-pre6 and newer. OpenSSL 1.1.1 will have TLS 1.3 cipers enabled by
3+
default.

Tools/ssl/multissltests.py

Lines changed: 98 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -41,30 +41,31 @@
4141
log = logging.getLogger("multissl")
4242

4343
OPENSSL_OLD_VERSIONS = [
44-
"0.9.8zh",
45-
"1.0.1u",
44+
"0.9.8zh",
45+
"1.0.1u",
46+
"1.0.2",
4647
]
4748

4849
OPENSSL_RECENT_VERSIONS = [
49-
"1.0.2",
50-
"1.0.2m",
51-
"1.1.0g",
50+
"1.0.2o",
51+
"1.1.0h",
52+
# "1.1.1-pre7",
5253
]
5354

5455
LIBRESSL_OLD_VERSIONS = [
55-
"2.3.10",
56-
"2.4.5",
56+
"2.5.5",
57+
"2.6.4",
5758
]
5859

5960
LIBRESSL_RECENT_VERSIONS = [
60-
"2.5.5",
61-
"2.6.4",
62-
"2.7.1",
61+
"2.7.3",
6362
]
6463

6564
# store files in ../multissl
66-
HERE = os.path.abspath(os.getcwd())
67-
MULTISSL_DIR = os.path.abspath(os.path.join(HERE, '..', 'multissl'))
65+
HERE = os.path.dirname(os.path.abspath(__file__))
66+
PYTHONROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
67+
MULTISSL_DIR = os.path.abspath(os.path.join(PYTHONROOT, '..', 'multissl'))
68+
6869

6970
parser = argparse.ArgumentParser(
7071
prog='multissl',
@@ -76,7 +77,7 @@
7677
parser.add_argument(
7778
'--debug',
7879
action='store_true',
79-
help="Enable debug mode",
80+
help="Enable debug logging",
8081
)
8182
parser.add_argument(
8283
'--disable-ancient',
@@ -119,37 +120,54 @@
119120
help="Disable network tests."
120121
)
121122
parser.add_argument(
122-
'--compile-only',
123-
action='store_true',
124-
help="Don't run tests, only compile _ssl.c and _hashopenssl.c."
123+
'--steps',
124+
choices=['library', 'modules', 'tests'],
125+
default='tests',
126+
help=(
127+
"Which steps to perform. 'library' downloads and compiles OpenSSL "
128+
"or LibreSSL. 'module' also compiles Python modules. 'tests' builds "
129+
"all and runs the test suite."
130+
)
125131
)
126132
parser.add_argument(
127133
'--system',
128134
default='',
129135
help="Override the automatic system type detection."
130136
)
137+
parser.add_argument(
138+
'--force',
139+
action='store_true',
140+
dest='force',
141+
help="Force build and installation."
142+
)
143+
parser.add_argument(
144+
'--keep-sources',
145+
action='store_true',
146+
dest='keep_sources',
147+
help="Keep original sources for debugging."
148+
)
131149

132150

133151
class AbstractBuilder(object):
134152
library = None
135153
url_template = None
136154
src_template = None
137155
build_template = None
156+
install_target = 'install'
138157

139158
module_files = ("Modules/_ssl.c",
140159
"Modules/_hashopenssl.c")
141160
module_libs = ("_ssl", "_hashlib")
142161

143-
def __init__(self, version, compile_args=(),
144-
basedir=MULTISSL_DIR):
162+
def __init__(self, version, args):
145163
self.version = version
146-
self.compile_args = compile_args
164+
self.args = args
147165
# installation directory
148166
self.install_dir = os.path.join(
149-
os.path.join(basedir, self.library.lower()), version
167+
os.path.join(args.base_directory, self.library.lower()), version
150168
)
151169
# source file
152-
self.src_dir = os.path.join(basedir, 'src')
170+
self.src_dir = os.path.join(args.base_directory, 'src')
153171
self.src_file = os.path.join(
154172
self.src_dir, self.src_template.format(version))
155173
# build directory (removed after install)
@@ -258,24 +276,31 @@ def _build_src(self):
258276
"""Now build openssl"""
259277
log.info("Running build in {}".format(self.build_dir))
260278
cwd = self.build_dir
261-
cmd = ["./config", "shared", "--prefix={}".format(self.install_dir)]
262-
cmd.extend(self.compile_args)
263-
env = None
279+
cmd = [
280+
"./config",
281+
"shared", "--debug",
282+
"--prefix={}".format(self.install_dir)
283+
]
284+
env = os.environ.copy()
285+
# set rpath
286+
env["LD_RUN_PATH"] = self.lib_dir
264287
if self.system:
265-
env = os.environ.copy()
266288
env['SYSTEM'] = self.system
267289
self._subprocess_call(cmd, cwd=cwd, env=env)
268290
# Old OpenSSL versions do not support parallel builds.
269291
self._subprocess_call(["make", "-j1"], cwd=cwd, env=env)
270292

271-
def _make_install(self, remove=True):
272-
self._subprocess_call(["make", "-j1", "install"], cwd=self.build_dir)
273-
if remove:
293+
def _make_install(self):
294+
self._subprocess_call(
295+
["make", "-j1", self.install_target],
296+
cwd=self.build_dir
297+
)
298+
if not self.args.keep_sources:
274299
shutil.rmtree(self.build_dir)
275300

276301
def install(self):
277302
log.info(self.openssl_cli)
278-
if not self.has_openssl:
303+
if not self.has_openssl or self.args.force:
279304
if not self.has_src:
280305
self._download_src()
281306
else:
@@ -341,6 +366,8 @@ class BuildOpenSSL(AbstractBuilder):
341366
url_template = "https://www.openssl.org/source/openssl-{}.tar.gz"
342367
src_template = "openssl-{}.tar.gz"
343368
build_template = "openssl-{}"
369+
# only install software, skip docs
370+
install_target = 'install_sw'
344371

345372

346373
class BuildLibreSSL(AbstractBuilder):
@@ -379,57 +406,63 @@ def main():
379406

380407
start = datetime.now()
381408

382-
for name in ['python', 'setup.py', 'Modules/_ssl.c']:
383-
if not os.path.isfile(name):
409+
if args.steps in {'modules', 'tests'}:
410+
for name in ['setup.py', 'Modules/_ssl.c']:
411+
if not os.path.isfile(os.path.join(PYTHONROOT, name)):
412+
parser.error(
413+
"Must be executed from CPython build dir"
414+
)
415+
if not os.path.samefile('python', sys.executable):
384416
parser.error(
385-
"Must be executed from CPython build dir"
417+
"Must be executed with ./python from CPython build dir"
386418
)
387-
if not os.path.samefile('python', sys.executable):
388-
parser.error(
389-
"Must be executed with ./python from CPython build dir"
390-
)
391-
392-
# check for configure and run make
393-
configure_make()
419+
# check for configure and run make
420+
configure_make()
394421

395422
# download and register builder
396423
builds = []
397424

398425
for version in args.openssl:
399-
build = BuildOpenSSL(version)
426+
build = BuildOpenSSL(
427+
version,
428+
args
429+
)
400430
build.install()
401431
builds.append(build)
402432

403433
for version in args.libressl:
404-
build = BuildLibreSSL(version)
434+
build = BuildLibreSSL(
435+
version,
436+
args
437+
)
405438
build.install()
406439
builds.append(build)
407440

408-
for build in builds:
409-
try:
410-
build.recompile_pymods()
411-
build.check_pyssl()
412-
if not args.compile_only:
413-
build.run_python_tests(
414-
tests=args.tests,
415-
network=args.network,
416-
)
417-
except Exception as e:
418-
log.exception("%s failed", build)
419-
print("{} failed: {}".format(build, e), file=sys.stderr)
420-
sys.exit(2)
421-
422-
print("\n{} finished in {}".format(
423-
"Tests" if not args.compile_only else "Builds",
424-
datetime.now() - start
425-
))
441+
if args.steps in {'modules', 'tests'}:
442+
for build in builds:
443+
try:
444+
build.recompile_pymods()
445+
build.check_pyssl()
446+
if args.steps == 'tests':
447+
build.run_python_tests(
448+
tests=args.tests,
449+
network=args.network,
450+
)
451+
except Exception as e:
452+
log.exception("%s failed", build)
453+
print("{} failed: {}".format(build, e), file=sys.stderr)
454+
sys.exit(2)
455+
456+
log.info("\n{} finished in {}".format(
457+
args.steps.capitalize(),
458+
datetime.now() - start
459+
))
426460
print('Python: ', sys.version)
427-
if args.compile_only:
428-
print('Build only')
429-
elif args.tests:
430-
print('Executed Tests:', ' '.join(args.tests))
431-
else:
432-
print('Executed all SSL tests.')
461+
if args.steps == 'tests':
462+
if args.tests:
463+
print('Executed Tests:', ' '.join(args.tests))
464+
else:
465+
print('Executed all SSL tests.')
433466

434467
print('OpenSSL / LibreSSL versions:')
435468
for build in builds:

0 commit comments

Comments
 (0)