pyopenssl: Setting socket timeouts breaks handshake
I’m seeing a weird issue when starting a TLS connection to any host. If I don’t set any timeout on the socket, it works fine. If I do, it breaks before the handshake with a OpenSSL.SSL.WantReadError. For example if I set timeout to 100, it will break after a second anyway.
For now I use a workaround of setting a timeout on connection, but then removing it before the handshake.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2 | OpenSSL.SSL.OP_NO_SSLv3)
ctx.set_verify(OpenSSL.SSL.VERIFY_NONE, lambda _a, _b, _c, _d, _e: None)
conn = OpenSSL.SSL.Connection(ctx, s)
conn.set_tlsext_host_name(hostname.encode('utf-8'))
conn.connect((ip, port))
# s.settimeout(None) # the workaround
try:
conn.do_handshake()
except OpenSSL.SSL.WantReadError:
# this happens on every connection
I’m running on Python 3.4.1, OpenSSL 0.14.
About this issue
- Original URL
- State: open
- Created 10 years ago
- Comments: 16 (3 by maintainers)
Commits related to this issue
- Fix connections to HTTPS server A pyOpenSSL currently prevents from safely setting a timeout on a socket used for SSL: https://github.com/pyca/pyopenssl/issues/168 Current workarounds do not sound... — committed to TeDomumLegacy/pdnscontrol by kaiyou 9 years ago
- Need to use timeout of OpenSSL. Cannot use timeout of the python socket (cf. https://github.com/pyca/pyopenssl/issues/168). — committed to theno/ctutlz by theno 7 years ago
- Only set the timeout for HTTP connections, not HTTPS (for now) This is to avoid https://github.com/pyca/pyopenssl/issues/168 — committed to andresriancho/w3af by deleted user 6 years ago
@moospit @webratz Here’s what works for me:
You can put a timeout on the connect and it will work as desired, you just have to put the socket back into blocking mode before calling into OpenSSL. Of course this just gets you a timeout on the TCP connection; if things stall during the SSL handshake you’re still going to be left hanging but it’s better than nothing.
Thanks.
From this, I see that the problem is the mismatch between expectations for
do_handshakevs the native Python socket APIs.Python’s
socket.socket.connectmethod respects the Python-level socket timeout. This is easy for it because they’re both native Python socket features (timeouts and connection setup).OpenSSL.SSL.Connection.do_handshakeis not a native Python socket operation though. It is a call into OpenSSL’sSSL_do_handshakeAPI which operates on the actual (platform-level) socket directly. Python’s timeout support puts that socket into non-blocking mode. OpenSSL’sSSL_do_handshakeencounters this and does the standard OpenSSL-level thing - translate the “EWOULDBLOCK” read error into an OpenSSLWantReadError(that’s pyOpenSSL’s spelling of the error but that’s easier to talk about here). pyOpenSSL raises this exception up to the caller ofdo_handshake.The only idea I have for fixing this is to teach pyOpenSSL about Python’s socket timeout feature: at every point in the API where there is an OpenSSL operation that operates directly on a platform-level socket, introduce the same kind of wait-and-retry logic that Python’s own socket library has (which implements the timeout feature).
This will introduce a lot of complexity into pyOpenSSL. That’s not a sufficient reason to say it would be a bad thing to add this functionality to pyOpenSSL but it does hint that it might not be a good idea. The contrary argument might be that any application that wants to use timeout-enabled sockets (or more generally, non-blocking sockets) with pyOpenSSL will need to implement this logic (and indeed they have, that’s why pyOpenSSL exposes these error conditions as exceptions in the first place).
The general shape of this solution is probably to make all methods that invoke an OpenSSL API that interacts with a socket (as opposed to methods that interact with the Python-level socket, eg
OpenSSL.SSL.Connection.connect) have some code kind of like this:This could probably be encapsulated into a helper function. It would probably also be beneficial to inspect the implementation of timeouts in Python’s sockets to see if there are any other behaviors worth emulating or non-obvious implementation concerns worth dealing with here (to provide the least surprising behavior).
I found this code here which worked great for me: https://gemfury.com/hemamaps/python:urllib3/-/content/contrib/pyopenssl.py
yeah in my case i sometimes connect to weird servers that happily open the tcp connection, but then do not properly respond on the SSL layer. My workaround was using the timeout-decorator from pypi