From e5b5edfa2ccf2c031be156a03267dc5629feda77 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 2 Dec 2013 02:56:02 +0100 Subject: [PATCH] Issue #19781: ftplib now supports SSLContext.check_hostname and server name indication for TLS/SSL connections. --- Doc/library/ftplib.rst | 10 ++++++++++ Lib/ftplib.py | 8 ++++++-- Lib/test/test_ftplib.py | 33 ++++++++++++++++++++++++++++++++- Misc/NEWS | 3 +++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Doc/library/ftplib.rst b/Doc/library/ftplib.rst index dcb2ac4107b..7f98d0bd81b 100644 --- a/Doc/library/ftplib.rst +++ b/Doc/library/ftplib.rst @@ -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 diff --git a/Lib/ftplib.py b/Lib/ftplib.py index 1b16e0ae48d..2cc470258e1 100644 --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -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): diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 41463e2a5bb..15458a8ebfe 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -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): diff --git a/Misc/NEWS b/Misc/NEWS index 07f8668ea0c..39785a78411 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,9 @@ Core and Builtins Library ------- +- Issue #19781: ftplib now supports SSLContext.check_hostname and server name + indication for TLS/SSL connections. + - Issue #19509: Add SSLContext.check_hostname to match the peer's certificate with server_hostname on handshake.