Skip to content

Commit 1672218

Browse files
gh-116333: Relax error string text expectations in SSL-related tests (GH-116334)
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 6261322 commit 1672218

File tree

4 files changed

+96
-38
lines changed

4 files changed

+96
-38
lines changed

Lib/test/test_asyncio/test_events.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,12 +1103,16 @@ def test_create_server_ssl_match_failed(self):
11031103
# incorrect server_hostname
11041104
f_c = self.loop.create_connection(MyProto, host, port,
11051105
ssl=sslcontext_client)
1106+
1107+
# Allow for flexible libssl error messages.
1108+
regex = re.compile(r"""(
1109+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
1110+
|
1111+
CERTIFICATE_VERIFY_FAILED # AWS-LC
1112+
)""", re.X)
11061113
with mock.patch.object(self.loop, 'call_exception_handler'):
11071114
with test_utils.disable_logger():
1108-
with self.assertRaisesRegex(
1109-
ssl.CertificateError,
1110-
"IP address mismatch, certificate is not valid for "
1111-
"'127.0.0.1'"):
1115+
with self.assertRaisesRegex(ssl.CertificateError, regex):
11121116
self.loop.run_until_complete(f_c)
11131117

11141118
# 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,
@@ -561,9 +562,13 @@ def test_ssl_raises(self):
561562
self.assertEqual(ssl_context.check_hostname, True)
562563
ssl_context.load_verify_locations(CAFILE)
563564

564-
with self.assertRaisesRegex(ssl.CertificateError,
565-
"IP address mismatch, certificate is not valid for "
566-
"'127.0.0.1'"):
565+
# Allow for flexible libssl error messages.
566+
regex = re.compile(r"""(
567+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
568+
|
569+
CERTIFICATE_VERIFY_FAILED # AWS-LC
570+
)""", re.X)
571+
with self.assertRaisesRegex(ssl.CertificateError, regex):
567572
_, server = self._setup(SimpleIMAPHandler)
568573
client = self.imap_class(*server.server_address,
569574
ssl_context=ssl_context)
@@ -967,10 +972,13 @@ def test_ssl_verified(self):
967972
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
968973
ssl_context.load_verify_locations(CAFILE)
969974

970-
with self.assertRaisesRegex(
971-
ssl.CertificateError,
972-
"IP address mismatch, certificate is not valid for "
973-
"'127.0.0.1'"):
975+
# Allow for flexible libssl error messages.
976+
regex = re.compile(r"""(
977+
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
978+
|
979+
CERTIFICATE_VERIFY_FAILED # AWS-LC
980+
)""", re.X)
981+
with self.assertRaisesRegex(ssl.CertificateError, regex):
974982
with self.reaped_server(SimpleIMAPHandler) as server:
975983
client = self.imap_class(*server.server_address,
976984
ssl_context=ssl_context)

Lib/test/test_ssl.py

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ def test_openssl_version(self):
557557
else:
558558
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
559559
self.assertTrue(
560-
s.startswith((openssl_ver, libressl_ver)),
560+
s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
561561
(s, t, hex(n))
562562
)
563563

@@ -1404,24 +1404,30 @@ def test_load_cert_chain(self):
14041404
with self.assertRaises(OSError) as cm:
14051405
ctx.load_cert_chain(NONEXISTINGCERT)
14061406
self.assertEqual(cm.exception.errno, errno.ENOENT)
1407-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1407+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14081408
ctx.load_cert_chain(BADCERT)
1409-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1409+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14101410
ctx.load_cert_chain(EMPTYCERT)
14111411
# Separate key and cert
14121412
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
14131413
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
14141414
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
14151415
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
1416-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1416+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14171417
ctx.load_cert_chain(ONLYCERT)
1418-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1418+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14191419
ctx.load_cert_chain(ONLYKEY)
1420-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1420+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14211421
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
14221422
# Mismatching key and cert
14231423
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
1424-
with self.assertRaisesRegex(ssl.SSLError, "key values mismatch"):
1424+
# Allow for flexible libssl error messages.
1425+
regex = re.compile(r"""(
1426+
key values mismatch # OpenSSL
1427+
|
1428+
KEY_VALUES_MISMATCH # AWS-LC
1429+
)""", re.X)
1430+
with self.assertRaisesRegex(ssl.SSLError, regex):
14251431
ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY)
14261432
# Password protected key and cert
14271433
ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD)
@@ -1489,7 +1495,7 @@ def test_load_verify_locations(self):
14891495
with self.assertRaises(OSError) as cm:
14901496
ctx.load_verify_locations(NONEXISTINGCERT)
14911497
self.assertEqual(cm.exception.errno, errno.ENOENT)
1492-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1498+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
14931499
ctx.load_verify_locations(BADCERT)
14941500
ctx.load_verify_locations(CERTFILE, CAPATH)
14951501
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
@@ -1888,9 +1894,10 @@ def test_lib_reason(self):
18881894
with self.assertRaises(ssl.SSLError) as cm:
18891895
ctx.load_dh_params(CERTFILE)
18901896
self.assertEqual(cm.exception.library, 'PEM')
1891-
self.assertEqual(cm.exception.reason, 'NO_START_LINE')
1897+
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
1898+
self.assertRegex(cm.exception.reason, regex)
18921899
s = str(cm.exception)
1893-
self.assertTrue(s.startswith("[PEM: NO_START_LINE] no start line"), s)
1900+
self.assertTrue("NO_START_LINE" in s, s)
18941901

18951902
def test_subclass(self):
18961903
# Check that the appropriate SSLError subclass is raised
@@ -2070,7 +2077,13 @@ def test_connect_fail(self):
20702077
s = test_wrap_socket(socket.socket(socket.AF_INET),
20712078
cert_reqs=ssl.CERT_REQUIRED)
20722079
self.addCleanup(s.close)
2073-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
2080+
# Allow for flexible libssl error messages.
2081+
regex = re.compile(r"""(
2082+
certificate verify failed # OpenSSL
2083+
|
2084+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2085+
)""", re.X)
2086+
self.assertRaisesRegex(ssl.SSLError, regex,
20742087
s.connect, self.server_addr)
20752088

20762089
def test_connect_ex(self):
@@ -2138,7 +2151,13 @@ def test_connect_with_context_fail(self):
21382151
server_hostname=SIGNED_CERTFILE_HOSTNAME
21392152
)
21402153
self.addCleanup(s.close)
2141-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
2154+
# Allow for flexible libssl error messages.
2155+
regex = re.compile(r"""(
2156+
certificate verify failed # OpenSSL
2157+
|
2158+
CERTIFICATE_VERIFY_FAILED # AWS-LC
2159+
)""", re.X)
2160+
self.assertRaisesRegex(ssl.SSLError, regex,
21422161
s.connect, self.server_addr)
21432162

21442163
def test_connect_capath(self):
@@ -2355,14 +2374,16 @@ def test_bio_handshake(self):
23552374
self.assertIsNone(sslobj.version())
23562375
self.assertIsNone(sslobj.shared_ciphers())
23572376
self.assertRaises(ValueError, sslobj.getpeercert)
2358-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2377+
# tls-unique is not defined for TLSv1.3
2378+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
2379+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
23592380
self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
23602381
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
23612382
self.assertTrue(sslobj.cipher())
23622383
self.assertIsNone(sslobj.shared_ciphers())
23632384
self.assertIsNotNone(sslobj.version())
23642385
self.assertTrue(sslobj.getpeercert())
2365-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2386+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
23662387
self.assertTrue(sslobj.get_channel_binding('tls-unique'))
23672388
try:
23682389
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
@@ -3087,11 +3108,16 @@ def test_crl_check(self):
30873108
client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
30883109

30893110
server = ThreadedEchoServer(context=server_context, chatty=True)
3111+
# Allow for flexible libssl error messages.
3112+
regex = re.compile(r"""(
3113+
certificate verify failed # OpenSSL
3114+
|
3115+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3116+
)""", re.X)
30903117
with server:
30913118
with client_context.wrap_socket(socket.socket(),
30923119
server_hostname=hostname) as s:
3093-
with self.assertRaisesRegex(ssl.SSLError,
3094-
"certificate verify failed"):
3120+
with self.assertRaisesRegex(ssl.SSLError, regex):
30953121
s.connect((HOST, server.port))
30963122

30973123
# now load a CRL file. The CRL file is signed by the CA.
@@ -3122,12 +3148,16 @@ def test_check_hostname(self):
31223148

31233149
# incorrect hostname should raise an exception
31243150
server = ThreadedEchoServer(context=server_context, chatty=True)
3151+
# Allow for flexible libssl error messages.
3152+
regex = re.compile(r"""(
3153+
certificate verify failed # OpenSSL
3154+
|
3155+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3156+
)""", re.X)
31253157
with server:
31263158
with client_context.wrap_socket(socket.socket(),
31273159
server_hostname="invalid") as s:
3128-
with self.assertRaisesRegex(
3129-
ssl.CertificateError,
3130-
"Hostname mismatch, certificate is not valid for 'invalid'."):
3160+
with self.assertRaisesRegex(ssl.CertificateError, regex):
31313161
s.connect((HOST, server.port))
31323162

31333163
# missing server_hostname arg should cause an exception, too
@@ -3331,7 +3361,7 @@ def test_wrong_cert_tls13(self):
33313361
s.connect((HOST, server.port))
33323362
with self.assertRaisesRegex(
33333363
ssl.SSLError,
3334-
'alert unknown ca|EOF occurred'
3364+
'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA'
33353365
):
33363366
# TLS 1.3 perform client cert exchange after handshake
33373367
s.write(b'data')
@@ -3395,13 +3425,21 @@ def test_ssl_cert_verify_error(self):
33953425
server_hostname=SIGNED_CERTFILE_HOSTNAME) as s:
33963426
try:
33973427
s.connect((HOST, server.port))
3428+
self.fail("Expected connection failure")
33983429
except ssl.SSLError as e:
33993430
msg = 'unable to get local issuer certificate'
34003431
self.assertIsInstance(e, ssl.SSLCertVerificationError)
34013432
self.assertEqual(e.verify_code, 20)
34023433
self.assertEqual(e.verify_message, msg)
3403-
self.assertIn(msg, repr(e))
3404-
self.assertIn('certificate verify failed', repr(e))
3434+
# Allow for flexible libssl error messages.
3435+
regex = f"({msg}|CERTIFICATE_VERIFY_FAILED)"
3436+
self.assertRegex(repr(e), regex)
3437+
regex = re.compile(r"""(
3438+
certificate verify failed # OpenSSL
3439+
|
3440+
CERTIFICATE_VERIFY_FAILED # AWS-LC
3441+
)""", re.X)
3442+
self.assertRegex(repr(e), regex)
34053443

34063444
@requires_tls_version('SSLv2')
34073445
def test_protocol_sslv2(self):
@@ -3968,7 +4006,7 @@ def test_no_shared_ciphers(self):
39684006
server_hostname=hostname) as s:
39694007
with self.assertRaises(OSError):
39704008
s.connect((HOST, server.port))
3971-
self.assertIn("no shared cipher", server.conn_errors[0])
4009+
self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0])
39724010

39734011
def test_version_basic(self):
39744012
"""
@@ -4056,7 +4094,7 @@ def test_min_max_version_mismatch(self):
40564094
server_hostname=hostname) as s:
40574095
with self.assertRaises(ssl.SSLError) as e:
40584096
s.connect((HOST, server.port))
4059-
self.assertIn("alert", str(e.exception))
4097+
self.assertRegex("(alert|ALERT)", str(e.exception))
40604098

40614099
@requires_tls_version('SSLv3')
40624100
def test_min_max_version_sslv3(self):
@@ -4098,6 +4136,10 @@ def test_tls_unique_channel_binding(self):
40984136

40994137
client_context, server_context, hostname = testing_context()
41004138

4139+
# tls-unique is not defined for TLSv1.3
4140+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
4141+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
4142+
41014143
server = ThreadedEchoServer(context=server_context,
41024144
chatty=True,
41034145
connectionchatty=False)
@@ -4184,7 +4226,7 @@ def test_dh_params(self):
41844226
cipher = stats["cipher"][0]
41854227
parts = cipher.split("-")
41864228
if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts:
4187-
self.fail("Non-DH cipher: " + cipher[0])
4229+
self.fail("Non-DH key exchange: " + cipher[0])
41884230

41894231
def test_ecdh_curve(self):
41904232
# server secp384r1, client auto
@@ -4351,8 +4393,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
43514393
chatty=False,
43524394
sni_name='supermessage')
43534395

4354-
self.assertEqual(cm.exception.reason,
4355-
'SSLV3_ALERT_HANDSHAKE_FAILURE')
4396+
# Allow for flexible libssl error messages.
4397+
regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)"
4398+
self.assertRegex(regex, cm.exception.reason)
43564399
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)
43574400

43584401
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)