Skip to content

Commit 2c24f2c

Browse files
authored
[3.7] bpo-38275: Skip ssl tests for disabled versions (GH-16427)
test_ssl now handles disabled TLS/SSL versions better. OpenSSL's crypto policy and run-time settings are recognized and tests for disabled versions are skipped. Signed-off-by: Christian Heimes <christian@python.org> https://bugs.python.org/issue38275 (cherry picked from commit df6ac7e)
1 parent e6b5ed1 commit 2c24f2c

File tree

2 files changed

+150
-56
lines changed

2 files changed

+150
-56
lines changed

Lib/test/test_ssl.py

Lines changed: 146 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import platform
2020
import functools
2121
import sysconfig
22+
import functools
2223
try:
2324
import ctypes
2425
except ImportError:
@@ -142,6 +143,87 @@ def data_file(*name):
142143
OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0)
143144

144145

146+
def has_tls_protocol(protocol):
147+
"""Check if a TLS protocol is available and enabled
148+
149+
:param protocol: enum ssl._SSLMethod member or name
150+
:return: bool
151+
"""
152+
if isinstance(protocol, str):
153+
assert protocol.startswith('PROTOCOL_')
154+
protocol = getattr(ssl, protocol, None)
155+
if protocol is None:
156+
return False
157+
if protocol in {
158+
ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS_SERVER,
159+
ssl.PROTOCOL_TLS_CLIENT
160+
}:
161+
# auto-negotiate protocols are always available
162+
return True
163+
name = protocol.name
164+
return has_tls_version(name[len('PROTOCOL_'):])
165+
166+
167+
@functools.lru_cache()
168+
def has_tls_version(version):
169+
"""Check if a TLS/SSL version is enabled
170+
171+
:param version: TLS version name or ssl.TLSVersion member
172+
:return: bool
173+
"""
174+
if version == "SSLv2":
175+
# never supported and not even in TLSVersion enum
176+
return False
177+
178+
if isinstance(version, str):
179+
version = ssl.TLSVersion.__members__[version]
180+
181+
# check compile time flags like ssl.HAS_TLSv1_2
182+
if not getattr(ssl, f'HAS_{version.name}'):
183+
return False
184+
185+
# check runtime and dynamic crypto policy settings. A TLS version may
186+
# be compiled in but disabled by a policy or config option.
187+
ctx = ssl.SSLContext()
188+
if (
189+
hasattr(ctx, 'minimum_version') and
190+
ctx.minimum_version != ssl.TLSVersion.MINIMUM_SUPPORTED and
191+
version < ctx.minimum_version
192+
):
193+
return False
194+
if (
195+
hasattr(ctx, 'maximum_version') and
196+
ctx.maximum_version != ssl.TLSVersion.MAXIMUM_SUPPORTED and
197+
version > ctx.maximum_version
198+
):
199+
return False
200+
201+
return True
202+
203+
204+
def requires_tls_version(version):
205+
"""Decorator to skip tests when a required TLS version is not available
206+
207+
:param version: TLS version name or ssl.TLSVersion member
208+
:return:
209+
"""
210+
def decorator(func):
211+
@functools.wraps(func)
212+
def wrapper(*args, **kw):
213+
if not has_tls_version(version):
214+
raise unittest.SkipTest(f"{version} is not available.")
215+
else:
216+
return func(*args, **kw)
217+
return wrapper
218+
return decorator
219+
220+
221+
requires_minimum_version = unittest.skipUnless(
222+
hasattr(ssl.SSLContext, 'minimum_version'),
223+
"required OpenSSL >= 1.1.0g"
224+
)
225+
226+
145227
def handle_error(prefix):
146228
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
147229
if support.verbose:
@@ -1124,19 +1206,23 @@ def test_hostname_checks_common_name(self):
11241206
with self.assertRaises(AttributeError):
11251207
ctx.hostname_checks_common_name = True
11261208

1127-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
1128-
"required OpenSSL 1.1.0g")
1209+
@requires_minimum_version
1210+
@unittest.skipIf(IS_LIBRESSL, "see bpo-34001")
11291211
def test_min_max_version(self):
11301212
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
11311213
# OpenSSL default is MINIMUM_SUPPORTED, however some vendors like
11321214
# Fedora override the setting to TLS 1.0.
1215+
minimum_range = {
1216+
# stock OpenSSL
1217+
ssl.TLSVersion.MINIMUM_SUPPORTED,
1218+
# Fedora 29 uses TLS 1.0 by default
1219+
ssl.TLSVersion.TLSv1,
1220+
# RHEL 8 uses TLS 1.2 by default
1221+
ssl.TLSVersion.TLSv1_2
1222+
}
1223+
11331224
self.assertIn(
1134-
ctx.minimum_version,
1135-
{ssl.TLSVersion.MINIMUM_SUPPORTED,
1136-
# Fedora 29 uses TLS 1.0 by default
1137-
ssl.TLSVersion.TLSv1,
1138-
# RHEL 8 uses TLS 1.2 by default
1139-
ssl.TLSVersion.TLSv1_2}
1225+
ctx.minimum_version, minimum_range
11401226
)
11411227
self.assertEqual(
11421228
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
@@ -1182,8 +1268,8 @@ def test_min_max_version(self):
11821268

11831269
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)
11841270

1185-
self.assertEqual(
1186-
ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED
1271+
self.assertIn(
1272+
ctx.minimum_version, minimum_range
11871273
)
11881274
self.assertEqual(
11891275
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
@@ -2723,6 +2809,8 @@ def test_echo(self):
27232809
for protocol in PROTOCOLS:
27242810
if protocol in {ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER}:
27252811
continue
2812+
if not has_tls_protocol(protocol):
2813+
continue
27262814
with self.subTest(protocol=ssl._PROTOCOL_NAMES[protocol]):
27272815
context = ssl.SSLContext(protocol)
27282816
context.load_cert_chain(CERTFILE)
@@ -3014,7 +3102,7 @@ def test_wrong_cert_tls12(self):
30143102
else:
30153103
self.fail("Use of invalid cert should have failed!")
30163104

3017-
@unittest.skipUnless(ssl.HAS_TLSv1_3, "Test needs TLS 1.3")
3105+
@requires_tls_version('TLSv1_3')
30183106
def test_wrong_cert_tls13(self):
30193107
client_context, server_context, hostname = testing_context()
30203108
# load client cert that is not signed by trusted CA
@@ -3109,9 +3197,7 @@ def test_ssl_cert_verify_error(self):
31093197
self.assertIn(msg, repr(e))
31103198
self.assertIn('certificate verify failed', repr(e))
31113199

3112-
@skip_if_broken_ubuntu_ssl
3113-
@unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv2'),
3114-
"OpenSSL is compiled without SSLv2 support")
3200+
@requires_tls_version('SSLv2')
31153201
def test_protocol_sslv2(self):
31163202
"""Connecting to an SSLv2 server with various client options"""
31173203
if support.verbose:
@@ -3120,7 +3206,7 @@ def test_protocol_sslv2(self):
31203206
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_OPTIONAL)
31213207
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_REQUIRED)
31223208
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_TLS, False)
3123-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3209+
if has_tls_version('SSLv3'):
31243210
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv3, False)
31253211
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_TLSv1, False)
31263212
# SSLv23 client with specific SSL options
@@ -3138,7 +3224,7 @@ def test_PROTOCOL_TLS(self):
31383224
"""Connecting to an SSLv23 server with various client options"""
31393225
if support.verbose:
31403226
sys.stdout.write("\n")
3141-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3227+
if has_tls_version('SSLv2'):
31423228
try:
31433229
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv2, True)
31443230
except OSError as x:
@@ -3147,43 +3233,44 @@ def test_PROTOCOL_TLS(self):
31473233
sys.stdout.write(
31483234
" SSL2 client to SSL23 server test unexpectedly failed:\n %s\n"
31493235
% str(x))
3150-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3236+
if has_tls_version('SSLv3'):
31513237
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False)
31523238
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True)
3153-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1')
3239+
if has_tls_version('TLSv1'):
3240+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1')
31543241

3155-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3242+
if has_tls_version('SSLv3'):
31563243
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_OPTIONAL)
31573244
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_OPTIONAL)
3158-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
3245+
if has_tls_version('TLSv1'):
3246+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
31593247

3160-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3248+
if has_tls_version('SSLv3'):
31613249
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_REQUIRED)
31623250
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_REQUIRED)
3163-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
3251+
if has_tls_version('TLSv1'):
3252+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
31643253

31653254
# Server with specific SSL options
3166-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3255+
if has_tls_version('SSLv3'):
31673256
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False,
31683257
server_options=ssl.OP_NO_SSLv3)
31693258
# Will choose TLSv1
31703259
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True,
31713260
server_options=ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
3172-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, False,
3173-
server_options=ssl.OP_NO_TLSv1)
3174-
3261+
if has_tls_version('TLSv1'):
3262+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, False,
3263+
server_options=ssl.OP_NO_TLSv1)
31753264

3176-
@skip_if_broken_ubuntu_ssl
3177-
@unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv3'),
3178-
"OpenSSL is compiled without SSLv3 support")
3265+
@requires_tls_version('SSLv3')
31793266
def test_protocol_sslv3(self):
31803267
"""Connecting to an SSLv3 server with various client options"""
31813268
if support.verbose:
31823269
sys.stdout.write("\n")
31833270
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3')
31843271
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_OPTIONAL)
31853272
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_REQUIRED)
3186-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3273+
if has_tls_version('SSLv2'):
31873274
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv2, False)
31883275
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLS, False,
31893276
client_options=ssl.OP_NO_SSLv3)
@@ -3193,44 +3280,40 @@ def test_protocol_sslv3(self):
31933280
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLS,
31943281
False, client_options=ssl.OP_NO_SSLv2)
31953282

3196-
@skip_if_broken_ubuntu_ssl
3283+
@requires_tls_version('TLSv1')
31973284
def test_protocol_tlsv1(self):
31983285
"""Connecting to a TLSv1 server with various client options"""
31993286
if support.verbose:
32003287
sys.stdout.write("\n")
32013288
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1')
32023289
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
32033290
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
3204-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3291+
if has_tls_version('SSLv2'):
32053292
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv2, False)
3206-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3293+
if has_tls_version('SSLv3'):
32073294
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv3, False)
32083295
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLS, False,
32093296
client_options=ssl.OP_NO_TLSv1)
32103297

3211-
@skip_if_broken_ubuntu_ssl
3212-
@unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_1"),
3213-
"TLS version 1.1 not supported.")
3298+
@requires_tls_version('TLSv1_1')
32143299
def test_protocol_tlsv1_1(self):
32153300
"""Connecting to a TLSv1.1 server with various client options.
32163301
Testing against older TLS versions."""
32173302
if support.verbose:
32183303
sys.stdout.write("\n")
32193304
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
3220-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3305+
if has_tls_version('SSLv2'):
32213306
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv2, False)
3222-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3307+
if has_tls_version('SSLv3'):
32233308
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False)
32243309
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLS, False,
32253310
client_options=ssl.OP_NO_TLSv1_1)
32263311

32273312
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
3228-
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1, False)
3229-
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_1, False)
3313+
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False)
3314+
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False)
32303315

3231-
@skip_if_broken_ubuntu_ssl
3232-
@unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_2"),
3233-
"TLS version 1.2 not supported.")
3316+
@requires_tls_version('TLSv1_2')
32343317
def test_protocol_tlsv1_2(self):
32353318
"""Connecting to a TLSv1.2 server with various client options.
32363319
Testing against older TLS versions."""
@@ -3239,9 +3322,9 @@ def test_protocol_tlsv1_2(self):
32393322
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2',
32403323
server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,
32413324
client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,)
3242-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3325+
if has_tls_version('SSLv2'):
32433326
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv2, False)
3244-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3327+
if has_tls_version('SSLv3'):
32453328
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv3, False)
32463329
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLS, False,
32473330
client_options=ssl.OP_NO_TLSv1_2)
@@ -3684,7 +3767,7 @@ def test_version_basic(self):
36843767
self.assertIs(s.version(), None)
36853768
self.assertIs(s._sslobj, None)
36863769
s.connect((HOST, server.port))
3687-
if IS_OPENSSL_1_1_1 and ssl.HAS_TLSv1_3:
3770+
if IS_OPENSSL_1_1_1 and has_tls_version('TLSv1_3'):
36883771
self.assertEqual(s.version(), 'TLSv1.3')
36893772
elif ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
36903773
self.assertEqual(s.version(), 'TLSv1.2')
@@ -3693,8 +3776,7 @@ def test_version_basic(self):
36933776
self.assertIs(s._sslobj, None)
36943777
self.assertIs(s.version(), None)
36953778

3696-
@unittest.skipUnless(ssl.HAS_TLSv1_3,
3697-
"test requires TLSv1.3 enabled OpenSSL")
3779+
@requires_tls_version('TLSv1_3')
36983780
def test_tls1_3(self):
36993781
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
37003782
context.load_cert_chain(CERTFILE)
@@ -3711,9 +3793,9 @@ def test_tls1_3(self):
37113793
})
37123794
self.assertEqual(s.version(), 'TLSv1.3')
37133795

3714-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
3715-
"required OpenSSL 1.1.0g")
3716-
def test_min_max_version(self):
3796+
@requires_minimum_version
3797+
@requires_tls_version('TLSv1_2')
3798+
def test_min_max_version_tlsv1_2(self):
37173799
client_context, server_context, hostname = testing_context()
37183800
# client TLSv1.0 to 1.2
37193801
client_context.minimum_version = ssl.TLSVersion.TLSv1
@@ -3728,7 +3810,13 @@ def test_min_max_version(self):
37283810
s.connect((HOST, server.port))
37293811
self.assertEqual(s.version(), 'TLSv1.2')
37303812

3813+
@requires_minimum_version
3814+
@requires_tls_version('TLSv1_1')
3815+
def test_min_max_version_tlsv1_1(self):
3816+
client_context, server_context, hostname = testing_context()
37313817
# client 1.0 to 1.2, server 1.0 to 1.1
3818+
client_context.minimum_version = ssl.TLSVersion.TLSv1
3819+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
37323820
server_context.minimum_version = ssl.TLSVersion.TLSv1
37333821
server_context.maximum_version = ssl.TLSVersion.TLSv1_1
37343822

@@ -3738,6 +3826,10 @@ def test_min_max_version(self):
37383826
s.connect((HOST, server.port))
37393827
self.assertEqual(s.version(), 'TLSv1.1')
37403828

3829+
@requires_minimum_version
3830+
@requires_tls_version('TLSv1_2')
3831+
def test_min_max_version_mismatch(self):
3832+
client_context, server_context, hostname = testing_context()
37413833
# client 1.0, server 1.2 (mismatch)
37423834
server_context.minimum_version = ssl.TLSVersion.TLSv1_2
37433835
server_context.maximum_version = ssl.TLSVersion.TLSv1_2
@@ -3750,10 +3842,8 @@ def test_min_max_version(self):
37503842
s.connect((HOST, server.port))
37513843
self.assertIn("alert", str(e.exception))
37523844

3753-
3754-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
3755-
"required OpenSSL 1.1.0g")
3756-
@unittest.skipUnless(ssl.HAS_SSLv3, "requires SSLv3 support")
3845+
@requires_minimum_version
3846+
@requires_tls_version('SSLv3')
37573847
def test_min_max_version_sslv3(self):
37583848
client_context, server_context, hostname = testing_context()
37593849
server_context.minimum_version = ssl.TLSVersion.SSLv3
@@ -4272,7 +4362,7 @@ def test_session_handling(self):
42724362
'Session refers to a different SSLContext.')
42734363

42744364

4275-
@unittest.skipUnless(ssl.HAS_TLSv1_3, "Test needs TLS 1.3")
4365+
@unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3")
42764366
class TestPostHandshakeAuth(unittest.TestCase):
42774367
def test_pha_setter(self):
42784368
protocols = [
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
test_ssl now handles disabled TLS/SSL versions better. OpenSSL's crypto
2+
policy and run-time settings are recognized and tests for disabled versions
3+
are skipped. Tests also accept more TLS minimum_versions for platforms that
4+
override OpenSSL's default with strict settings.

0 commit comments

Comments
 (0)