Skip to content

SSL Error: Unsafe Legacy Renegotiation Disabled

Problem Statement

When using Python to connect to certain HTTPS servers, you might encounter an error similar to:

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)')))

This error typically occurs when:

  • Your client is using OpenSSL 3 or later
  • The server doesn't support RFC 5746 secure renegotiation
  • You're connecting to older infrastructure that uses legacy SSL/TLS renegotiation

SECURITY CONSIDERATIONS

Enabling legacy renegotiation makes your SSL connections vulnerable to the Man-in-the-Middle prefix attack described in CVE-2009-3555. Only use these solutions when connecting to trusted internal servers or when no alternative exists.

Solution 1: Custom SSL Context with Legacy Support (Python)

This approach creates a custom SSL context that allows legacy renegotiation while maintaining other security features:

python
import ssl
import requests
import urllib3

class CustomHttpAdapter(requests.adapters.HTTPAdapter):
    """Transport adapter that allows 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():
    """Create a requests session that allows legacy SSL connections"""
    ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    ctx.options |= 0x4  # OP_LEGACY_SERVER_CONNECT flag
    session = requests.session()
    session.mount('https://', CustomHttpAdapter(ctx))
    return session

# Usage
response = get_legacy_session().get("https://your-target-url.com")

Solution 2: Using urllib with Custom Context

For applications using urllib directly:

python
import urllib.request
import ssl

# Create custom SSL context
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4  # Enable legacy renegotiation

# Make request
url = "https://your-target-url.com"
response = urllib.request.urlopen(url, context=ctx)
data = response.read().decode("utf-8")

Solution 3: For aiohttp Users

If you're using aiohttp for asynchronous requests:

python
import aiohttp
import ssl

custom_ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
custom_ssl_context.options |= 0x00040000  # SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION

connector = aiohttp.TCPConnector(ssl=custom_ssl_context)

async with aiohttp.ClientSession(connector=connector) as session:
    async with session.get(url) as response:
        return await response.text()

DEVELOPMENT ONLY

For testing purposes only, you might disable SSL verification (not recommended for production):

python
custom_ssl_context.check_hostname = False
custom_ssl_context.verify_mode = ssl.CERT_NONE

Alternative Approaches

OpenSSL Configuration Method

Create a custom OpenSSL configuration file (custom_openssl.cnf):

ini
openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

Then set the environment variable before running your Python script:

bash
export OPENSSL_CONF=/path/to/custom_openssl.cnf
python your_script.py

Package Management Solutions

NOT RECOMMENDED

These approaches downgrade security and should be temporary solutions only.

For pip environments:

bash
pip install cryptography==36.0.2

For conda environments:

bash
conda install openssl=1

Understanding the Technical Context

This error occurs because OpenSSL 3.0+ disabled support for insecure legacy renegotiation by default to protect against security vulnerabilities. Some older servers, particularly embedded systems or legacy infrastructure, haven't implemented the secure renegotiation extension (RFC 5746).

The hexadecimal value 0x4 corresponds to the OpenSSL option SSL_OP_LEGACY_SERVER_CONNECT, which isn't directly exposed in Python's ssl module but can be set using the raw value.

Best Practices

  1. Prefer server updates: Whenever possible, update the server to support modern SSL/TLS standards
  2. Isolate legacy connections: Use the custom session approach only for specific problematic domains
  3. Monitor for updates: Regularly check if the server has been updated to remove the need for legacy support
  4. Document security implications: Keep track of where you've implemented these workarounds

Conclusion

While the "unsafe legacy renegotiation disabled" error can be frustrating, Python provides flexible SSL context configuration that allows you to work with legacy servers when necessary. The custom HTTP adapter approach is generally preferred as it minimizes the security impact by isolating the legacy behavior to specific connections rather than affecting your entire Python environment.

Remember that these solutions should be temporary measures while working toward more secure server configurations that comply with modern SSL/TLS standards.