Python requests で HTTPS リクエスト時の certificate verify failed: self signed certificate in certificate chain エラーに対応する

Python の requests パッケージで https インターネットアクセスする際に、TLS インスペクションが入っている環境で発生する certificate verify failed: self signed certificate in certificate chain エラーに対応する方法を紹介します。

やりたいこと

Python で http リクエストを送る際に requests パッケージを使用することはよくあると思います。 ところが企業の環境等で TLS インスペクションが入っている環境において requests パッケージでインターネットアクセスする際に certificate verify failed: self signed certificate in certificate chain というエラーが発生することがあります。このエラーの対応方法を紹介します。

環境

OS
Microsoft Windows 10 22H2
Python
3.11.2
requests
2.32.3

事象

例えば TLS インスペクションがある環境で以下のようなコードで Yes か No を返してくれる YESNO API を叩いてみます。

import requests

res = requests.get("https://yesno.wtf/api")
print(res.content)

すると以下のようなエラーが発生します。

requests.exceptions.SSLError: HTTPSConnectionPool(host='xxx.com', port=443): Max retries exceeded with url: /api (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:992)')))

このエラーは TLS インスペクションが入っていることにより証明書検証でエラーとなっていることがほとんどです。 TLS インスペクションの簡単な説明と、後ほど使用する証明書の特定、取得方法を こちらの記事 で紹介していますので必要であればご参照ください。

対応方法

証明書検証をオフにする (非推奨)

requests では requests.request() メソッドのキーワード引数 verifyFalse を渡すことで証明書検証をオフにすることができます。ただしあらゆる HTTPS における証明書検証がなされなくなるので、この方法はおすすめしません。

なお、キーワード引数 verifyrequests.request() のラップメソッドである requests.get()requests.post() などでも同様に使用できます。

verify – (optional) Either a boolean, in which case it controls whether we verify the server’s TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to True. Developer Interface - Requests 2.32.3 documentation

verify=False として実行すると、

import requests

res = requests.get("https://yesno.wtf/api", verify=False)
print(res.content)

以下のワーニングが表示されますが、

InsecureRequestWarning: Unverified HTTPS request is being made to host 'xxx.xxx'. Adding certificate verification is strongly advised. 

API のレスポンスは受け取れていることがわかります。

{
  "answer": "yes",
  "forced": false,
  "image": "https://yesno.wtf/assets/yes/0-c44a7789d54cbdcad867fb7845ff03ae.gif"
}

自己署名証明書を指定する

本質的にはプロキシの自己署名証明書を追加することがあるべき姿です。

requests.request() メソッドのキーワード引数 verify の公式ドキュメントの記載を再掲しますが、 verify は Boolean 値以外に証明書パスを指定できます。繰り返しになりますが、追加する証明書の特定、取得方法を こちらの記事 で紹介していますので必要であればご参照ください。

verify – (optional) Either a boolean, in which case it controls whether we verify the server’s TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to True. Developer Interface - Requests 2.32.3 documentation

キーワード引数 verify に証明書パスを指定して実行するとワーニングも出ずに API のレスポンスが取得出来ると思います。

import requests

res = requests.get("https://yesno.wtf/api", verify="C:/path/to/cert.pem")
print(res.content)

環境変数で自己署名証明書を指定する

requests では環境変数 REQUESTS_CA_BUNDLE で証明書を指定することもできます。サードパーティーのパッケージの中には http リクエストを行うために requests を組み込んでいるものが多くあります。例えば kagglehub パッケージの model_download() メソッドはその一つですが、model_download() の引数に verify のようなものは用意されていません。そのような場合、環境変数で証明書をしていすることで外から証明書パスを指定することができます。

This list of trusted CAs can also be specified through the REQUESTS_CA_BUNDLE environment variable. Advanced Usage - Requests 2.32.3 documentation

環境変数 REQUESTS_CA_BUNDLE に証明書パスを指定して実行すると問題なく API レスポンスが取得できます。

import os
import requests

os.environ["REQUESTS_CA_BUNDLE"] = "C:/path/to/cert.pem"
res = requests.get("https://yesno.wtf/api")
print(res.content)

ここでは Python で環境変数を設定していますが、PowerShell やコマンドプロンプトで設定することも可能です。

あとがき

TLS インスペクションが入った環境化における Python の requests パッケージで HTTPS リクエスト時に発生する certificate verify failed: self signed certificate in certificate chain エラーの対応方法について紹介しました。まだまだ OS の証明書ストアを見てくれないものが多いので個別に対応する必要があり面倒ですね。