Skip to content

Commit c85d841

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 1f72fb5 commit c85d841

File tree

4 files changed

+96
-38
lines changed

4 files changed

+96
-38
lines changed

Lib/test/test_asyncio/test_events.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1125,12 +1125,16 @@ def test_create_server_ssl_match_failed(self):
11251125
# incorrect server_hostname
11261126
f_c = self.loop.create_connection(MyProto, host, port,
11271127
ssl=sslcontext_client)
1128+
1129+
# Allow for flexible libssl error messages.
1130+
regex = re.compile(r"""(
1131+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
1132+
|
1133+
CERTIFICATE_VERIFY_FAILED # AWS-LC
1134+
)""", re.X)
11281135
with mock.patch.object(self.loop, 'call_exception_handler'):
11291136
with test_utils.disable_logger():
1130-
with self.assertRaisesRegex(
1131-
ssl.CertificateError,
1132-
"IP address mismatch, certificate is not valid for "
1133-
"'127.0.0.1'"):
1137+
with self.assertRaisesRegex(ssl.CertificateError, regex):
11341138
self.loop.run_until_complete(f_c)
11351139

11361140
# close connection

Lib/test/test_imaplib.py

+15-7
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, run_with_tz, run_with_locale, cpython_only, requires_resource
@@ -558,9 +559,13 @@ def test_ssl_raises(self):
558559
self.assertEqual(ssl_context.check_hostname, True)
559560
ssl_context.load_verify_locations(CAFILE)
560561

561-
with self.assertRaisesRegex(ssl.CertificateError,
562-
"IP address mismatch, certificate is not valid for "
563-
"'127.0.0.1'"):
562+
# Allow for flexible libssl error messages.
563+
regex = re.compile(r"""(
564+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
565+
|
566+
CERTIFICATE_VERIFY_FAILED # AWS-LC
567+
)""", re.X)
568+
with self.assertRaisesRegex(ssl.CertificateError, regex):
564569
_, server = self._setup(SimpleIMAPHandler)
565570
client = self.imap_class(*server.server_address,
566571
ssl_context=ssl_context)
@@ -954,10 +959,13 @@ def test_ssl_verified(self):
954959
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
955960
ssl_context.load_verify_locations(CAFILE)
956961

957-
with self.assertRaisesRegex(
958-
ssl.CertificateError,
959-
"IP address mismatch, certificate is not valid for "
960-
"'127.0.0.1'"):
962+
# Allow for flexible libssl error messages.
963+
regex = re.compile(r"""(
964+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
965+
|
966+
CERTIFICATE_VERIFY_FAILED # AWS-LC
967+
)""", re.X)
968+
with self.assertRaisesRegex(ssl.CertificateError, regex):
961969
with self.reaped_server(SimpleIMAPHandler) as server:
962970
client = self.imap_class(*server.server_address,
963971
ssl_context=ssl_context)

Lib/test/test_ssl.py

+70-27
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def test_openssl_version(self):
551551
else:
552552
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
553553
self.assertTrue(
554-
s.startswith((openssl_ver, libressl_ver)),
554+
s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
555555
(s, t, hex(n))
556556
)
557557

@@ -1169,24 +1169,30 @@ def test_load_cert_chain(self):
11691169
with self.assertRaises(OSError) as cm:
11701170
ctx.load_cert_chain(NONEXISTINGCERT)
11711171
self.assertEqual(cm.exception.errno, errno.ENOENT)
1172-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1172+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11731173
ctx.load_cert_chain(BADCERT)
1174-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1174+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11751175
ctx.load_cert_chain(EMPTYCERT)
11761176
# Separate key and cert
11771177
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
11781178
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
11791179
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
11801180
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
1181-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1181+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11821182
ctx.load_cert_chain(ONLYCERT)
1183-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1183+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11841184
ctx.load_cert_chain(ONLYKEY)
1185-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1185+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11861186
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
11871187
# Mismatching key and cert
11881188
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
1189-
with self.assertRaisesRegex(ssl.SSLError, "key values mismatch"):
1189+
# Allow for flexible libssl error messages.
1190+
regex = re.compile(r"""(
1191+
key values mismatch # OpenSSL
1192+
|
1193+
KEY_VALUES_MISMATCH # AWS-LC
1194+
)""", re.X)
1195+
with self.assertRaisesRegex(ssl.SSLError, regex):
11901196
ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY)
11911197
# Password protected key and cert
11921198
ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD)
@@ -1254,7 +1260,7 @@ def test_load_verify_locations(self):
12541260
with self.assertRaises(OSError) as cm:
12551261
ctx.load_verify_locations(NONEXISTINGCERT)
12561262
self.assertEqual(cm.exception.errno, errno.ENOENT)
1257-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1263+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
12581264
ctx.load_verify_locations(BADCERT)
12591265
ctx.load_verify_locations(CERTFILE, CAPATH)
12601266
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
@@ -1662,9 +1668,10 @@ def test_lib_reason(self):
16621668
with self.assertRaises(ssl.SSLError) as cm:
16631669
ctx.load_dh_params(CERTFILE)
16641670
self.assertEqual(cm.exception.library, 'PEM')
1665-
self.assertEqual(cm.exception.reason, 'NO_START_LINE')
1671+
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
1672+
self.assertRegex(cm.exception.reason, regex)
16661673
s = str(cm.exception)
1667-
self.assertTrue(s.startswith("[PEM: NO_START_LINE] no start line"), s)
1674+
self.assertTrue("NO_START_LINE" in s, s)
16681675

16691676
def test_subclass(self):
16701677
# Check that the appropriate SSLError subclass is raised
@@ -1844,7 +1851,13 @@ def test_connect_fail(self):
18441851
s = test_wrap_socket(socket.socket(socket.AF_INET),
18451852
cert_reqs=ssl.CERT_REQUIRED)
18461853
self.addCleanup(s.close)
1847-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
1854+
# Allow for flexible libssl error messages.
1855+
regex = re.compile(r"""(
1856+
certificate verify failed # OpenSSL
1857+
|
1858+
CERTIFICATE_VERIFY_FAILED # AWS-LC
1859+
)""", re.X)
1860+
self.assertRaisesRegex(ssl.SSLError, regex,
18481861
s.connect, self.server_addr)
18491862

18501863
def test_connect_ex(self):
@@ -1912,7 +1925,13 @@ def test_connect_with_context_fail(self):
19121925
server_hostname=SIGNED_CERTFILE_HOSTNAME
19131926
)
19141927
self.addCleanup(s.close)
1915-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
1928+
# Allow for flexible libssl error messages.
1929+
regex = re.compile(r"""(
1930+
certificate verify failed # OpenSSL
1931+
|
1932+
CERTIFICATE_VERIFY_FAILED # AWS-LC
1933+
)""", re.X)
1934+
self.assertRaisesRegex(ssl.SSLError, regex,
19161935
s.connect, self.server_addr)
19171936

19181937
def test_connect_capath(self):
@@ -2129,14 +2148,16 @@ def test_bio_handshake(self):
21292148
self.assertIsNone(sslobj.version())
21302149
self.assertIsNone(sslobj.shared_ciphers())
21312150
self.assertRaises(ValueError, sslobj.getpeercert)
2132-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2151+
# tls-unique is not defined for TLSv1.3
2152+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
2153+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
21332154
self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
21342155
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
21352156
self.assertTrue(sslobj.cipher())
21362157
self.assertIsNone(sslobj.shared_ciphers())
21372158
self.assertIsNotNone(sslobj.version())
21382159
self.assertTrue(sslobj.getpeercert())
2139-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2160+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
21402161
self.assertTrue(sslobj.get_channel_binding('tls-unique'))
21412162
try:
21422163
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
@@ -2861,11 +2882,16 @@ def test_crl_check(self):
28612882
client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
28622883

28632884
server = ThreadedEchoServer(context=server_context, chatty=True)
2885+
# Allow for flexible libssl error messages.
2886+
regex = re.compile(r"""(
2887+
certificate verify failed # OpenSSL
2888+
|
2889+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2890+
)""", re.X)
28642891
with server:
28652892
with client_context.wrap_socket(socket.socket(),
28662893
server_hostname=hostname) as s:
2867-
with self.assertRaisesRegex(ssl.SSLError,
2868-
"certificate verify failed"):
2894+
with self.assertRaisesRegex(ssl.SSLError, regex):
28692895
s.connect((HOST, server.port))
28702896

28712897
# now load a CRL file. The CRL file is signed by the CA.
@@ -2896,12 +2922,16 @@ def test_check_hostname(self):
28962922

28972923
# incorrect hostname should raise an exception
28982924
server = ThreadedEchoServer(context=server_context, chatty=True)
2925+
# Allow for flexible libssl error messages.
2926+
regex = re.compile(r"""(
2927+
certificate verify failed # OpenSSL
2928+
|
2929+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2930+
)""", re.X)
28992931
with server:
29002932
with client_context.wrap_socket(socket.socket(),
29012933
server_hostname="invalid") as s:
2902-
with self.assertRaisesRegex(
2903-
ssl.CertificateError,
2904-
"Hostname mismatch, certificate is not valid for 'invalid'."):
2934+
with self.assertRaisesRegex(ssl.CertificateError, regex):
29052935
s.connect((HOST, server.port))
29062936

29072937
# missing server_hostname arg should cause an exception, too
@@ -3137,7 +3167,7 @@ def test_wrong_cert_tls13(self):
31373167
s.connect((HOST, server.port))
31383168
with self.assertRaisesRegex(
31393169
ssl.SSLError,
3140-
'alert unknown ca|EOF occurred'
3170+
'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA'
31413171
):
31423172
# TLS 1.3 perform client cert exchange after handshake
31433173
s.write(b'data')
@@ -3201,13 +3231,21 @@ def test_ssl_cert_verify_error(self):
32013231
server_hostname=SIGNED_CERTFILE_HOSTNAME) as s:
32023232
try:
32033233
s.connect((HOST, server.port))
3234+
self.fail("Expected connection failure")
32043235
except ssl.SSLError as e:
32053236
msg = 'unable to get local issuer certificate'
32063237
self.assertIsInstance(e, ssl.SSLCertVerificationError)
32073238
self.assertEqual(e.verify_code, 20)
32083239
self.assertEqual(e.verify_message, msg)
3209-
self.assertIn(msg, repr(e))
3210-
self.assertIn('certificate verify failed', repr(e))
3240+
# Allow for flexible libssl error messages.
3241+
regex = f"({msg}|CERTIFICATE_VERIFY_FAILED)"
3242+
self.assertRegex(repr(e), regex)
3243+
regex = re.compile(r"""(
3244+
certificate verify failed # OpenSSL
3245+
|
3246+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3247+
)""", re.X)
3248+
self.assertRegex(repr(e), regex)
32113249

32123250
def test_PROTOCOL_TLS(self):
32133251
"""Connecting to an SSLv23 server with various client options"""
@@ -3739,7 +3777,7 @@ def test_no_shared_ciphers(self):
37393777
server_hostname=hostname) as s:
37403778
with self.assertRaises(OSError):
37413779
s.connect((HOST, server.port))
3742-
self.assertIn("no shared cipher", server.conn_errors[0])
3780+
self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0])
37433781

37443782
def test_version_basic(self):
37453783
"""
@@ -3827,7 +3865,7 @@ def test_min_max_version_mismatch(self):
38273865
server_hostname=hostname) as s:
38283866
with self.assertRaises(ssl.SSLError) as e:
38293867
s.connect((HOST, server.port))
3830-
self.assertIn("alert", str(e.exception))
3868+
self.assertRegex("(alert|ALERT)", str(e.exception))
38313869

38323870
@requires_tls_version('SSLv3')
38333871
def test_min_max_version_sslv3(self):
@@ -3869,6 +3907,10 @@ def test_tls_unique_channel_binding(self):
38693907

38703908
client_context, server_context, hostname = testing_context()
38713909

3910+
# tls-unique is not defined for TLSv1.3
3911+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
3912+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
3913+
38723914
server = ThreadedEchoServer(context=server_context,
38733915
chatty=True,
38743916
connectionchatty=False)
@@ -3969,7 +4011,7 @@ def test_dh_params(self):
39694011
cipher = stats["cipher"][0]
39704012
parts = cipher.split("-")
39714013
if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts:
3972-
self.fail("Non-DH cipher: " + cipher[0])
4014+
self.fail("Non-DH key exchange: " + cipher[0])
39734015

39744016
def test_ecdh_curve(self):
39754017
# server secp384r1, client auto
@@ -4136,8 +4178,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
41364178
chatty=False,
41374179
sni_name='supermessage')
41384180

4139-
self.assertEqual(cm.exception.reason,
4140-
'SSLV3_ALERT_HANDSHAKE_FAILURE')
4181+
# Allow for flexible libssl error messages.
4182+
regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)"
4183+
self.assertRegex(regex, cm.exception.reason)
41414184
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)
41424185

41434186
def test_sni_callback_wrong_return_type(self):
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)