Skip to content

Commit 64c1f8f

Browse files
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 [4]: https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
1 parent eb22e2b commit 64c1f8f

File tree

3 files changed

+98
-38
lines changed

3 files changed

+98
-38
lines changed

Lib/test/test_asyncio/test_events.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,12 +1125,18 @@ 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 = "("
1131+
# OpenSSL
1132+
regex += "IP address mismatch, certificate is not valid for '127.0.0.1'"
1133+
regex += "|"
1134+
# AWS-LC/BoringSSL
1135+
regex += "CERTIFICATE_VERIFY_FAILED"
1136+
regex += ")"
11281137
with mock.patch.object(self.loop, 'call_exception_handler'):
11291138
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'"):
1139+
with self.assertRaisesRegex(ssl.CertificateError, regex):
11341140
self.loop.run_until_complete(f_c)
11351141

11361142
# close connection

Lib/test/test_imaplib.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -558,9 +558,15 @@ def test_ssl_raises(self):
558558
self.assertEqual(ssl_context.check_hostname, True)
559559
ssl_context.load_verify_locations(CAFILE)
560560

561-
with self.assertRaisesRegex(ssl.CertificateError,
562-
"IP address mismatch, certificate is not valid for "
563-
"'127.0.0.1'"):
561+
# Allow for flexible libssl error messages.
562+
regex = "("
563+
# OpenSSL
564+
regex += "IP address mismatch, certificate is not valid for '127.0.0.1'"
565+
regex += "|"
566+
# AWS-LC/BoringSSL
567+
regex += "CERTIFICATE_VERIFY_FAILED"
568+
regex += ")"
569+
with self.assertRaisesRegex(ssl.CertificateError, regex):
564570
_, server = self._setup(SimpleIMAPHandler)
565571
client = self.imap_class(*server.server_address,
566572
ssl_context=ssl_context)
@@ -954,10 +960,15 @@ def test_ssl_verified(self):
954960
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
955961
ssl_context.load_verify_locations(CAFILE)
956962

957-
with self.assertRaisesRegex(
958-
ssl.CertificateError,
959-
"IP address mismatch, certificate is not valid for "
960-
"'127.0.0.1'"):
963+
# Allow for flexible libssl error messages.
964+
regex = "("
965+
# OpenSSL
966+
regex += "IP address mismatch, certificate is not valid for '127.0.0.1'"
967+
regex += "|"
968+
# AWS-LC/BoringSSL
969+
regex += "CERTIFICATE_VERIFY_FAILED"
970+
regex += ")"
971+
with self.assertRaisesRegex(ssl.CertificateError, regex):
961972
with self.reaped_server(SimpleIMAPHandler) as server:
962973
client = self.imap_class(*server.server_address,
963974
ssl_context=ssl_context)

Lib/test/test_ssl.py

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ def test_openssl_version(self):
544544
else:
545545
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
546546
self.assertTrue(
547-
s.startswith((openssl_ver, libressl_ver)),
547+
s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
548548
(s, t, hex(n))
549549
)
550550

@@ -1162,24 +1162,30 @@ def test_load_cert_chain(self):
11621162
with self.assertRaises(OSError) as cm:
11631163
ctx.load_cert_chain(NONEXISTINGCERT)
11641164
self.assertEqual(cm.exception.errno, errno.ENOENT)
1165-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1165+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11661166
ctx.load_cert_chain(BADCERT)
1167-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1167+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11681168
ctx.load_cert_chain(EMPTYCERT)
11691169
# Separate key and cert
11701170
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
11711171
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
11721172
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
11731173
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
1174-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1174+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11751175
ctx.load_cert_chain(ONLYCERT)
1176-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1176+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11771177
ctx.load_cert_chain(ONLYKEY)
1178-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1178+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
11791179
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
11801180
# Mismatching key and cert
11811181
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
1182-
with self.assertRaisesRegex(ssl.SSLError, "key values mismatch"):
1182+
# Allow for flexible libssl error messages.
1183+
regex = "("
1184+
regex += "key values mismatch" # OpenSSL
1185+
regex += "|"
1186+
regex += "KEY_VALUES_MISMATCH" # AWS-LC/BoringSSL
1187+
regex += ")"
1188+
with self.assertRaisesRegex(ssl.SSLError, regex):
11831189
ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY)
11841190
# Password protected key and cert
11851191
ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD)
@@ -1247,7 +1253,7 @@ def test_load_verify_locations(self):
12471253
with self.assertRaises(OSError) as cm:
12481254
ctx.load_verify_locations(NONEXISTINGCERT)
12491255
self.assertEqual(cm.exception.errno, errno.ENOENT)
1250-
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
1256+
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
12511257
ctx.load_verify_locations(BADCERT)
12521258
ctx.load_verify_locations(CERTFILE, CAPATH)
12531259
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
@@ -1651,9 +1657,10 @@ def test_lib_reason(self):
16511657
with self.assertRaises(ssl.SSLError) as cm:
16521658
ctx.load_dh_params(CERTFILE)
16531659
self.assertEqual(cm.exception.library, 'PEM')
1654-
self.assertEqual(cm.exception.reason, 'NO_START_LINE')
1660+
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
1661+
self.assertRegex(cm.exception.reason, regex)
16551662
s = str(cm.exception)
1656-
self.assertTrue(s.startswith("[PEM: NO_START_LINE] no start line"), s)
1663+
self.assertTrue("NO_START_LINE" in s, s)
16571664

16581665
def test_subclass(self):
16591666
# Check that the appropriate SSLError subclass is raised
@@ -1833,7 +1840,13 @@ def test_connect_fail(self):
18331840
s = test_wrap_socket(socket.socket(socket.AF_INET),
18341841
cert_reqs=ssl.CERT_REQUIRED)
18351842
self.addCleanup(s.close)
1836-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
1843+
# Allow for flexible libssl error messages.
1844+
regex = "("
1845+
regex += "certificate verify failed" # OpenSSL
1846+
regex += "|"
1847+
regex += "CERTIFICATE_VERIFY_FAILED" # AWS-LC/BoringSSL
1848+
regex += ")"
1849+
self.assertRaisesRegex(ssl.SSLError, regex,
18371850
s.connect, self.server_addr)
18381851

18391852
def test_connect_ex(self):
@@ -1901,7 +1914,13 @@ def test_connect_with_context_fail(self):
19011914
server_hostname=SIGNED_CERTFILE_HOSTNAME
19021915
)
19031916
self.addCleanup(s.close)
1904-
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
1917+
# Allow for flexible libssl error messages.
1918+
regex = "("
1919+
regex += "certificate verify failed" # OpenSSL
1920+
regex += "|"
1921+
regex += "CERTIFICATE_VERIFY_FAILED" # AWS-LC/BoringSSL
1922+
regex += ")"
1923+
self.assertRaisesRegex(ssl.SSLError, regex,
19051924
s.connect, self.server_addr)
19061925

19071926
def test_connect_capath(self):
@@ -2118,14 +2137,16 @@ def test_bio_handshake(self):
21182137
self.assertIsNone(sslobj.version())
21192138
self.assertIsNone(sslobj.shared_ciphers())
21202139
self.assertRaises(ValueError, sslobj.getpeercert)
2121-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2140+
# tls-unique is not defined for TLSv1.3
2141+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
2142+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
21222143
self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
21232144
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
21242145
self.assertTrue(sslobj.cipher())
21252146
self.assertIsNone(sslobj.shared_ciphers())
21262147
self.assertIsNotNone(sslobj.version())
21272148
self.assertTrue(sslobj.getpeercert())
2128-
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
2149+
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
21292150
self.assertTrue(sslobj.get_channel_binding('tls-unique'))
21302151
try:
21312152
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
@@ -2850,11 +2871,16 @@ def test_crl_check(self):
28502871
client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
28512872

28522873
server = ThreadedEchoServer(context=server_context, chatty=True)
2874+
# Allow for flexible libssl error messages.
2875+
regex = "("
2876+
regex += "certificate verify failed" # OpenSSL
2877+
regex += "|"
2878+
regex += "CERTIFICATE_VERIFY_FAILED" # AWS-LC/BoringSSL
2879+
regex += ")"
28532880
with server:
28542881
with client_context.wrap_socket(socket.socket(),
28552882
server_hostname=hostname) as s:
2856-
with self.assertRaisesRegex(ssl.SSLError,
2857-
"certificate verify failed"):
2883+
with self.assertRaisesRegex(ssl.SSLError, regex):
28582884
s.connect((HOST, server.port))
28592885

28602886
# now load a CRL file. The CRL file is signed by the CA.
@@ -2885,12 +2911,16 @@ def test_check_hostname(self):
28852911

28862912
# incorrect hostname should raise an exception
28872913
server = ThreadedEchoServer(context=server_context, chatty=True)
2914+
# Allow for flexible libssl error messages.
2915+
regex = "("
2916+
regex += "Hostname mismatch, certificate is not valid" # OpenSSL
2917+
regex += "|"
2918+
regex += "CERTIFICATE_VERIFY_FAILED" # AWS-LC/BoringSSL
2919+
regex += ")"
28882920
with server:
28892921
with client_context.wrap_socket(socket.socket(),
28902922
server_hostname="invalid") as s:
2891-
with self.assertRaisesRegex(
2892-
ssl.CertificateError,
2893-
"Hostname mismatch, certificate is not valid for 'invalid'."):
2923+
with self.assertRaisesRegex(ssl.CertificateError, regex):
28942924
s.connect((HOST, server.port))
28952925

28962926
# missing server_hostname arg should cause an exception, too
@@ -3094,7 +3124,7 @@ def test_wrong_cert_tls13(self):
30943124
s.connect((HOST, server.port))
30953125
with self.assertRaisesRegex(
30963126
ssl.SSLError,
3097-
'alert unknown ca|EOF occurred'
3127+
'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA'
30983128
):
30993129
# TLS 1.3 perform client cert exchange after handshake
31003130
s.write(b'data')
@@ -3158,13 +3188,21 @@ def test_ssl_cert_verify_error(self):
31583188
server_hostname=SIGNED_CERTFILE_HOSTNAME) as s:
31593189
try:
31603190
s.connect((HOST, server.port))
3191+
self.fail("Expected connection failure")
31613192
except ssl.SSLError as e:
31623193
msg = 'unable to get local issuer certificate'
31633194
self.assertIsInstance(e, ssl.SSLCertVerificationError)
31643195
self.assertEqual(e.verify_code, 20)
31653196
self.assertEqual(e.verify_message, msg)
3166-
self.assertIn(msg, repr(e))
3167-
self.assertIn('certificate verify failed', repr(e))
3197+
# Allow for flexible libssl error messages.
3198+
regex = "(" + msg + "|CERTIFICATE_VERIFY_FAILED)"
3199+
self.assertRegex(repr(e), regex)
3200+
regex = "("
3201+
regex += "certificate verify failed" # OpenSSL
3202+
regex += "|"
3203+
regex += "CERTIFICATE_VERIFY_FAILED" # AWS-LC/BoringSSL
3204+
regex += ")"
3205+
self.assertRegex(repr(e), regex)
31683206

31693207
def test_PROTOCOL_TLS(self):
31703208
"""Connecting to an SSLv23 server with various client options"""
@@ -3696,7 +3734,7 @@ def test_no_shared_ciphers(self):
36963734
server_hostname=hostname) as s:
36973735
with self.assertRaises(OSError):
36983736
s.connect((HOST, server.port))
3699-
self.assertIn("no shared cipher", server.conn_errors[0])
3737+
self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0])
37003738

37013739
def test_version_basic(self):
37023740
"""
@@ -3784,7 +3822,7 @@ def test_min_max_version_mismatch(self):
37843822
server_hostname=hostname) as s:
37853823
with self.assertRaises(ssl.SSLError) as e:
37863824
s.connect((HOST, server.port))
3787-
self.assertIn("alert", str(e.exception))
3825+
self.assertRegex("(alert|ALERT)", str(e.exception))
37883826

37893827
@requires_tls_version('SSLv3')
37903828
def test_min_max_version_sslv3(self):
@@ -3826,6 +3864,10 @@ def test_tls_unique_channel_binding(self):
38263864

38273865
client_context, server_context, hostname = testing_context()
38283866

3867+
# tls-unique is not defined for TLSv1.3
3868+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
3869+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
3870+
38293871
server = ThreadedEchoServer(context=server_context,
38303872
chatty=True,
38313873
connectionchatty=False)
@@ -3926,7 +3968,7 @@ def test_dh_params(self):
39263968
cipher = stats["cipher"][0]
39273969
parts = cipher.split("-")
39283970
if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts:
3929-
self.fail("Non-DH cipher: " + cipher[0])
3971+
self.fail("Non-DH key exchange: " + cipher[0])
39303972

39313973
def test_ecdh_curve(self):
39323974
# server secp384r1, client auto
@@ -4093,8 +4135,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
40934135
chatty=False,
40944136
sni_name='supermessage')
40954137

4096-
self.assertEqual(cm.exception.reason,
4097-
'SSLV3_ALERT_HANDSHAKE_FAILURE')
4138+
# Allow for flexible libssl error messages.
4139+
regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)"
4140+
self.assertRegex(regex, cm.exception.reason)
40984141
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)
40994142

41004143
def test_sni_callback_wrong_return_type(self):

0 commit comments

Comments
 (0)