HOWTO · Python
Pythonの「SSL: CERTIFICATE_VERIFY_FAILED」証明書検証エラーを解決する方法
Pythonで発生する証明書検証エラーの原因を切り分け、安全に解決する方法を説明します。
このページの内容
SSL: CERTIFICATE_VERIFY_FAILED は、TLS接続で提示された証明書が、Pythonの信頼する認証局につながり、要求されたホスト名とも一致することを確認できなかったことを意味します。これはクライアント側の検証失敗であり、ドメインが存在しないために認証局がエラーを返したという意味ではありません。
TLSは通信を暗号化し、証明書によってサーバーの身元を確認します。どの信頼確認に失敗したかを特定する間も、証明書検証は有効にしておきましょう。
SSL: CERTIFICATE_VERIFY_FAILED の意味
TLS証明書は接続時にサーバーから提示されます。クライアントは証明書の有効期間、ホスト名、発行者の証明書チェーン、そしてそのチェーンがクライアントの信頼ストアにある信頼済みルート証明書までつながるかを確認します。Pythonのsslモジュールは、検証を行うSSLコンテキストを使うと、これらの確認を実施します。Requestsも既定で証明書を検証します。
接続のどちら側に問題があっても、このエラーは発生し得ます。たとえば、サーバーが中間証明書を送信していない場合や、ローカルのPython環境に古いCAバンドルがある場合です。ホスト名の不一致、システム時計のずれ、プライベート認証局、TLSを検査するプロキシも原因になり得ます。エラーだけでは、どの原因に該当するかは分かりません。
原因を特定する
問題が起きているアプリケーションと同じホスト名、同じPython環境を使って、次の項目を確認してください。
- サーバー証明書と証明書チェーンを確認する。 証明書が有効期間内であること、URLのホスト名に対して発行されていること、発行者までたどるのに必要な中間証明書が含まれていることを確認します。ブラウザーや別のクライアントで接続できる場合は、その信頼設定やネットワーク経路を比較してください。違いは手掛かりになりますが、サーバーに問題がない証明にはなりません。
- 時計を確認する。 マシンやコンテナーの日付と時刻が正しいことを確認します。時計が証明書の有効期間から大きくずれていると、有効な証明書が期限切れ、または有効期間前と判定されることがあります。
- Python環境とHTTPクライアントを特定する。 アプリケーションを実行しているインタープリターと、問題のリクエストが標準ライブラリ、Requests、その他のパッケージのどれを使っているかを確認します。システムのPython、仮想環境、コンテナー、パッケージ化されたアプリケーションでは、異なる信頼ストアが使われることがあります。
- 信頼の参照元とネットワーク経路を確認する。 サーバーがプライベートCAを使っているか、社内プロキシがTLSを傍受しているかを調べます。ネットワークまたはセキュリティ担当者に、承認済みのCA証明書と、その環境向けの手順を問い合わせてください。エラーを消すために、入手元を確認できない証明書をダウンロードしてはいけません。
サーバー証明書の期限が切れている、ホスト名が一致しない、または必要な中間証明書が不足している場合は、サーバー管理者にデプロイ設定の修正を依頼してください。クライアントのCAバンドルを変更しても、壊れたサーバー側の証明書チェーンは修復できません。
証明書または信頼ストアを修正する
サーバー側に問題がある場合、恒久的な解決策は、サーバー管理者が要求されたホスト名に合う証明書へ更新するか、証明書チェーンを正しく設定することです。別のブラウザーで再試行すると動作の比較には役立ちますが、証明書の問題を修復したり、信頼できない接続を安全にしたりすることはできません。
プライベートサービスや、承認済みのTLS検査プロキシを使う場合は、組織の信頼できる配布経路からルートCA証明書を入手してください。対象のOS、コンテナー、Python環境で使われる信頼ストアにインストールするか、アプリケーションにそのCAバンドルを使うよう設定します。証明書の入手元と適用範囲を確認できるよう、管理者が文書化した手順に従ってください。
サーバーが新しい有効な公開CAを使っている場合、古いCAバンドルが原因で失敗することもあります。アプリケーションが使う信頼ストアまたはCAバンドルのパッケージを更新してください。Pythonのインストール環境によっては、pipを更新すると、pip自身がパッケージのダウンロード時に証明書を検証できるようになる場合があります。Requestsがcertifiのバンドルを使う設定なら、certifiを更新することでそのバンドルを新しくできることがあります。こうした更新では、期限切れのサーバー証明書、ホスト名の不一致、不足しているサーバー中間証明書、信頼設定に追加されていない組織固有のCAは修正できません。
PythonとRequestsで証明書検証を設定する
Python標準ライブラリのsslモジュールとRequestsでは、異なる信頼情報源が使われることがあります。ssl.create_default_context()は、そのPythonとOpenSSLの環境で利用できる既定の証明書を読み込みます。内容はビルド方法やプラットフォームの設定によって異なる場合があります。Requestsは通常、certifiが提供するCAバンドルで証明書を検証します。session.verify = Trueは「Requestsに設定された既定のバンドルを使う」という意味であり、「Pythonのsslの既定値を使う」ことや、必ずOSのストアを使うことを意味するわけではありません。セッションで明示的に設定しない限り、RequestsはREQUESTS_CA_BUNDLE(またはCURL_CA_BUNDLE)からバンドルのパスを取得することもあります。
次の短い例では、Pythonの既定コンテキストで証明書検証が必須であることを確認できます。この例は外部サービスへの接続を必要としません。表示されるTrueは検証が必須であることを示し、出力は英語表記のままです。
このセクションのコードと表示されるラベルや出力は英語のままです。
import ssl
context = ssl.create_default_context()
print(context.verify_mode == ssl.CERT_REQUIRED)
True
プライベートCAを使う場合は、信頼済みのCAバンドルをcafileとしてPythonに渡し、その検証用コンテキストを標準ライブラリのHTTPSリクエストに指定します。URLとバンドルのパスは、管理者から提供されたサービス用の値に置き換えてください。この例はサービスへの接続を伴うため、ここでは実行していません。値を設定して実行できるよう、接続エラーを捕捉します。
import ssl
import urllib.error
import urllib.request
SERVICE_URL = "https://service.example"
CA_BUNDLE = "/path/to/organization-ca-bundle.pem"
try:
context = ssl.create_default_context(cafile=CA_BUNDLE)
with urllib.request.urlopen(SERVICE_URL, context=context, timeout=10) as response:
print(response.status)
except (OSError, urllib.error.URLError) as error:
print(f"Connection not completed: {type(error).__name__}: {error}")
Requestsのverify設定にはCAバンドルのパスを指定できます。組織から提供されたバンドルをセッションに設定し、同じセッションでリクエストを送信してください。証明書検証は有効なままです。URLとパスは利用するサービスに合わせて置き換えてください。この例もサービスへの接続を伴うため、ここでは実行していません。
import requests
SERVICE_URL = "https://service.example"
CA_BUNDLE = "/path/to/organization-ca-bundle.pem"
session = requests.Session()
session.verify = CA_BUNDLE
try:
response = session.get(SERVICE_URL, timeout=10)
print(response.status_code)
except (OSError, requests.RequestException) as error:
print(f"Connection not completed: {type(error).__name__}: {error}")
発行者を組織の信頼できる経路で確認したCAバンドルを使用してください。カスタムバンドルには、サービスの証明書チェーンを検証するために必要なCA証明書が含まれている必要があります。CAを設定しても、期限切れの証明書、ホスト名の不一致、サーバー側で不足している中間証明書は修復できません。Requestsに通常のバンドルを使わせる場合は、session.verifyにTrueを設定せず、既定値のままにしてください。組織のCAを使う場合はパスを明示します。古い信頼情報がRequestsに同梱された公開ルート証明書なら、certifiの更新で新しくできる場合があります。また、pipの更新はpip自身のパッケージダウンロードに役立つことがあります。どちらの更新も、すべての環境でPythonのsslの信頼設定を変更するわけではなく、壊れたサーバー証明書を修復するものでもありません。
証明書検証を無効にしてはいけない理由
証明書検証を無効にすると、攻撃者や信頼できないプロキシが別の身元を示す証明書を提示しても、クライアントが接続を受け入れてしまいます。通信が暗号化されているだけでは、相手が意図したサーバーであることを確認できません。その結果、認証情報、トークン、応答データが漏えいしたり、改ざんされたりするおそれがあります。
修正方法として、verify=False、検証を行わないSSLコンテキスト、またはPythonの既定HTTPSコンテキストのグローバルな置き換えを使わないでください。こうした方法は診断の手掛かりを隠し、影響を受けるすべての接続を弱めます。サーバーの証明書チェーン、クライアントの時計や信頼ストア、または明示的に設定した信頼済みCAバンドルを修正し、検証を有効に戻してください。