Skip to content

Commit 1931132

Browse files
authored
[3.8] bpo-38275: Skip ssl tests for disabled versions (GH-16386) (GH-16425)
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 <[email protected]> https://bugs.python.org/issue38275 (cherry picked from commit df6ac7e)
1 parent c989340 commit 1931132

File tree

2 files changed

+149
-51
lines changed

2 files changed

+149
-51
lines changed

Lib/test/test_ssl.py

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

145146

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

1107-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
1108-
"required OpenSSL 1.1.0g")
1189+
@requires_minimum_version
11091190
@unittest.skipIf(IS_LIBRESSL, "see bpo-34001")
11101191
def test_min_max_version(self):
11111192
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
11121193
# OpenSSL default is MINIMUM_SUPPORTED, however some vendors like
11131194
# Fedora override the setting to TLS 1.0.
1195+
minimum_range = {
1196+
# stock OpenSSL
1197+
ssl.TLSVersion.MINIMUM_SUPPORTED,
1198+
# Fedora 29 uses TLS 1.0 by default
1199+
ssl.TLSVersion.TLSv1,
1200+
# RHEL 8 uses TLS 1.2 by default
1201+
ssl.TLSVersion.TLSv1_2
1202+
}
1203+
11141204
self.assertIn(
1115-
ctx.minimum_version,
1116-
{ssl.TLSVersion.MINIMUM_SUPPORTED,
1117-
# Fedora 29 uses TLS 1.0 by default
1118-
ssl.TLSVersion.TLSv1,
1119-
# RHEL 8 uses TLS 1.2 by default
1120-
ssl.TLSVersion.TLSv1_2}
1205+
ctx.minimum_version, minimum_range
11211206
)
11221207
self.assertEqual(
11231208
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
@@ -1163,8 +1248,8 @@ def test_min_max_version(self):
11631248

11641249
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)
11651250

1166-
self.assertEqual(
1167-
ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED
1251+
self.assertIn(
1252+
ctx.minimum_version, minimum_range
11681253
)
11691254
self.assertEqual(
11701255
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
@@ -2717,6 +2802,8 @@ def test_echo(self):
27172802
for protocol in PROTOCOLS:
27182803
if protocol in {ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER}:
27192804
continue
2805+
if not has_tls_protocol(protocol):
2806+
continue
27202807
with self.subTest(protocol=ssl._PROTOCOL_NAMES[protocol]):
27212808
context = ssl.SSLContext(protocol)
27222809
context.load_cert_chain(CERTFILE)
@@ -3008,7 +3095,7 @@ def test_wrong_cert_tls12(self):
30083095
else:
30093096
self.fail("Use of invalid cert should have failed!")
30103097

3011-
@unittest.skipUnless(ssl.HAS_TLSv1_3, "Test needs TLS 1.3")
3098+
@requires_tls_version('TLSv1_3')
30123099
def test_wrong_cert_tls13(self):
30133100
client_context, server_context, hostname = testing_context()
30143101
# load client cert that is not signed by trusted CA
@@ -3103,8 +3190,7 @@ def test_ssl_cert_verify_error(self):
31033190
self.assertIn(msg, repr(e))
31043191
self.assertIn('certificate verify failed', repr(e))
31053192

3106-
@unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv2'),
3107-
"OpenSSL is compiled without SSLv2 support")
3193+
@requires_tls_version('SSLv2')
31083194
def test_protocol_sslv2(self):
31093195
"""Connecting to an SSLv2 server with various client options"""
31103196
if support.verbose:
@@ -3113,7 +3199,7 @@ def test_protocol_sslv2(self):
31133199
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_OPTIONAL)
31143200
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv2, True, ssl.CERT_REQUIRED)
31153201
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_TLS, False)
3116-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3202+
if has_tls_version('SSLv3'):
31173203
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_SSLv3, False)
31183204
try_protocol_combo(ssl.PROTOCOL_SSLv2, ssl.PROTOCOL_TLSv1, False)
31193205
# SSLv23 client with specific SSL options
@@ -3130,7 +3216,7 @@ def test_PROTOCOL_TLS(self):
31303216
"""Connecting to an SSLv23 server with various client options"""
31313217
if support.verbose:
31323218
sys.stdout.write("\n")
3133-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3219+
if has_tls_version('SSLv2'):
31343220
try:
31353221
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv2, True)
31363222
except OSError as x:
@@ -3139,42 +3225,44 @@ def test_PROTOCOL_TLS(self):
31393225
sys.stdout.write(
31403226
" SSL2 client to SSL23 server test unexpectedly failed:\n %s\n"
31413227
% str(x))
3142-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3228+
if has_tls_version('SSLv3'):
31433229
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False)
31443230
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True)
3145-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1')
3231+
if has_tls_version('TLSv1'):
3232+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1')
31463233

3147-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3234+
if has_tls_version('SSLv3'):
31483235
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_OPTIONAL)
31493236
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_OPTIONAL)
3150-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
3237+
if has_tls_version('TLSv1'):
3238+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
31513239

3152-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3240+
if has_tls_version('SSLv3'):
31533241
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_REQUIRED)
31543242
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_REQUIRED)
3155-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
3243+
if has_tls_version('TLSv1'):
3244+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
31563245

31573246
# Server with specific SSL options
3158-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3247+
if has_tls_version('SSLv3'):
31593248
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False,
31603249
server_options=ssl.OP_NO_SSLv3)
31613250
# Will choose TLSv1
31623251
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True,
31633252
server_options=ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
3164-
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, False,
3165-
server_options=ssl.OP_NO_TLSv1)
3166-
3253+
if has_tls_version('TLSv1'):
3254+
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, False,
3255+
server_options=ssl.OP_NO_TLSv1)
31673256

3168-
@unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv3'),
3169-
"OpenSSL is compiled without SSLv3 support")
3257+
@requires_tls_version('SSLv3')
31703258
def test_protocol_sslv3(self):
31713259
"""Connecting to an SSLv3 server with various client options"""
31723260
if support.verbose:
31733261
sys.stdout.write("\n")
31743262
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3')
31753263
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_OPTIONAL)
31763264
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_REQUIRED)
3177-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3265+
if has_tls_version('SSLv2'):
31783266
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv2, False)
31793267
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLS, False,
31803268
client_options=ssl.OP_NO_SSLv3)
@@ -3184,41 +3272,40 @@ def test_protocol_sslv3(self):
31843272
try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLS,
31853273
False, client_options=ssl.OP_NO_SSLv2)
31863274

3275+
@requires_tls_version('TLSv1')
31873276
def test_protocol_tlsv1(self):
31883277
"""Connecting to a TLSv1 server with various client options"""
31893278
if support.verbose:
31903279
sys.stdout.write("\n")
31913280
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1')
31923281
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
31933282
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
3194-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3283+
if has_tls_version('SSLv2'):
31953284
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv2, False)
3196-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3285+
if has_tls_version('SSLv3'):
31973286
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv3, False)
31983287
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLS, False,
31993288
client_options=ssl.OP_NO_TLSv1)
32003289

3201-
@unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_1"),
3202-
"TLS version 1.1 not supported.")
3290+
@requires_tls_version('TLSv1_1')
32033291
def test_protocol_tlsv1_1(self):
32043292
"""Connecting to a TLSv1.1 server with various client options.
32053293
Testing against older TLS versions."""
32063294
if support.verbose:
32073295
sys.stdout.write("\n")
32083296
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
3209-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3297+
if has_tls_version('SSLv2'):
32103298
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv2, False)
3211-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3299+
if has_tls_version('SSLv3'):
32123300
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False)
32133301
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLS, False,
32143302
client_options=ssl.OP_NO_TLSv1_1)
32153303

32163304
try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
3217-
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1, False)
3218-
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_1, False)
3305+
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False)
3306+
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False)
32193307

3220-
@unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_2"),
3221-
"TLS version 1.2 not supported.")
3308+
@requires_tls_version('TLSv1_2')
32223309
def test_protocol_tlsv1_2(self):
32233310
"""Connecting to a TLSv1.2 server with various client options.
32243311
Testing against older TLS versions."""
@@ -3227,9 +3314,9 @@ def test_protocol_tlsv1_2(self):
32273314
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2',
32283315
server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,
32293316
client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,)
3230-
if hasattr(ssl, 'PROTOCOL_SSLv2'):
3317+
if has_tls_version('SSLv2'):
32313318
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv2, False)
3232-
if hasattr(ssl, 'PROTOCOL_SSLv3'):
3319+
if has_tls_version('SSLv3'):
32333320
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv3, False)
32343321
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLS, False,
32353322
client_options=ssl.OP_NO_TLSv1_2)
@@ -3672,7 +3759,7 @@ def test_version_basic(self):
36723759
self.assertIs(s.version(), None)
36733760
self.assertIs(s._sslobj, None)
36743761
s.connect((HOST, server.port))
3675-
if IS_OPENSSL_1_1_1 and ssl.HAS_TLSv1_3:
3762+
if IS_OPENSSL_1_1_1 and has_tls_version('TLSv1_3'):
36763763
self.assertEqual(s.version(), 'TLSv1.3')
36773764
elif ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
36783765
self.assertEqual(s.version(), 'TLSv1.2')
@@ -3681,8 +3768,7 @@ def test_version_basic(self):
36813768
self.assertIs(s._sslobj, None)
36823769
self.assertIs(s.version(), None)
36833770

3684-
@unittest.skipUnless(ssl.HAS_TLSv1_3,
3685-
"test requires TLSv1.3 enabled OpenSSL")
3771+
@requires_tls_version('TLSv1_3')
36863772
def test_tls1_3(self):
36873773
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
36883774
context.load_cert_chain(CERTFILE)
@@ -3699,9 +3785,9 @@ def test_tls1_3(self):
36993785
})
37003786
self.assertEqual(s.version(), 'TLSv1.3')
37013787

3702-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
3703-
"required OpenSSL 1.1.0g")
3704-
def test_min_max_version(self):
3788+
@requires_minimum_version
3789+
@requires_tls_version('TLSv1_2')
3790+
def test_min_max_version_tlsv1_2(self):
37053791
client_context, server_context, hostname = testing_context()
37063792
# client TLSv1.0 to 1.2
37073793
client_context.minimum_version = ssl.TLSVersion.TLSv1
@@ -3716,7 +3802,13 @@ def test_min_max_version(self):
37163802
s.connect((HOST, server.port))
37173803
self.assertEqual(s.version(), 'TLSv1.2')
37183804

3805+
@requires_minimum_version
3806+
@requires_tls_version('TLSv1_1')
3807+
def test_min_max_version_tlsv1_1(self):
3808+
client_context, server_context, hostname = testing_context()
37193809
# client 1.0 to 1.2, server 1.0 to 1.1
3810+
client_context.minimum_version = ssl.TLSVersion.TLSv1
3811+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
37203812
server_context.minimum_version = ssl.TLSVersion.TLSv1
37213813
server_context.maximum_version = ssl.TLSVersion.TLSv1_1
37223814

@@ -3726,6 +3818,10 @@ def test_min_max_version(self):
37263818
s.connect((HOST, server.port))
37273819
self.assertEqual(s.version(), 'TLSv1.1')
37283820

3821+
@requires_minimum_version
3822+
@requires_tls_version('TLSv1_2')
3823+
def test_min_max_version_mismatch(self):
3824+
client_context, server_context, hostname = testing_context()
37293825
# client 1.0, server 1.2 (mismatch)
37303826
server_context.maximum_version = ssl.TLSVersion.TLSv1_2
37313827
server_context.minimum_version = ssl.TLSVersion.TLSv1_2
@@ -3738,10 +3834,8 @@ def test_min_max_version(self):
37383834
s.connect((HOST, server.port))
37393835
self.assertIn("alert", str(e.exception))
37403836

3741-
3742-
@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
3743-
"required OpenSSL 1.1.0g")
3744-
@unittest.skipUnless(ssl.HAS_SSLv3, "requires SSLv3 support")
3837+
@requires_minimum_version
3838+
@requires_tls_version('SSLv3')
37453839
def test_min_max_version_sslv3(self):
37463840
client_context, server_context, hostname = testing_context()
37473841
server_context.minimum_version = ssl.TLSVersion.SSLv3
@@ -4264,7 +4358,7 @@ def test_session_handling(self):
42644358
'Session refers to a different SSLContext.')
42654359

42664360

4267-
@unittest.skipUnless(ssl.HAS_TLSv1_3, "Test needs TLS 1.3")
4361+
@unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3")
42684362
class TestPostHandshakeAuth(unittest.TestCase):
42694363
def test_pha_setter(self):
42704364
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)