Skip to content

Commit e68158c

Browse files
gh-116333: Relax error string text expectations in SSL-related tests (GH-116334)
* Relax error string text expectations in SSL-related tests As suggested [here][1], this change relaxes the OpenSSL error string text expectations in a number of tests. This was specifically done in support of more easily building CPython [AWS-LC][2], but because AWS-LC is a fork of [BoringSSL][3], it should increase compatibility with that library as well. In addition to the error string relaxations, we also add some guards around the `tls-unique` channel binding being used with TLSv1.3, as that feature (described in [RFC 6929][4]) is [not defined][5] for TLSv1.3. [1]: https://discuss.python.org/t/support-building-ssl-and-hashlib-modules-against-aws-lc/44505/4 [2]: https://github.com/aws/aws-lc [3]: https://github.com/google/boringssl [4]: https://datatracker.ietf.org/doc/html/rfc5929#section-3 [5]: https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
1 parent 83518b3 commit e68158c

File tree

4 files changed

+100
-48
lines changed

4 files changed

+100
-48
lines changed

Lib/test/test_asyncio/test_events.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,12 +1106,16 @@ def test_create_server_ssl_match_failed(self):
11061106
# incorrect server_hostname
11071107
f_c = self.loop.create_connection(MyProto, host, port,
11081108
ssl=sslcontext_client)
1109+
1110+
# Allow for flexible libssl error messages.
1111+
regex = re.compile(r"""(
1112+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
1113+
|
1114+
CERTIFICATE_VERIFY_FAILED # AWS-LC
1115+
)""", re.X)
11091116
with mock.patch.object(self.loop, 'call_exception_handler'):
11101117
with test_utils.disable_logger():
1111-
with self.assertRaisesRegex(
1112-
ssl.CertificateError,
1113-
"IP address mismatch, certificate is not valid for "
1114-
"'127.0.0.1'"):
1118+
with self.assertRaisesRegex(ssl.CertificateError, regex):
11151119
self.loop.run_until_complete(f_c)
11161120

11171121
# close connection

Lib/test/test_imaplib.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import time
99
import calendar
1010
import threading
11+
import re
1112
import socket
1213

1314
from test.support import (verbose,
@@ -555,9 +556,13 @@ def test_ssl_raises(self):
555556
self.assertEqual(ssl_context.check_hostname, True)
556557
ssl_context.load_verify_locations(CAFILE)
557558

558-
with self.assertRaisesRegex(ssl.CertificateError,
559-
"IP address mismatch, certificate is not valid for "
560-
"'127.0.0.1'"):
559+
# Allow for flexible libssl error messages.
560+
regex = re.compile(r"""(
561+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
562+
|
563+
CERTIFICATE_VERIFY_FAILED # AWS-LC
564+
)""", re.X)
565+
with self.assertRaisesRegex(ssl.CertificateError, regex):
561566
_, server = self._setup(SimpleIMAPHandler)
562567
client = self.imap_class(*server.server_address,
563568
ssl_context=ssl_context)
@@ -960,10 +965,13 @@ def test_ssl_verified(self):
960965
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
961966
ssl_context.load_verify_locations(CAFILE)
962967

963-
with self.assertRaisesRegex(
964-
ssl.CertificateError,
965-
"IP address mismatch, certificate is not valid for "
966-
"'127.0.0.1'"):
968+
# Allow for flexible libssl error messages.
969+
regex = re.compile(r"""(
970+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
971+
|
972+
CERTIFICATE_VERIFY_FAILED # AWS-LC
973+
)""", re.X)
974+
with self.assertRaisesRegex(ssl.CertificateError, regex):
967975
with self.reaped_server(SimpleIMAPHandler) as server:
968976
client = self.imap_class(*server.server_address,
969977
ssl_context=ssl_context)

Lib/test/test_ssl.py

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ def test_openssl_version(self):
547547
else:
548548
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
549549
self.assertTrue(
550-
s.startswith((openssl_ver, libressl_ver)),
550+
s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
551551
(s, t, hex(n))
552552
)
553553

@@ -1378,24 +1378,30 @@ def test_load_cert_chain(self):
13781378
with self.assertRaises(OSError) as cm:
13791379
ctx.load_cert_chain(NONEXISTINGCERT)
13801380
self.assertEqual(cm.exception.errno, errno.ENOENT)
1381-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1381+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
13821382
ctx.load_cert_chain(BADCERT)
1383-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1383+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
13841384
ctx.load_cert_chain(EMPTYCERT)
13851385
# Separate key and cert
13861386
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
13871387
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
13881388
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
13891389
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
1390-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1390+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
13911391
ctx.load_cert_chain(ONLYCERT)
1392-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1392+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
13931393
ctx.load_cert_chain(ONLYKEY)
1394-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1394+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
13951395
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
13961396
# Mismatching key and cert
13971397
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
1398-
with self.assertRaisesRegex(ssl.SSLError, "key values mismatch"):
1398+
# Allow for flexible libssl error messages.
1399+
regex = re.compile(r"""(
1400+
key values mismatch # OpenSSL
1401+
|
1402+
KEY_VALUES_MISMATCH # AWS-LC
1403+
)""", re.X)
1404+
with self.assertRaisesRegex(ssl.SSLError, regex):
13991405
ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY)
14001406
# Password protected key and cert
14011407
ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD)
@@ -1463,7 +1469,7 @@ def test_load_verify_locations(self):
14631469
with self.assertRaises(OSError) as cm:
14641470
ctx.load_verify_locations(NONEXISTINGCERT)
14651471
self.assertEqual(cm.exception.errno, errno.ENOENT)
1466-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1472+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14671473
ctx.load_verify_locations(BADCERT)
14681474
ctx.load_verify_locations(CERTFILE, CAPATH)
14691475
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
@@ -1862,9 +1868,10 @@ def test_lib_reason(self):
18621868
with self.assertRaises(ssl.SSLError) as cm:
18631869
ctx.load_dh_params(CERTFILE)
18641870
self.assertEqual(cm.exception.library, 'PEM')
1865-
self.assertEqual(cm.exception.reason, 'NO_START_LINE')
1871+
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
1872+
self.assertRegex(cm.exception.reason, regex)
18661873
s = str(cm.exception)
1867-
self.assertTrue(s.startswith("[PEM: NO_START_LINE] no start line"), s)
1874+
self.assertTrue("NO_START_LINE" in s, s)
18681875

18691876
def test_subclass(self):
18701877
# Check that the appropriate SSLError subclass is raised
@@ -2041,7 +2048,13 @@ def test_connect_fail(self):
20412048
s = test_wrap_socket(socket.socket(socket.AF_INET),
20422049
cert_reqs=ssl.CERT_REQUIRED)
20432050
self.addCleanup(s.close)
2044-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
2051+
# Allow for flexible libssl error messages.
2052+
regex = re.compile(r"""(
2053+
certificate verify failed # OpenSSL
2054+
|
2055+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2056+
)""", re.X)
2057+
self.assertRaisesRegex(ssl.SSLError, regex,
20452058
s.connect, self.server_addr)
20462059

20472060
def test_connect_ex(self):
@@ -2109,7 +2122,13 @@ def test_connect_with_context_fail(self):
21092122
server_hostname=SIGNED_CERTFILE_HOSTNAME
21102123
)
21112124
self.addCleanup(s.close)
2112-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
2125+
# Allow for flexible libssl error messages.
2126+
regex = re.compile(r"""(
2127+
certificate verify failed # OpenSSL
2128+
|
2129+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2130+
)""", re.X)
2131+
self.assertRaisesRegex(ssl.SSLError, regex,
21132132
s.connect, self.server_addr)
21142133

21152134
def test_connect_capath(self):
@@ -2329,14 +2348,16 @@ def test_bio_handshake(self):
23292348
self.assertIsNone(sslobj.version())
23302349
self.assertIsNone(sslobj.shared_ciphers())
23312350
self.assertRaises(ValueError, sslobj.getpeercert)
2332-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2351+
# tls-unique is not defined for TLSv1.3
2352+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
2353+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
23332354
self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
23342355
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
23352356
self.assertTrue(sslobj.cipher())
23362357
self.assertIsNone(sslobj.shared_ciphers())
23372358
self.assertIsNotNone(sslobj.version())
23382359
self.assertTrue(sslobj.getpeercert())
2339-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2360+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
23402361
self.assertTrue(sslobj.get_channel_binding('tls-unique'))
23412362
try:
23422363
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
@@ -3058,11 +3079,16 @@ def test_crl_check(self):
30583079
client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
30593080

30603081
server = ThreadedEchoServer(context=server_context, chatty=True)
3082+
# Allow for flexible libssl error messages.
3083+
regex = re.compile(r"""(
3084+
certificate verify failed # OpenSSL
3085+
|
3086+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3087+
)""", re.X)
30613088
with server:
30623089
with client_context.wrap_socket(socket.socket(),
30633090
server_hostname=hostname) as s:
3064-
with self.assertRaisesRegex(ssl.SSLError,
3065-
"certificate verify failed"):
3091+
with self.assertRaisesRegex(ssl.SSLError, regex):
30663092
s.connect((HOST, server.port))
30673093

30683094
# now load a CRL file. The CRL file is signed by the CA.
@@ -3093,12 +3119,16 @@ def test_check_hostname(self):
30933119

30943120
# incorrect hostname should raise an exception
30953121
server = ThreadedEchoServer(context=server_context, chatty=True)
3122+
# Allow for flexible libssl error messages.
3123+
regex = re.compile(r"""(
3124+
certificate verify failed # OpenSSL
3125+
|
3126+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3127+
)""", re.X)
30963128
with server:
30973129
with client_context.wrap_socket(socket.socket(),
30983130
server_hostname="invalid") as s:
3099-
with self.assertRaisesRegex(
3100-
ssl.CertificateError,
3101-
"Hostname mismatch, certificate is not valid for 'invalid'."):
3131+
with self.assertRaisesRegex(ssl.CertificateError, regex):
31023132
s.connect((HOST, server.port))
31033133

31043134
# missing server_hostname arg should cause an exception, too
@@ -3291,21 +3321,15 @@ def test_wrong_cert_tls13(self):
32913321
suppress_ragged_eofs=False) as s:
32923322
# TLS 1.3 perform client cert exchange after handshake
32933323
s.connect((HOST, server.port))
3294-
try:
3324+
with self.assertRaisesRegex(
3325+
ssl.SSLError,
3326+
'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA'
3327+
):
3328+
# TLS 1.3 perform client cert exchange after handshake
32953329
s.write(b'data')
32963330
s.read(1000)
32973331
s.write(b'should have failed already')
32983332
s.read(1000)
3299-
except ssl.SSLError as e:
3300-
if support.verbose:
3301-
sys.stdout.write("\nSSLError is %r\n" % e)
3302-
except OSError as e:
3303-
if e.errno != errno.ECONNRESET:
3304-
raise
3305-
if support.verbose:
3306-
sys.stdout.write("\nsocket.error is %r\n" % e)
3307-
else:
3308-
self.fail("Use of invalid cert should have failed!")
33093333

33103334
def test_rude_shutdown(self):
33113335
"""A brutal shutdown of an SSL server should raise an OSError
@@ -3363,13 +3387,21 @@ def test_ssl_cert_verify_error(self):
33633387
server_hostname=SIGNED_CERTFILE_HOSTNAME) as s:
33643388
try:
33653389
s.connect((HOST, server.port))
3390+
self.fail("Expected connection failure")
33663391
except ssl.SSLError as e:
33673392
msg = 'unable to get local issuer certificate'
33683393
self.assertIsInstance(e, ssl.SSLCertVerificationError)
33693394
self.assertEqual(e.verify_code, 20)
33703395
self.assertEqual(e.verify_message, msg)
3371-
self.assertIn(msg, repr(e))
3372-
self.assertIn('certificate verify failed', repr(e))
3396+
# Allow for flexible libssl error messages.
3397+
regex = f"({msg}|CERTIFICATE_VERIFY_FAILED)"
3398+
self.assertRegex(repr(e), regex)
3399+
regex = re.compile(r"""(
3400+
certificate verify failed # OpenSSL
3401+
|
3402+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3403+
)""", re.X)
3404+
self.assertRegex(repr(e), regex)
33733405

33743406
@requires_tls_version('SSLv2')
33753407
def test_protocol_sslv2(self):
@@ -3916,7 +3948,7 @@ def test_no_shared_ciphers(self):
39163948
server_hostname=hostname) as s:
39173949
with self.assertRaises(OSError):
39183950
s.connect((HOST, server.port))
3919-
self.assertIn("no shared cipher", server.conn_errors[0])
3951+
self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0])
39203952

39213953
def test_version_basic(self):
39223954
"""
@@ -4004,7 +4036,7 @@ def test_min_max_version_mismatch(self):
40044036
server_hostname=hostname) as s:
40054037
with self.assertRaises(ssl.SSLError) as e:
40064038
s.connect((HOST, server.port))
4007-
self.assertIn("alert", str(e.exception))
4039+
self.assertRegex("(alert|ALERT)", str(e.exception))
40084040

40094041
@requires_tls_version('SSLv3')
40104042
def test_min_max_version_sslv3(self):
@@ -4046,6 +4078,10 @@ def test_tls_unique_channel_binding(self):
40464078

40474079
client_context, server_context, hostname = testing_context()
40484080

4081+
# tls-unique is not defined for TLSv1.3
4082+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
4083+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
4084+
40494085
server = ThreadedEchoServer(context=server_context,
40504086
chatty=True,
40514087
connectionchatty=False)
@@ -4132,7 +4168,7 @@ def test_dh_params(self):
41324168
cipher = stats["cipher"][0]
41334169
parts = cipher.split("-")
41344170
if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts:
4135-
self.fail("Non-DH cipher: " + cipher[0])
4171+
self.fail("Non-DH key exchange: " + cipher[0])
41364172

41374173
def test_ecdh_curve(self):
41384174
# server secp384r1, client auto
@@ -4299,8 +4335,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
42994335
chatty=False,
43004336
sni_name='supermessage')
43014337

4302-
self.assertEqual(cm.exception.reason,
4303-
'SSLV3_ALERT_HANDSHAKE_FAILURE')
4338+
# Allow for flexible libssl error messages.
4339+
regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)"
4340+
self.assertRegex(regex, cm.exception.reason)
43044341
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)
43054342

43064343
def test_sni_callback_wrong_return_type(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Tests of TLS related things (error codes, etc) were updated to be more
2+
lenient about specific error message strings and behaviors as seen in the
3+
BoringSSL and AWS-LC forks of OpenSSL.

0 commit comments

Comments
 (0)