SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED エラーの解決方法
問題の概要
PythonでHTTPS経由の接続(例: HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443)
)を行う際に、以下のSSLエラーが発生することがあります:
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アダプターを作成して安全でないレガシーな再ネゴシエーションを許可できます:
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
を直接使用している場合の解決方法:
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を使用している場合:
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設定ファイルを使用する方法もあります:
- カスタム
openssl.cnf
ファイルを作成:
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
- 環境変数を設定してPythonスクリプトを実行:
export OPENSSL_CONF=/path/to/custom/openssl.cnf
python your_script.py
注意
この方法はシステム全体の設定を変更するため、他のアプリケーションにも影響を与える可能性があります。可能な限りアプリケーション単位の設定(方法1-3)を使用してください。
その他の解決策
Conda環境でのOpenSSLダウングレード
Conda環境を使用している場合:
conda install -n your-env-name openssl=1.0
cryptographyパッケージのダウングレード
pip install cryptography==36.0.2
TIP
これらのダウングレード方法は一時的な解決策であり、セキュリティアップデートが受けられなくなるため、長期的な使用は推奨されません。
まとめ
UNSAFE_LEGACY_RENEGOTIATION_DISABLED
エラーは、現代的なSSL実装と古いサーバー間の互換性問題です。最も安全で効果的な解決方法は、アプリケーション単位でカスタムSSLコンテキストを使用する方法(方法1)です。セキュリティリスクを理解した上で、適切な解決策を選択してください。
信頼できる環境でない場合や、サーバー側を更新できる場合は、サーバーのSSL設定を現代的な標準に更新することが最良の長期的解決策です。