Commit c49f63c1 authored by stratakis's avatar stratakis Committed by Victor Stinner

[2.7] bpo-33570: TLS 1.3 ciphers for OpenSSL 1.1.1 (GH-6976) (GH-8760) (GH-10607)

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 <christian@python.org>.
(cherry picked from commit 3e630c541b35c96bfe5619165255e559f577ee71)
Co-authored-by: default avatarChristian Heimes <christian@python.org>
parent 826a8b70
...@@ -294,11 +294,6 @@ purposes. ...@@ -294,11 +294,6 @@ purposes.
3DES was dropped from the default cipher string. 3DES was dropped from the default cipher string.
.. versionchanged:: 2.7.15
TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
.. function:: _https_verify_certificates(enable=True) .. function:: _https_verify_certificates(enable=True)
Specifies whether or not server certificates are verified when creating Specifies whether or not server certificates are verified when creating
...@@ -1179,6 +1174,9 @@ to speed up repeated connections from the same clients. ...@@ -1179,6 +1174,9 @@ to speed up repeated connections from the same clients.
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
give the currently selected cipher. give the currently selected cipher.
OpenSSL 1.1.1 has TLS 1.3 cipher suites enabled by default. The suites
cannot be disabled with :meth:`~SSLContext.set_ciphers`.
.. method:: SSLContext.set_alpn_protocols(protocols) .. method:: SSLContext.set_alpn_protocols(protocols)
Specify which protocols the socket should advertise during the SSL/TLS Specify which protocols the socket should advertise during the SSL/TLS
......
...@@ -2796,19 +2796,24 @@ else: ...@@ -2796,19 +2796,24 @@ else:
sock.do_handshake() sock.do_handshake()
self.assertEqual(cm.exception.errno, errno.ENOTCONN) self.assertEqual(cm.exception.errno, errno.ENOTCONN)
def test_default_ciphers(self): def test_no_shared_ciphers(self):
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) server_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
try: server_context.load_cert_chain(SIGNED_CERTFILE)
# Force a set of weak ciphers on our client context client_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.set_ciphers("DES") client_context.verify_mode = ssl.CERT_REQUIRED
except ssl.SSLError: client_context.check_hostname = True
self.skipTest("no DES cipher available")
with ThreadedEchoServer(CERTFILE, # OpenSSL enables all TLS 1.3 ciphers, enforce TLS 1.2 for test
ssl_version=ssl.PROTOCOL_SSLv23, client_context.options |= ssl.OP_NO_TLSv1_3
chatty=False) as server: # Force different suites on client and master
with closing(context.wrap_socket(socket.socket())) as s: client_context.set_ciphers("AES128")
with self.assertRaises(ssl.SSLError): server_context.set_ciphers("AES256")
s.connect((HOST, server.port)) with ThreadedEchoServer(context=server_context) as server:
s = client_context.wrap_socket(
socket.socket(),
server_hostname="localhost")
with self.assertRaises(ssl.SSLError):
s.connect((HOST, server.port))
self.assertIn("no shared cipher", str(server.conn_errors[0])) self.assertIn("no shared cipher", str(server.conn_errors[0]))
def test_version_basic(self): def test_version_basic(self):
...@@ -2839,9 +2844,9 @@ else: ...@@ -2839,9 +2844,9 @@ else:
with context.wrap_socket(socket.socket()) as s: with context.wrap_socket(socket.socket()) as s:
s.connect((HOST, server.port)) s.connect((HOST, server.port))
self.assertIn(s.cipher()[0], [ self.assertIn(s.cipher()[0], [
'TLS13-AES-256-GCM-SHA384', 'TLS_AES_256_GCM_SHA384',
'TLS13-CHACHA20-POLY1305-SHA256', 'TLS_CHACHA20_POLY1305_SHA256',
'TLS13-AES-128-GCM-SHA256', 'TLS_AES_128_GCM_SHA256',
]) ])
@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL") @unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
......
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.
...@@ -41,15 +41,14 @@ import tarfile ...@@ -41,15 +41,14 @@ import tarfile
log = logging.getLogger("multissl") log = logging.getLogger("multissl")
OPENSSL_OLD_VERSIONS = [ OPENSSL_OLD_VERSIONS = [
"0.9.8zc",
"0.9.8zh",
"1.0.1u", "1.0.1u",
"1.0.2o",
] ]
OPENSSL_RECENT_VERSIONS = [ OPENSSL_RECENT_VERSIONS = [
"1.0.2", "1.0.2p",
"1.0.2l", "1.1.0i",
"1.1.0f", # "1.1.1",
] ]
LIBRESSL_OLD_VERSIONS = [ LIBRESSL_OLD_VERSIONS = [
...@@ -59,13 +58,15 @@ LIBRESSL_OLD_VERSIONS = [ ...@@ -59,13 +58,15 @@ LIBRESSL_OLD_VERSIONS = [
LIBRESSL_RECENT_VERSIONS = [ LIBRESSL_RECENT_VERSIONS = [
"2.5.5", "2.5.5",
"2.6.4", "2.6.5",
"2.7.1", "2.7.4",
] ]
# store files in ../multissl # store files in ../multissl
HERE = os.path.abspath(os.getcwd()) HERE = os.path.dirname(os.path.abspath(__file__))
MULTISSL_DIR = os.path.abspath(os.path.join(HERE, '..', 'multissl')) PYTHONROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
MULTISSL_DIR = os.path.abspath(os.path.join(PYTHONROOT, '..', 'multissl'))
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog='multissl', prog='multissl',
...@@ -77,7 +78,7 @@ parser = argparse.ArgumentParser( ...@@ -77,7 +78,7 @@ parser = argparse.ArgumentParser(
parser.add_argument( parser.add_argument(
'--debug', '--debug',
action='store_true', action='store_true',
help="Enable debug mode", help="Enable debug logging",
) )
parser.add_argument( parser.add_argument(
'--disable-ancient', '--disable-ancient',
...@@ -120,32 +121,49 @@ parser.add_argument( ...@@ -120,32 +121,49 @@ parser.add_argument(
help="Disable network tests." help="Disable network tests."
) )
parser.add_argument( parser.add_argument(
'--compile-only', '--steps',
action='store_true', choices=['library', 'modules', 'tests'],
help="Don't run tests, only compile _ssl.c and _hashopenssl.c." default='tests',
help=(
"Which steps to perform. 'library' downloads and compiles OpenSSL "
"or LibreSSL. 'module' also compiles Python modules. 'tests' builds "
"all and runs the test suite."
)
) )
parser.add_argument(
'--force',
action='store_true',
dest='force',
help="Force build and installation."
)
parser.add_argument(
'--keep-sources',
action='store_true',
dest='keep_sources',
help="Keep original sources for debugging."
)
class AbstractBuilder(object): class AbstractBuilder(object):
library = None library = None
url_template = None url_template = None
src_template = None src_template = None
build_template = None build_template = None
install_target = 'install'
module_files = ("Modules/_ssl.c", module_files = ("Modules/_ssl.c",
"Modules/_hashopenssl.c") "Modules/_hashopenssl.c")
module_libs = ("_ssl", "_hashlib") module_libs = ("_ssl", "_hashlib")
def __init__(self, version, compile_args=(), def __init__(self, version, args):
basedir=MULTISSL_DIR):
self.version = version self.version = version
self.compile_args = compile_args self.args = args
# installation directory # installation directory
self.install_dir = os.path.join( self.install_dir = os.path.join(
os.path.join(basedir, self.library.lower()), version os.path.join(args.base_directory, self.library.lower()), version
) )
# source file # source file
self.src_dir = os.path.join(basedir, 'src') self.src_dir = os.path.join(args.base_directory, 'src')
self.src_file = os.path.join( self.src_file = os.path.join(
self.src_dir, self.src_template.format(version)) self.src_dir, self.src_template.format(version))
# build directory (removed after install) # build directory (removed after install)
...@@ -252,21 +270,29 @@ class AbstractBuilder(object): ...@@ -252,21 +270,29 @@ class AbstractBuilder(object):
def _build_src(self): def _build_src(self):
"""Now build openssl""" """Now build openssl"""
log.info("Running build in {}".format(self.build_dir)) log.info("Running build in {}".format(self.build_dir))
cwd = self.build_dir cmd = [
cmd = ["./config", "shared", "--prefix={}".format(self.install_dir)] "./config",
cmd.extend(self.compile_args) "shared", "--debug",
"--prefix={}".format(self.install_dir)
]
env = os.environ.copy()
# set rpath
env["LD_RUN_PATH"] = self.lib_dir
self._subprocess_call(cmd, cwd=cwd) self._subprocess_call(cmd, cwd=cwd)
# Old OpenSSL versions do not support parallel builds. # Old OpenSSL versions do not support parallel builds.
self._subprocess_call(["make", "-j1"], cwd=cwd) self._subprocess_call(["make", "-j1"], cwd=cwd)
def _make_install(self, remove=True): def _make_install(self):
self._subprocess_call(["make", "-j1", "install"], cwd=self.build_dir) self._subprocess_call(
if remove: ["make", "-j1", self.install_target],
cwd=self.build_dir
)
if not self.args.keep_sources:
shutil.rmtree(self.build_dir) shutil.rmtree(self.build_dir)
def install(self): def install(self):
log.info(self.openssl_cli) log.info(self.openssl_cli)
if not self.has_openssl: if not self.has_openssl or self.args.force:
if not self.has_src: if not self.has_src:
self._download_src() self._download_src()
else: else:
...@@ -332,6 +358,8 @@ class BuildOpenSSL(AbstractBuilder): ...@@ -332,6 +358,8 @@ class BuildOpenSSL(AbstractBuilder):
url_template = "https://www.openssl.org/source/openssl-{}.tar.gz" url_template = "https://www.openssl.org/source/openssl-{}.tar.gz"
src_template = "openssl-{}.tar.gz" src_template = "openssl-{}.tar.gz"
build_template = "openssl-{}" build_template = "openssl-{}"
# only install software, skip docs
install_target = 'install_sw'
class BuildLibreSSL(AbstractBuilder): class BuildLibreSSL(AbstractBuilder):
...@@ -370,57 +398,64 @@ def main(): ...@@ -370,57 +398,64 @@ def main():
start = datetime.now() start = datetime.now()
for name in ['python', 'setup.py', 'Modules/_ssl.c']: if args.steps in {'modules', 'tests'}:
if not os.path.isfile(name): for name in ['setup.py', 'Modules/_ssl.c']:
if not os.path.isfile(os.path.join(PYTHONROOT, name)):
parser.error(
"Must be executed with ./python from CPython build dir"
)
if not os.path.samefile('python', sys.executable):
parser.error( parser.error(
"Must be executed from CPython build dir"
)
if not os.path.samefile('python', sys.executable):
parser.error(
"Must be executed with ./python from CPython build dir" "Must be executed with ./python from CPython build dir"
) )
# check for configure and run make # check for configure and run make
configure_make() configure_make()
# download and register builder # download and register builder
builds = [] builds = []
for version in args.openssl: for version in args.openssl:
build = BuildOpenSSL(version) build = BuildOpenSSL(
version,
args
)
build.install() build.install()
builds.append(build) builds.append(build)
for version in args.libressl: for version in args.libressl:
build = BuildLibreSSL(version) build = BuildLibreSSL(
version,
args
)
build.install() build.install()
builds.append(build) builds.append(build)
for build in builds: if args.steps in {'modules', 'tests'}:
try: for build in builds:
build.recompile_pymods() try:
build.check_pyssl() build.recompile_pymods()
if not args.compile_only: build.check_pyssl()
build.run_python_tests( if args.steps == 'tests':
tests=args.tests, build.run_python_tests(
network=args.network, tests=args.tests,
) network=args.network,
except Exception as e: )
log.exception("%s failed", build) except Exception as e:
print("{} failed: {}".format(build, e), file=sys.stderr) log.exception("%s failed", build)
sys.exit(2) print("{} failed: {}".format(build, e), file=sys.stderr)
sys.exit(2)
print("\n{} finished in {}".format(
"Tests" if not args.compile_only else "Builds", log.info("\n{} finished in {}".format(
datetime.now() - start args.steps.capitalize(),
)) datetime.now() - start
))
print('Python: ', sys.version) print('Python: ', sys.version)
if args.compile_only: if args.steps == 'tests':
print('Build only') if args.tests:
elif args.tests: print('Executed Tests:', ' '.join(args.tests))
print('Executed Tests:', ' '.join(args.tests)) else:
else: print('Executed all SSL tests.')
print('Executed all SSL tests.')
print('OpenSSL / LibreSSL versions:') print('OpenSSL / LibreSSL versions:')
for build in builds: for build in builds:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment