-
Notifications
You must be signed in to change notification settings - Fork 421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setting socket timeouts breaks handshake #168
Comments
Can you produce a self-contained example of this behavior? My simple attempts produce this contrary result:
|
Prints out "badness" for me. I used the ip of "github.com". After commenting out Runtime takes 1 second, not 100.
|
Thanks. From this, I see that the problem is the mismatch between expectations for Python's
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
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). |
While this gets implemented is there any workaround to get timeouts to work? |
A pyOpenSSL currently prevents from safely setting a timeout on a socket used for SSL: pyca/pyopenssl#168 Current workarounds do not sound good enough for production, it seems safe to assume that a longer timeout is will bring less harm than unexpected SSL errors.
Has this ever been fixed? Or is there a proper workaround? |
This hasn't been fixed, so far as I am aware. Anyone who is using pyOpenSSL via Twisted gets properly working timeouts - and that's the only way I use pyOpenSSL. So, that's one possible work-around. |
I use it directly only to determine information about the used certificate so using twisted is a bit of overkill |
Same here. I just need to get certificate information. Using additional libraries would be overkill. |
Python implements timeout on top of standard socket IO with |
The timeout magic happens in sock_call_ex and internal_select https://github.com/python/cpython/blob/master/Modules/socketmodule.c#L765 |
@moospit @webratz Here's what works for me: def print_chain(context, hostname):
print('Connecting to {0}'.format(hostname))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = SSL.Connection(context=context, socket=sock)
sock.settimeout(5)
sock.connect((hostname, 443))
sock.setblocking(1)
sock.do_handshake()
for cert in sock.get_peer_cert_chain():
print(' s:{0}'.format(cert.get_subject()))
print(' i:{0}'.format(cert.get_issuer()))
sock.shutdown()
sock.close() 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. |
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. |
Cannot use timeout of the python socket (cf. pyca/pyopenssl#168).
I was able to work around this by doing a select before calling do_handshake: readable, writable, errored = select.select([self._sock], [], [], 10)
if self._sock in readable:
module_logger.debug("socket in readable")
self._sock.do_handshake()
|
This hasn't been fixed, so far as I am aware. |
Note: In order to conform to the documentation on socket timeouts they must get reset each time a read is made. So there's no reason for a "very complex" solution., just a moderately complex one:
|
I found this code here which worked great for me:
|
timeout-decorator fails with multithreading yepp! timeout-decorator works : ) used in danilobellini/aia#3 # @timeout_decorator.timeout(timeout, timeout_exception=TimeoutError, use_signals=False)
@timeout_decorator.timeout(timeout, timeout_exception=TimeoutError)
def do_handshake():
conn.do_handshake()
#conn.do_handshake()
do_handshake()
cert_chain = conn.get_peer_cert_chain()
assert cert_chain != None do_handshake.pyimport timeout_decorator
import OpenSSL # pyopenssl
import certifi
timeout = 5
host, port = "127.0.0.1", 4430
ssl_context = OpenSSL.SSL.Context(method=OpenSSL.SSL.TLS_CLIENT_METHOD)
ssl_context.load_verify_locations(cafile=certifi.where())
conn = OpenSSL.SSL.Connection(
ssl_context,
socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
)
conn.settimeout(timeout)
conn.connect((host, port))
conn.setblocking(1)
conn.set_tlsext_host_name(host.encode())
@timeout_decorator.timeout(timeout, timeout_exception=TimeoutError)
def do_handshake():
conn.do_handshake()
#conn.do_handshake()
do_handshake()
cert_chain = conn.get_peer_cert_chain()
assert cert_chain != None edit: nope. timeout-decorator is limited to the main thread
timeout-decorator#multithreading timeout-decorator#multithreading
similar issue: read with timeout milahu/gnumake-tokenpool#10 |
yepp! this works, see test removed select for a writable sock, because sock is always writable - select.select([sock], [sock], [], remain)
+ select.select([sock], [], [], remain) #!/usr/bin/env python3
import sys
import time
import select
import socket
from urllib.parse import urlsplit
import OpenSSL # pyopenssl
import certifi
cafile = certifi.where()
def get_cert_chain(hostname, port, timeout=5):
# https://github.com/pyca/pyopenssl/issues/168#issuecomment-61813592
# exarkun commented on Nov 5, 2014
ssl_context = OpenSSL.SSL.Context(method=OpenSSL.SSL.TLS_CLIENT_METHOD)
ssl_context.load_verify_locations(cafile=cafile)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn = OpenSSL.SSL.Connection(context=ssl_context, socket=sock)
#sock.settimeout(5) # no. sock.gettimeout() still returns None
conn.settimeout(5) # is this needed?
conn.connect((hostname, port))
conn.setblocking(1)
#conn.do_handshake()
def do_handshake():
conn.setblocking(0) # unblock conn.do_handshake
#timeout = sock.gettimeout() # None
#timeout = conn.gettimeout() # None
timeout = 5
#print("timeout", timeout)
if timeout is not None:
start = time.time()
last_remain = timeout
while True:
try:
#return <some OpenSSL API>
#print("conn.do_handshake ...")
res = conn.do_handshake()
print("conn.do_handshake ok")
conn.setblocking(1)
return res
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError) as exc:
#print("exc", exc)
remain = timeout - (time.time() - start)
t_step = last_remain - remain # 0.0004
last_remain = remain
#print("remain", remain)
#print("t_step", t_step)
#if timeout is None or start + timeout > time.time():
if remain < 0:
#raise
conn.setblocking(1)
raise TimeoutError
# TODO? handle timeout from select
readable, writable, errored = select.select(
# no. dont select writable sock
#[sock], [sock], [], remain
[sock], [], [], remain
)
print("select", (readable, writable, errored))
#if <select timed out>:
# raise <something - the original exception? a specific timeout exception?>
# no. this was only needed with select writable sock
# because the sock is always writable
#time.sleep(0.5) # reduce cpu load
do_handshake()
cert_chain = conn.get_peer_cert_chain()
conn.shutdown()
conn.close()
return cert_chain
hostname = sys.argv[1]
port = int(sys.argv[2])
try:
get_cert_chain = get_cert_chain(hostname, port)
except TimeoutError:
print("timeout") |
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.
I'm running on Python 3.4.1, OpenSSL 0.14.
The text was updated successfully, but these errors were encountered: