]> granicus.if.org Git - python/commitdiff
Issue #19781: ftplib now supports SSLContext.check_hostname and server name
authorChristian Heimes <christian@cheimes.de>
Mon, 2 Dec 2013 01:56:02 +0000 (02:56 +0100)
committerChristian Heimes <christian@cheimes.de>
Mon, 2 Dec 2013 01:56:02 +0000 (02:56 +0100)
indication for TLS/SSL connections.

Doc/library/ftplib.rst
Lib/ftplib.py
Lib/test/test_ftplib.py
Misc/NEWS

index dcb2ac4107ba3a4897e13d63cdab0b700bd1c7c6..7f98d0bd81b0d8a9aa30a68b25304028513f8a12 100644 (file)
@@ -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
index 1b16e0ae48dda3a3a19791faf456c72cec56fccb..2cc470258e18e6185af3f6ebb0cc4e3f1027001f 100644 (file)
@@ -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):
index 41463e2a5bb8be6dd87d5e973778e00b4a20feb9..15458a8ebfed537157ef5ea8ae43a7e648304c1c 100644 (file)
@@ -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):
 
index 07f8668ea0c4b13f10826c6e1bebf7bed0ded966..39785a78411e1b0625935d24d2f051da8c8359f0 100644 (file)
--- 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.