Skip to content

SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED エラーの解決方法

問題の概要

PythonでHTTPS経由の接続(例: HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443))を行う際に、以下のSSLエラーが発生することがあります:

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

このエラーは、クライアントがOpenSSL 3を使用している場合に、RFC 5746による安全な再ネゴシエーションをサポートしていない古いサーバーに接続しようとすると発生します。

セキュリティ注意事項

レガシーな安全でない再ネゴシエーションを有効にすると、CVE-2009-3555で説明されているMan-in-the-Middleプレフィックス攻撃に対して脆弱になります。この解決策は、セキュリティリスクを理解した上で、信頼できる環境でのみ使用してください。

解決方法

方法1: PythonリクエストでカスタムSSLコンテキストを使用する(推奨)

requestsライブラリを使用している場合、カスタムのHTTPアダプターを作成して安全でないレガシーな再ネゴシエーションを許可できます:

python
import requests
import urllib3
import ssl

class CustomHttpAdapter(requests.adapters.HTTPAdapter):
    """カスタムSSLコンテキストを使用するためのトランスポートアダプター"""
    
    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 (値は0x4)
    session = requests.session()
    session.mount('https://', CustomHttpAdapter(ctx))
    return session

# 使用例
response = get_legacy_session().get("https://example.com")
print(response.text)

方法2: urllibで直接使用する場合

urllibを直接使用している場合の解決方法:

python
import urllib.request
import ssl

# カスタムSSLコンテキストの作成
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4  # OP_LEGACY_SERVER_CONNECT

# URLオープン時にコンテキストを指定
url = "https://example.com"
response = urllib.request.urlopen(url, context=ctx)
data = response.read().decode("utf-8")

方法3: aiohttpを使用する場合

非同期HTTPリクエストライブラリのaiohttpを使用している場合:

python
import aiohttp
import ssl

# カスタムSSLコンテキストの作成
custom_ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
custom_ssl_context.options |= 0x00040000  # SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION

# 開発環境でのみ使用(本番環境では非推奨)
# custom_ssl_context.check_hostname = False
# custom_ssl_context.verify_mode = ssl.CERT_NONE

connector = aiohttp.TCPConnector(ssl=custom_ssl_context)

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

環境変数を使ったシステム全体の設定

一時的な解決策として、カスタムOpenSSL設定ファイルを使用する方法もあります:

  1. カスタム 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
  1. 環境変数を設定してPythonスクリプトを実行:
bash
export OPENSSL_CONF=/path/to/custom/openssl.cnf
python your_script.py

注意

この方法はシステム全体の設定を変更するため、他のアプリケーションにも影響を与える可能性があります。可能な限りアプリケーション単位の設定(方法1-3)を使用してください。

その他の解決策

Conda環境でのOpenSSLダウングレード

Conda環境を使用している場合:

bash
conda install -n your-env-name openssl=1.0

cryptographyパッケージのダウングレード

bash
pip install cryptography==36.0.2

TIP

これらのダウングレード方法は一時的な解決策であり、セキュリティアップデートが受けられなくなるため、長期的な使用は推奨されません。

まとめ

UNSAFE_LEGACY_RENEGOTIATION_DISABLEDエラーは、現代的なSSL実装と古いサーバー間の互換性問題です。最も安全で効果的な解決方法は、アプリケーション単位でカスタムSSLコンテキストを使用する方法(方法1)です。セキュリティリスクを理解した上で、適切な解決策を選択してください。

信頼できる環境でない場合や、サーバー側を更新できる場合は、サーバーのSSL設定を現代的な標準に更新することが最良の長期的解決策です。