See the end of this post for a final decision. The rest are the steps necessary to debug the problem.
I am trying to connect to an FTP server that only supports TLS 1.2. Using Python 3.4.1
How do you know?
ssl.SSLEOFError: EOF occurred with a protocol violation (_ssl.c: 598)
I would suggest one of the many SSL problems between the client and the server, for example, a server that does not support TLS 1.2, not common ciphers, etc. These problems are difficult to debug, because you either receive only some SSL notification, or the server just closes the connection for no obvious reason. If you have access to the server, look for error messages on the server side.
You can also try not to use the SSL version, but instead use the default value so that the client and server agree with the best version of SSL. If this still does not work, try with a client that, as you know, works with this server and does batch capture of good and bad connections and compares. If you need help with this post, the package captures cloudshark.org.
Edit # 1: tried only with python 3.4.0 and 3.4.2 against the test server:
- python 3.4.0 does the handshake of TLS 1.0 i.e. ignores setting
- python 3.4.2 makes a successful handshake TLS 1.2
In both versions, ftplib has a minor bug that sends AUTH SSL instead of AUTH TLS if ftps.ssl_version is something other than TLS 1.0, i.e. SSLv3 or TLS1.1. +. Although I doubt this is the cause of the problem, it could actually be if the FTP server handles AUTH TLS and AUTH SSL differently.
Change # 2 and solution:
Packet capture shows that setting ftps.ssl_version has no effect, and SSL handshakes will still only be performed with TLS 1.0. Looking at the ftplib source code in 3.4.0, you get:
ssl_version = ssl.PROTOCOL_TLSv1 def __init__(self, host='', user='', passwd='', acct='', keyfile=None, certfile=None, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None): .... if context is None: context = ssl._create_stdlib_context(self.ssl_version, certfile=certfile, keyfile=keyfile) self.context = context
Since __init__ is called when ftplib.FTP_TLS() is called an SSL context, ssl_version will be created by default, used by ftplib ( ssl.PROTOCOL_TLSv1 ), and not with your own version. To use a different version of SSL, you must provide your own context with the required version of SSL. The following works for me:
import ftplib import ssl ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1_2) ftps = ftplib.FTP_TLS(context=ctx) print (ftps.connect('108.61.166.122',31000)) print(ftps.login('test','test123')) ftps.prot_p() print (ftps.retrlines('LIST'))
Alternatively, you can set the protocol version globally, and not just for this FTP_TLS object:
ftplib.FTP_TLS.ssl_version = ssl.PROTOCOL_TLSv1_2 ftps = ftplib.FTP_TLS()
And just a small but important note: it seems that ftplib does not perform any certificate verification , as it accepts this self-signed certificate that does not match the name without complaints. This makes a man in the middle attack possible. I hope they correct this unsafe behavior in the future, and in this case the code here will fail due to an invalid certificate.