Skip to content

gh-116333: Relax error string text expectations in SSL-related tests #116334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,12 +1125,16 @@ def test_create_server_ssl_match_failed(self):
# incorrect server_hostname
f_c = self.loop.create_connection(MyProto, host, port,
ssl=sslcontext_client)

# Allow for flexible libssl error messages.
regex = re.compile(r"""(
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
with mock.patch.object(self.loop, 'call_exception_handler'):
with test_utils.disable_logger():
with self.assertRaisesRegex(
ssl.CertificateError,
"IP address mismatch, certificate is not valid for "
"'127.0.0.1'"):
with self.assertRaisesRegex(ssl.CertificateError, regex):
self.loop.run_until_complete(f_c)

# close connection
Expand Down
22 changes: 15 additions & 7 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import time
import calendar
import threading
import re
import socket

from test.support import verbose, run_with_tz, run_with_locale, cpython_only, requires_resource
Expand Down Expand Up @@ -558,9 +559,13 @@ def test_ssl_raises(self):
self.assertEqual(ssl_context.check_hostname, True)
ssl_context.load_verify_locations(CAFILE)

with self.assertRaisesRegex(ssl.CertificateError,
"IP address mismatch, certificate is not valid for "
"'127.0.0.1'"):
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
with self.assertRaisesRegex(ssl.CertificateError, regex):
_, server = self._setup(SimpleIMAPHandler)
client = self.imap_class(*server.server_address,
ssl_context=ssl_context)
Expand Down Expand Up @@ -954,10 +959,13 @@ def test_ssl_verified(self):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_verify_locations(CAFILE)

with self.assertRaisesRegex(
ssl.CertificateError,
"IP address mismatch, certificate is not valid for "
"'127.0.0.1'"):
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
IP address mismatch, certificate is not valid for '127.0.0.1' # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
with self.assertRaisesRegex(ssl.CertificateError, regex):
with self.reaped_server(SimpleIMAPHandler) as server:
client = self.imap_class(*server.server_address,
ssl_context=ssl_context)
Expand Down
97 changes: 70 additions & 27 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def test_openssl_version(self):
else:
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
self.assertTrue(
s.startswith((openssl_ver, libressl_ver)),
s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
(s, t, hex(n))
)

Expand Down Expand Up @@ -1169,24 +1169,30 @@ def test_load_cert_chain(self):
with self.assertRaises(OSError) as cm:
ctx.load_cert_chain(NONEXISTINGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_cert_chain(BADCERT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_cert_chain(EMPTYCERT)
# Separate key and cert
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(ONLYCERT, ONLYKEY)
ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_cert_chain(ONLYCERT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_cert_chain(ONLYKEY)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT)
# Mismatching key and cert
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
with self.assertRaisesRegex(ssl.SSLError, "key values mismatch"):
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
key values mismatch # OpenSSL
|
KEY_VALUES_MISMATCH # AWS-LC
)""", re.X)
with self.assertRaisesRegex(ssl.SSLError, regex):
ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY)
# Password protected key and cert
ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD)
Expand Down Expand Up @@ -1254,7 +1260,7 @@ def test_load_verify_locations(self):
with self.assertRaises(OSError) as cm:
ctx.load_verify_locations(NONEXISTINGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"):
ctx.load_verify_locations(BADCERT)
ctx.load_verify_locations(CERTFILE, CAPATH)
ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH)
Expand Down Expand Up @@ -1662,9 +1668,10 @@ def test_lib_reason(self):
with self.assertRaises(ssl.SSLError) as cm:
ctx.load_dh_params(CERTFILE)
self.assertEqual(cm.exception.library, 'PEM')
self.assertEqual(cm.exception.reason, 'NO_START_LINE')
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
self.assertRegex(cm.exception.reason, regex)
s = str(cm.exception)
self.assertTrue(s.startswith("[PEM: NO_START_LINE] no start line"), s)
self.assertTrue("NO_START_LINE" in s, s)

def test_subclass(self):
# Check that the appropriate SSLError subclass is raised
Expand Down Expand Up @@ -1844,7 +1851,13 @@ def test_connect_fail(self):
s = test_wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_REQUIRED)
self.addCleanup(s.close)
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
certificate verify failed # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
self.assertRaisesRegex(ssl.SSLError, regex,
s.connect, self.server_addr)

def test_connect_ex(self):
Expand Down Expand Up @@ -1912,7 +1925,13 @@ def test_connect_with_context_fail(self):
server_hostname=SIGNED_CERTFILE_HOSTNAME
)
self.addCleanup(s.close)
self.assertRaisesRegex(ssl.SSLError, "certificate verify failed",
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
certificate verify failed # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
self.assertRaisesRegex(ssl.SSLError, regex,
s.connect, self.server_addr)

def test_connect_capath(self):
Expand Down Expand Up @@ -2129,14 +2148,16 @@ def test_bio_handshake(self):
self.assertIsNone(sslobj.version())
self.assertIsNone(sslobj.shared_ciphers())
self.assertRaises(ValueError, sslobj.getpeercert)
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
# tls-unique is not defined for TLSv1.3
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
self.assertIsNone(sslobj.get_channel_binding('tls-unique'))
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
self.assertTrue(sslobj.cipher())
self.assertIsNone(sslobj.shared_ciphers())
self.assertIsNotNone(sslobj.version())
self.assertTrue(sslobj.getpeercert())
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES:
if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3":
self.assertTrue(sslobj.get_channel_binding('tls-unique'))
try:
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
Expand Down Expand Up @@ -2861,11 +2882,16 @@ def test_crl_check(self):
client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF

server = ThreadedEchoServer(context=server_context, chatty=True)
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
certificate verify failed # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
with server:
with client_context.wrap_socket(socket.socket(),
server_hostname=hostname) as s:
with self.assertRaisesRegex(ssl.SSLError,
"certificate verify failed"):
with self.assertRaisesRegex(ssl.SSLError, regex):
s.connect((HOST, server.port))

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

# incorrect hostname should raise an exception
server = ThreadedEchoServer(context=server_context, chatty=True)
# Allow for flexible libssl error messages.
regex = re.compile(r"""(
certificate verify failed # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
with server:
with client_context.wrap_socket(socket.socket(),
server_hostname="invalid") as s:
with self.assertRaisesRegex(
ssl.CertificateError,
"Hostname mismatch, certificate is not valid for 'invalid'."):
with self.assertRaisesRegex(ssl.CertificateError, regex):
s.connect((HOST, server.port))

# missing server_hostname arg should cause an exception, too
Expand Down Expand Up @@ -3137,7 +3167,7 @@ def test_wrong_cert_tls13(self):
s.connect((HOST, server.port))
with self.assertRaisesRegex(
ssl.SSLError,
'alert unknown ca|EOF occurred'
'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA'
):
# TLS 1.3 perform client cert exchange after handshake
s.write(b'data')
Expand Down Expand Up @@ -3201,13 +3231,21 @@ def test_ssl_cert_verify_error(self):
server_hostname=SIGNED_CERTFILE_HOSTNAME) as s:
try:
s.connect((HOST, server.port))
self.fail("Expected connection failure")
except ssl.SSLError as e:
msg = 'unable to get local issuer certificate'
self.assertIsInstance(e, ssl.SSLCertVerificationError)
self.assertEqual(e.verify_code, 20)
self.assertEqual(e.verify_message, msg)
self.assertIn(msg, repr(e))
self.assertIn('certificate verify failed', repr(e))
# Allow for flexible libssl error messages.
regex = f"({msg}|CERTIFICATE_VERIFY_FAILED)"
self.assertRegex(repr(e), regex)
regex = re.compile(r"""(
certificate verify failed # OpenSSL
|
CERTIFICATE_VERIFY_FAILED # AWS-LC
)""", re.X)
self.assertRegex(repr(e), regex)

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

def test_version_basic(self):
"""
Expand Down Expand Up @@ -3827,7 +3865,7 @@ def test_min_max_version_mismatch(self):
server_hostname=hostname) as s:
with self.assertRaises(ssl.SSLError) as e:
s.connect((HOST, server.port))
self.assertIn("alert", str(e.exception))
self.assertRegex("(alert|ALERT)", str(e.exception))

@requires_tls_version('SSLv3')
def test_min_max_version_sslv3(self):
Expand Down Expand Up @@ -3869,6 +3907,10 @@ def test_tls_unique_channel_binding(self):

client_context, server_context, hostname = testing_context()

# tls-unique is not defined for TLSv1.3
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5
client_context.maximum_version = ssl.TLSVersion.TLSv1_2

server = ThreadedEchoServer(context=server_context,
chatty=True,
connectionchatty=False)
Expand Down Expand Up @@ -3969,7 +4011,7 @@ def test_dh_params(self):
cipher = stats["cipher"][0]
parts = cipher.split("-")
if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts:
self.fail("Non-DH cipher: " + cipher[0])
self.fail("Non-DH key exchange: " + cipher[0])

def test_ecdh_curve(self):
# server secp384r1, client auto
Expand Down Expand Up @@ -4136,8 +4178,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
chatty=False,
sni_name='supermessage')

self.assertEqual(cm.exception.reason,
'SSLV3_ALERT_HANDSHAKE_FAILURE')
# Allow for flexible libssl error messages.
regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)"
self.assertRegex(regex, cm.exception.reason)
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)

def test_sni_callback_wrong_return_type(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Tests of TLS related things (error codes, etc) were updated to be more
lenient about specific error message strings and behaviors as seen in the
BoringSSL and AWS-LC forks of OpenSSL.