I have been trying to perform an HTTPS request in Python 3 using requests
and aggregating pretty much all the knowledge from the prior attempts documented on StackOverflow. I cannot for the life of me seem to get out of the sslv3 alert handshake failure
rabbit hole.
This is my environment:
- macOS 10.13.6
- Python 3.7.0 (installed via Homebrew along with openssl)
- OpenSSL 1.0.2p 14 Aug 2018 (output of
print(ssl.OPENSSL_VERSION)
) - requests 2.19.1 (output of
print(requests.__version__)
) installed viapip install requests[security]
- even cryptography 2.3.1 is installed
This is the bare-bone failing code:
>>> import requests
>>> requests.get('https://iris.nuigalway.ie')
And this is the output:
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/urllib3-1.23-py3.7.egg/urllib3/contrib/pyopenssl.py", line 444, in wrap_socket
cnx.do_handshake()
File "/usr/local/lib/python3.7/site-packages/pyOpenSSL-18.0.0-py3.7.egg/OpenSSL/SSL.py", line 1907, in do_handshake
self._raise_ssl_error(self._ssl, result)
File "/usr/local/lib/python3.7/site-packages/pyOpenSSL-18.0.0-py3.7.egg/OpenSSL/SSL.py", line 1639, in _raise_ssl_error
_raise_current_error()
File "/usr/local/lib/python3.7/site-packages/pyOpenSSL-18.0.0-py3.7.egg/OpenSSL/_util.py", line 54, in exception_from_error_queue
raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'ssl3_read_bytes', 'sslv3 alert handshake failure')]
Needless to say it works with cURL, browsers etc.
curl --verbose "https://iris.nuigalway.ie"
Here's a handshake snippet of the ouput:
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.0 (IN), TLS handshake, Server hello (2):
* TLSv1.0 (IN), TLS handshake, Certificate (11):
* TLSv1.0 (IN), TLS handshake, Server finished (14):
* TLSv1.0 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.0 (OUT), TLS change cipher, Client hello (1):
* TLSv1.0 (OUT), TLS handshake, Finished (20):
* TLSv1.0 (IN), TLS change cipher, Client hello (1):
* TLSv1.0 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.0 / DES-CBC3-SHA
* ALPN, server did not agree to a protocol
Now the cipher used by cURL does not indeed seem to be among the default ciphers of urllib3
1.23 (seemingly used by requests
) as per https://github.com/urllib3/urllib3/blob/1.23/urllib3/util/ssl_.py
So I tried adding it using the advice given at https://stackoverflow.com/a/40741362 like this:
>>> requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += 'DES-CBC3-SHA'
and even setting it to ALL
. I even tried not to verify the certificate, all to no avail.
>>> requests.get('https://iris.nuigalway.ie', verify=False)
A check with s_client
on the server:
$ openssl s_client -connect iris.nuigalway.ie:443
reveals the following TLS version and cipher:
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1
Cipher : RC4-SHA
What options could I possibly have not tried yet?
Many Thanks
UPDATE
The values of ssl.OPENSSL_VERSION
OpenSSL.SSL.SSLeay_version(0)
revealed two different versions of OpenSSL used by ssl
and pyOpenSSL
respectively, the latter being a more recent OpenSSL 1.1.0i 14 Aug 2018
that has most likely dropped support for the DES-CBC3-SHA
cipher.
Below is the temporary solution I have adopted:
- uninstall
cryptography
- injecting only the required cipher, like this:
(Note that it is no longer a concatenation)
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'DES-CBC3-SHA'
I appreciate that this solution may be sub-optimal and not applicable to many cases, but at least the lesson learnt is that different versions of OpenSSL may be at play from one package to another.
I will be happy to know of a more flexible solution if any.