Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ jobs:
- { name: openssl, version: 3.4.5 }
- { name: openssl, version: 3.5.6 }
- { name: openssl, version: 3.6.2 }
- { name: openssl, version: 4.0.0 }
env:
SSLLIB_VER: ${{ matrix.ssllib.version }}
MULTISSL_DIR: ${{ github.workspace }}/multissl
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_poplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ def _do_tls_handshake(self):
elif err.args[0] == ssl.SSL_ERROR_EOF:
return self.handle_close()
# TODO: SSLError does not expose alert information
elif ("SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1] or
elif ("TLS_ALERT_BAD_CERTIFICATE" in err.args[1] or
"SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1] or
"SSLV3_ALERT_CERTIFICATE_UNKNOWN" in err.args[1]):
return self.handle_close()
raise
Expand Down Expand Up @@ -415,6 +416,7 @@ def test_stls_context(self):
self.assertEqual(ctx.check_hostname, True)
with self.assertRaises(ssl.CertificateError):
resp = self.client.stls(context=ctx)

self.client = poplib.POP3("localhost", self.server.port,
timeout=test_support.LOOPBACK_TIMEOUT)
resp = self.client.stls(context=ctx)
Expand Down
130 changes: 105 additions & 25 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def test_constants(self):
ssl.OP_NO_COMPRESSION
self.assertEqual(ssl.HAS_SNI, True)
self.assertEqual(ssl.HAS_ECDH, True)
self.assertEqual(ssl.HAS_TLSv1_2, True)
self.assertIsInstance(ssl.HAS_TLSv1_2, bool)
self.assertEqual(ssl.HAS_TLSv1_3, True)
ssl.OP_NO_SSLv2
ssl.OP_NO_SSLv3
Expand Down Expand Up @@ -587,11 +587,11 @@ def test_openssl_version(self):
# Some sanity checks follow
# >= 1.1.1
self.assertGreaterEqual(n, 0x10101000)
# < 4.0
self.assertLess(n, 0x40000000)
# < 5.0
self.assertLess(n, 0x50000000)
major, minor, fix, patch, status = t
self.assertGreaterEqual(major, 1)
self.assertLess(major, 4)
self.assertLess(major, 5)
self.assertGreaterEqual(minor, 0)
self.assertLess(minor, 256)
self.assertGreaterEqual(fix, 0)
Expand Down Expand Up @@ -657,12 +657,14 @@ def test_openssl111_deprecations(self):
ssl.OP_NO_TLSv1_2,
ssl.OP_NO_TLSv1_3
]
protocols = [
ssl.PROTOCOL_TLSv1,
ssl.PROTOCOL_TLSv1_1,
ssl.PROTOCOL_TLSv1_2,
ssl.PROTOCOL_TLS
]
protocols = []
if hasattr(ssl, 'PROTOCOL_TLSv1'):
protocols.append(ssl.PROTOCOL_TLSv1)
if hasattr(ssl, 'PROTOCOL_TLSv1_1'):
protocols.append(ssl.PROTOCOL_TLSv1_1)
if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
protocols.append(ssl.PROTOCOL_TLSv1_2)
protocols.append(ssl.PROTOCOL_TLS)
versions = [
ssl.TLSVersion.SSLv3,
ssl.TLSVersion.TLSv1,
Expand Down Expand Up @@ -1156,6 +1158,7 @@ def test_min_max_version(self):
ssl.TLSVersion.TLSv1,
ssl.TLSVersion.TLSv1_1,
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_3,
ssl.TLSVersion.SSLv3,
}
)
Expand All @@ -1169,7 +1172,7 @@ def test_min_max_version(self):
with self.assertRaises(ValueError):
ctx.minimum_version = 42

if has_tls_protocol(ssl.PROTOCOL_TLSv1_1):
if has_tls_protocol('PROTOCOL_TLSv1_1'):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)

self.assertIn(
Expand Down Expand Up @@ -1664,23 +1667,24 @@ def test__create_stdlib_context(self):
self.assertFalse(ctx.check_hostname)
self._assert_context_options(ctx)

if has_tls_protocol(ssl.PROTOCOL_TLSv1):
if has_tls_protocol('PROTOCOL_TLSv1'):
with warnings_helper.check_warnings():
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self._assert_context_options(ctx)

with warnings_helper.check_warnings():
ctx = ssl._create_stdlib_context(
ssl.PROTOCOL_TLSv1_2,
cert_reqs=ssl.CERT_REQUIRED,
check_hostname=True
)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertTrue(ctx.check_hostname)
self._assert_context_options(ctx)
if has_tls_protocol('PROTOCOL_TLSv1_2'):
with warnings_helper.check_warnings():
ctx = ssl._create_stdlib_context(
ssl.PROTOCOL_TLSv1_2,
cert_reqs=ssl.CERT_REQUIRED,
check_hostname=True
)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertTrue(ctx.check_hostname)
self._assert_context_options(ctx)

ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER)
Expand Down Expand Up @@ -2707,6 +2711,36 @@ def close(self):
def stop(self):
self.active = False

class TestEOFServer(threading.Thread):
def __init__(self):
super().__init__()
self.listening = threading.Event()
self.address = None

def run(self):
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(CERTFILE)
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with server_sock:
server_sock.settimeout(support.SHORT_TIMEOUT)
server_sock.bind((HOST, 0))
server_sock.listen(5)

self.address = server_sock.getsockname()
self.listening.set()

sock, addr = server_sock.accept()
sslconn = context.wrap_socket(sock, server_side=True)
with sslconn:
request = b''
while chunk := sslconn.recv(1024):
request += chunk
if b'\n' in chunk:
break

sslconn.sendall(b'server\n')
sslconn.shutdown(socket.SHUT_WR)

class AsyncoreEchoServer(threading.Thread):

# this one's based on asyncore.dispatcher
Expand Down Expand Up @@ -3576,10 +3610,10 @@ def test_protocol_tlsv1_2(self):
client_options=ssl.OP_NO_TLSv1_2)

try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2')
if has_tls_protocol(ssl.PROTOCOL_TLSv1):
if has_tls_protocol('PROTOCOL_TLSv1'):
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False)
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False)
if has_tls_protocol(ssl.PROTOCOL_TLSv1_1):
if has_tls_protocol('PROTOCOL_TLSv1_1'):
try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False)
try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False)

Expand Down Expand Up @@ -4428,7 +4462,9 @@ def cb_raising(ssl_sock, server_name, initial_context):
sni_name='supermessage')

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

Expand Down Expand Up @@ -4741,6 +4777,50 @@ def background(sock):
if cm.exc_value is not None:
raise cm.exc_value

def test_got_eof(self):
# gh-148292: Test that _ssl._SSLSocket behaves the same on all OpenSSL
# versions on calling methods after EOF (after the first SSLEOFError).

server = TestEOFServer()
server.start()
if not server.listening.wait(support.SHORT_TIMEOUT):
raise RuntimeError("server took too long")
self.addCleanup(server.join)

context = ssl.create_default_context(cafile=CERTFILE)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(support.SHORT_TIMEOUT)
sock.connect(server.address)
sslsock = context.wrap_socket(sock, server_hostname='localhost')
with sslsock:
sslsock.sendall(b'client\n')
# test the _ssl._SSLSocket object, not ssl.SSLSocket
sslobj = sslsock._sslobj

data = sslobj.read(1024)
self.assertEqual(data, b'server\n')

# The second read gets EOF error and sets got_eof_error to 1
with self.assertRaises(ssl.SSLEOFError):
sslobj.read(1024)

# Following read(), sendfile(), write() and do_handshake() calls
# must raise SSLEOFError
with self.assertRaises(ssl.SSLEOFError):
# The _SSLSocket remembers the previous EOF error
# and raises again SSLEOFError
sslobj.read(1024)
if hasattr(sslobj, 'sendfile'):
with open(__file__, "rb") as fp:
with self.assertRaises(ssl.SSLEOFError):
sslobj.sendfile(fp.fileno(), 0, 1)
with self.assertRaises(ssl.SSLEOFError):
sslobj.write(b'client2\n')
with self.assertRaises(ssl.SSLEOFError):
sslsock.do_handshake()

self.assertEqual(sslsock.pending(), 0)


@unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA,
"Test needs TLS 1.3 PHA")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:mod:`ssl`: Update :class:`ssl.SSLSocket` and :class:`ssl.SSLObject` for
OpenSSL 4. The classes now remember if they get a :exc:`ssl.SSLEOFError`. In this
case, following :meth:`~ssl.SSLSocket.read`, :meth:`!sendfile`,
:meth:`~ssl.SSLSocket.write`, and :meth:`~ssl.SSLSocket.do_handshake` calls
raise :exc:`ssl.SSLEOFError` without calling the underlying OpenSSL function.
Thanks to that, :class:`ssl.SSLSocket` behaves the same on all OpenSSL versions
on EOF. Patch by Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add OpenSSL 4.0.0 support to test configurations.
Loading
Loading