SSL error unsafe legacy renegotiation disabled
Question:
I am running a Python code where I have to get some data from HTTPSConnectionPool(host=’ssd.jpl.nasa.gov’, port=443). But every time I try to run the code I get the following error. I am on MAC OS 12.1
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443): Max retries exceeded with url: /api/horizons.api?format=text&EPHEM_TYPE=OBSERVER&QUANTITIES_[...]_ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:997)')))
I really don’t know how to bypass this issue.. thank you for the help!
Answers:
I hit the same error on Linux (it happens when the server doesn’t support "RFC 5746 secure renegotiation" and the client is using OpenSSL 3, which enforces that standard by default).
Here is a solution (you may have to adjust it slightly).
- Import
ssl
and urllib3
in your Python code
- Create a custom HttpAdapter which uses a custom
ssl
Context
class CustomHttpAdapter (requests.adapters.HTTPAdapter):
'''Transport adapter" that allows us to use custom ssl_context.'''
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = urllib3.poolmanager.PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, ssl_context=self.ssl_context)
- Set up an
ssl
context which enables OP_LEGACY_SERVER_CONNECT
, and use it with your custom adapter.
ssl.OP_LEGACY_SERVER_CONNECT
is not available in Python yet (https://bugs.python.org/issue44888). However it turns out that in OpenSSL its value is 0x4 in the bitfield. So we can do the following.
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4
session.mount('https://', CustomHttpAdapter(ctx))
WARNING: When enabling Legacy Unsafe Renegotiation, SSL connections will be vulnerable to the Man-in-the-Middle prefix attack as described in CVE-2009-3555.
With the help of https://bugs.launchpad.net/bugs/1963834
and https://bugs.launchpad.net/ubuntu/+source/gnutls28/+bug/1856428
Beware that editing your system’s openssl.conf is not recommended, because you might lose your changes once openssl is updated.
Create a custom openssl.cnf
file in any directory with these contents:
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
Before running your program, make sure your OPENSSL_CONF
environment variable is set to your custom openssl.cnf
full path when running the scraper like so:
OPENSSL_CONF=/path/to/custom/openssl.cnf python your_scraper.py
or like so:
export OPENSSL_CONF=/path/to/custom/openssl.cnf
python your_scraper.py
or, if you are using pipenv or systemd or docker, place this into your .env
file
OPENSSL_CONF=/path/to/custom/openssl.cnf
This error comes up when using OpenSSL 3 to connect to a server which does not support it. The solution is to downgrade the cryptography package in python:
run pip install cryptography==36.0.2
in the used enviroment.
source: https://github.com/scrapy/scrapy/issues/5491
EDIT: Refer to Hally Mallon and ahmkara’s answer for a fix without downgrading cryptography
Complete code snippets for Harry Mallon‘s answer:
Define a method for reuse:
import requests
import urllib3
import ssl
class CustomHttpAdapter (requests.adapters.HTTPAdapter):
# "Transport adapter" that allows us to use custom ssl_context.
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = urllib3.poolmanager.PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, ssl_context=self.ssl_context)
def get_legacy_session():
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT
session = requests.session()
session.mount('https://', CustomHttpAdapter(ctx))
return session
Then use it in place of the requests call:
get_legacy_session().get("some-url")
This doesn’t really answer the issue, but a coworker switched from Node 18 to 16 and stopped getting this error.
For me, it worked when I downgraded python to v3.10.8.
(If you are facing the issue in docker container, read below)
In my docker image, I was using alpine-10 which was using v3.10.9. Since I couldn’t get alpine with v3.10.8, I used 3.10.8-slim-bullseye.
I am running a Python code where I have to get some data from HTTPSConnectionPool(host=’ssd.jpl.nasa.gov’, port=443). But every time I try to run the code I get the following error. I am on MAC OS 12.1
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443): Max retries exceeded with url: /api/horizons.api?format=text&EPHEM_TYPE=OBSERVER&QUANTITIES_[...]_ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:997)')))
I really don’t know how to bypass this issue.. thank you for the help!
I hit the same error on Linux (it happens when the server doesn’t support "RFC 5746 secure renegotiation" and the client is using OpenSSL 3, which enforces that standard by default).
Here is a solution (you may have to adjust it slightly).
- Import
ssl
andurllib3
in your Python code - Create a custom HttpAdapter which uses a custom
ssl
Context
class CustomHttpAdapter (requests.adapters.HTTPAdapter):
'''Transport adapter" that allows us to use custom ssl_context.'''
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = urllib3.poolmanager.PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, ssl_context=self.ssl_context)
- Set up an
ssl
context which enablesOP_LEGACY_SERVER_CONNECT
, and use it with your custom adapter.
ssl.OP_LEGACY_SERVER_CONNECT
is not available in Python yet (https://bugs.python.org/issue44888). However it turns out that in OpenSSL its value is 0x4 in the bitfield. So we can do the following.
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4
session.mount('https://', CustomHttpAdapter(ctx))
WARNING: When enabling Legacy Unsafe Renegotiation, SSL connections will be vulnerable to the Man-in-the-Middle prefix attack as described in CVE-2009-3555.
With the help of https://bugs.launchpad.net/bugs/1963834
and https://bugs.launchpad.net/ubuntu/+source/gnutls28/+bug/1856428
Beware that editing your system’s openssl.conf is not recommended, because you might lose your changes once openssl is updated.
Create a custom openssl.cnf
file in any directory with these contents:
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
Before running your program, make sure your OPENSSL_CONF
environment variable is set to your custom openssl.cnf
full path when running the scraper like so:
OPENSSL_CONF=/path/to/custom/openssl.cnf python your_scraper.py
or like so:
export OPENSSL_CONF=/path/to/custom/openssl.cnf
python your_scraper.py
or, if you are using pipenv or systemd or docker, place this into your .env
file
OPENSSL_CONF=/path/to/custom/openssl.cnf
This error comes up when using OpenSSL 3 to connect to a server which does not support it. The solution is to downgrade the cryptography package in python:
run pip install cryptography==36.0.2
in the used enviroment.
source: https://github.com/scrapy/scrapy/issues/5491
EDIT: Refer to Hally Mallon and ahmkara’s answer for a fix without downgrading cryptography
Complete code snippets for Harry Mallon‘s answer:
Define a method for reuse:
import requests
import urllib3
import ssl
class CustomHttpAdapter (requests.adapters.HTTPAdapter):
# "Transport adapter" that allows us to use custom ssl_context.
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = urllib3.poolmanager.PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, ssl_context=self.ssl_context)
def get_legacy_session():
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT
session = requests.session()
session.mount('https://', CustomHttpAdapter(ctx))
return session
Then use it in place of the requests call:
get_legacy_session().get("some-url")
This doesn’t really answer the issue, but a coworker switched from Node 18 to 16 and stopped getting this error.
For me, it worked when I downgraded python to v3.10.8.
(If you are facing the issue in docker container, read below)
In my docker image, I was using alpine-10 which was using v3.10.9. Since I couldn’t get alpine with v3.10.8, I used 3.10.8-slim-bullseye.