Issue #19781: ftplib now supports SSLContext.check_hostname and server name
indication for TLS/SSL connections.
This commit is contained in:
parent
1aa9a75fbf
commit
e5b5edfa2c
|
@ -94,6 +94,11 @@ The module defines the following items:
|
|||
.. versionchanged:: 3.3
|
||||
*source_address* parameter was added.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
The class now supports hostname check with
|
||||
:attr:`SSLContext.check_hostname` and *Server Name Indicator* (see
|
||||
:data:`~ssl.HAS_SNI`).
|
||||
|
||||
Here's a sample session using the :class:`FTP_TLS` class:
|
||||
|
||||
>>> from ftplib import FTP_TLS
|
||||
|
@ -427,6 +432,11 @@ FTP_TLS Objects
|
|||
Set up secure control connection by using TLS or SSL, depending on what
|
||||
specified in :meth:`ssl_version` attribute.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
The method now supports hostname check with
|
||||
:attr:`SSLContext.check_hostname` and *Server Name Indicator* (see
|
||||
:data:`~ssl.HAS_SNI`).
|
||||
|
||||
.. method:: FTP_TLS.ccc()
|
||||
|
||||
Revert control channel back to plaintext. This can be useful to take
|
||||
|
|
|
@ -748,7 +748,9 @@ else:
|
|||
resp = self.voidcmd('AUTH TLS')
|
||||
else:
|
||||
resp = self.voidcmd('AUTH SSL')
|
||||
self.sock = self.context.wrap_socket(self.sock)
|
||||
server_hostname = self.host if ssl.HAS_SNI else None
|
||||
self.sock = self.context.wrap_socket(self.sock,
|
||||
server_hostname=server_hostname)
|
||||
self.file = self.sock.makefile(mode='r', encoding=self.encoding)
|
||||
return resp
|
||||
|
||||
|
@ -787,7 +789,9 @@ else:
|
|||
def ntransfercmd(self, cmd, rest=None):
|
||||
conn, size = FTP.ntransfercmd(self, cmd, rest)
|
||||
if self._prot_p:
|
||||
conn = self.context.wrap_socket(conn)
|
||||
server_hostname = self.host if ssl.HAS_SNI else None
|
||||
conn = self.context.wrap_socket(conn,
|
||||
server_hostname=server_hostname)
|
||||
return conn, size
|
||||
|
||||
def abort(self):
|
||||
|
|
|
@ -301,7 +301,8 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread):
|
|||
|
||||
if ssl is not None:
|
||||
|
||||
CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
|
||||
CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem")
|
||||
CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem")
|
||||
|
||||
class SSLConnection(asyncore.dispatcher):
|
||||
"""An asyncore.dispatcher subclass supporting TLS/SSL."""
|
||||
|
@ -923,6 +924,36 @@ class TestTLS_FTPClass(TestCase):
|
|||
self.client.ccc()
|
||||
self.assertRaises(ValueError, self.client.sock.unwrap)
|
||||
|
||||
def test_check_hostname(self):
|
||||
self.client.quit()
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||
ctx.check_hostname = True
|
||||
ctx.load_verify_locations(CAFILE)
|
||||
self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT)
|
||||
|
||||
# 127.0.0.1 doesn't match SAN
|
||||
self.client.connect(self.server.host, self.server.port)
|
||||
with self.assertRaises(ssl.CertificateError):
|
||||
self.client.auth()
|
||||
# exception quits connection
|
||||
|
||||
self.client.connect(self.server.host, self.server.port)
|
||||
self.client.prot_p()
|
||||
with self.assertRaises(ssl.CertificateError):
|
||||
with self.client.transfercmd("list") as sock:
|
||||
pass
|
||||
self.client.quit()
|
||||
|
||||
self.client.connect("localhost", self.server.port)
|
||||
self.client.auth()
|
||||
self.client.quit()
|
||||
|
||||
self.client.connect("localhost", self.server.port)
|
||||
self.client.prot_p()
|
||||
with self.client.transfercmd("list") as sock:
|
||||
pass
|
||||
|
||||
|
||||
class TestTimeouts(TestCase):
|
||||
|
||||
|
|
Loading…
Reference in New Issue