🐍 Pythonライブラリ `certifi` を徹底解説!安全なHTTPS通信の要 ✨

プログラミング

現代のインターネット通信において、セキュリティは非常に重要です。特に、ウェブサイトやAPIとの通信で広く使われているHTTPSは、通信内容を暗号化し、通信相手が本物であることを保証するためにSSL/TLS証明書を利用します。PythonでHTTPS通信を行う際、この証明書の検証を適切に行うために欠かせないライブラリが certifi です。

この記事では、certifi がなぜ必要なのか、どのように機能するのか、そして具体的な使い方について詳しく解説していきます。Pythonを使った開発を行う上で、安全な通信を実現するための知識を深めていきましょう! 🔑

`certifi` とは何か? 🤔

certifi は、信頼できる認証局(CA: Certificate Authority)のルート証明書のコレクションを提供するPythonパッケージです。これらのルート証明書は、SSL/TLS通信時にサーバーから提示される証明書が正当なものかどうかを検証するために使用されます。

具体的には、certifiMozilla が慎重にキュレート(収集・整理)したルート証明書のリスト(CAバンドル)を含んでいます。Mozillaは主要なウェブブラウザであるFirefoxを提供しており、その証明書ストアは広く信頼されています。certifi は、この信頼性の高い証明書リストをPythonアプリケーションから簡単に利用できるようにパッケージ化したものです。

もともとは、人気のあるHTTPクライアントライブラリである requests プロジェクトの一部でしたが、証明書管理の重要性と独立性を高めるために別のライブラリとして切り出されました。

💡 ポイント

  • certifi は信頼されたルート証明書のコレクションを提供するPythonライブラリ。
  • Mozillaが管理する信頼性の高い証明書リスト(CAバンドル)を利用。
  • HTTPS通信におけるサーバー証明書の検証に使用される。
  • もともとは requests ライブラリの一部だった。

なぜ `certifi` が必要なのか? 🔒

「OSにも証明書ストアがあるのに、なぜわざわざPythonライブラリが必要なの?」と疑問に思うかもしれません。それにはいくつかの理由があります。

異なるOS(Windows, macOS, Linuxディストリビューション)は、それぞれ独自の証明書管理システムと証明書ストアを持っています。これらの場所や形式はOSごとに異なります。Pythonアプリケーションが様々な環境で動作する必要がある場合、OS固有の証明書ストアに依存すると、環境ごとの差異に対応するコードが必要になり、複雑さが増します。

certifi は、OSに依存しない一貫した方法で信頼できるルート証明書のセットを提供します。これにより、Pythonアプリケーションはどの環境で実行されても、同じ信頼基盤に基づいてSSL/TLS証明書の検証を行うことができ、高い移植性を実現します。

インターネット上の信頼関係は常に変化しており、新しい認証局が登場したり、既存の認証局が信頼を失ったり、証明書の危殆化(セキュリティ上の問題)が発生したりします。そのため、ルート証明書のリストは定期的に更新する必要があります。

OSの証明書ストアの更新は、OSのアップデートに依存することが多く、必ずしもタイムリーに行われるとは限りません。また、アプリケーション開発者がOSの証明書ストアの更新を直接コントロールすることは困難です。

certifi は独立したPythonパッケージであるため、pip コマンドを使って簡単に最新版に更新できます。これにより、アプリケーションは常に最新の信頼できる証明書リストを利用することが可能です。セキュリティの観点から、certifi を頻繁に更新することが推奨されています。

PythonでHTTP通信を行う際に最も広く使われているライブラリの一つが requests です。requests は、デフォルトでSSL証明書の検証を行いますが、その際に certifi がインストールされていれば、certifi が提供するCAバンドルを自動的に使用します。

以前のバージョンの requests (バージョン 2.16 より前) では、certifi がインストールされていない場合、requests ライブラリ自体にバンドルされた古い証明書リストを使用していました。これは、requests のバージョンを更新しない限り証明書リストが更新されず、セキュリティ上のリスクがありました。certifi を使うことで、requests のバージョンとは独立して証明書リストを最新に保つことができます。

⚠️ 注意

SSL証明書の検証を無効にすることは、中間者攻撃(Man-in-the-Middle attack)のリスクを高めるため、本番環境では絶対に避けるべきです。開発環境やテスト環境であっても、可能な限り検証を行うようにしましょう。


# requests で検証を無効にする例 (非推奨!)
import requests

response = requests.get('https://example.com', verify=False)
                    

`certifi` の仕組み ⚙️

certifi の中心となるのは、cacert.pem という名前のファイルです。このファイルには、Mozillaが信頼するルート証明書がPEM (Privacy-Enhanced Mail) 形式で連結されて格納されています。

PEM形式は、証明書や秘密鍵をテキスト形式で表現するための標準的なフォーマットの一つで、-----BEGIN CERTIFICATE----------END CERTIFICATE----- で囲まれたBase64エンコードされたデータで構成されます。cacert.pem には、複数の証明書がこの形式で連なって含まれています。

certifi パッケージをインストールすると、この cacert.pem ファイルがPythonのsite-packages ディレクトリ内に配置されます。certifi ライブラリは、このファイルの場所を特定するためのシンプルな関数を提供します。

重要な点として、certifi は提供されたCAバンドルの内容を変更(証明書の追加や削除)する機能はサポートしていません。これは、一貫性があり、移植性の高い信頼の基盤を提供することを目的としているためです。もしカスタム証明書(自己署名証明書や企業内のプライベートCAなど)を使用する必要がある場合は、certificacert.pem を直接編集するのではなく、HTTPクライアントライブラリ(requests など)の機能を使ってカスタム証明書ファイルを指定するか、環境変数(REQUESTS_CA_BUNDLESSL_CERT_FILE)を使用する必要があります。

インストールと基本的な使い方 🛠️

certifi はPyPIで公開されており、pip を使って簡単にインストールできます。


pip install certifi
                

requests ライブラリをインストールすると、通常は依存関係として certifi も自動的にインストールされます。


pip install requests
# requests が依存している certifi も一緒にインストールされる
                

すでにインストールされているか確認し、必要であればアップグレードするには以下のコマンドを実行します。


pip show certifi
pip install certifi --upgrade
                

certifi ライブラリの最も基本的な機能は、インストールされたCAバンドルファイル (cacert.pem) のパスを取得することです。これは certifi.where() 関数を使って行います。


import certifi

# CAバンドルファイルのパスを取得
ca_bundle_path = certifi.where()

print(f"Certifi CA Bundle Path: {ca_bundle_path}")
# 出力例 (環境によってパスは異なります):
# Certifi CA Bundle Path: /path/to/your/python/env/lib/python3.x/site-packages/certifi/cacert.pem
                

この取得したパスは、SSL証明書の検証にCAバンドルファイルを明示的に指定する必要がある場合に使用できます。例えば、標準ライブラリの sslurllib.request を使う場合などが該当します。


import ssl
import urllib.request
import certifi

# certifi が提供するCAバンドルを使ってSSLContextを作成
context = ssl.create_default_context(cafile=certifi.where())

# 作成したコンテキストを使ってHTTPSリクエストを送信
url = "https://www.python.org"
try:
    with urllib.request.urlopen(url, context=context) as response:
        print(f"Status Code: {response.getcode()}")
        # print(response.read().decode('utf-8')[:200]) # コンテンツの一部を表示
except urllib.error.URLError as e:
    print(f"Error accessing {url}: {e}")

                

また、コマンドラインから直接パスを確認することもできます。


python -m certifi
# 出力例: /path/to/your/python/env/lib/python3.x/site-packages/certifi/cacert.pem
                

過去のバージョンとの互換性: 以前のバージョンでは certifi.old_where() という関数が存在し、意図的に古い(1024ビット鍵を含む)証明書を追加したバンドルのパスを返す機能がありましたが、セキュリティ上の理由からこれは非推奨となり、現在は certifi.where() のエイリアス(別名)となっています。古いコードを移行するために一時的に残されていましたが、新しいコードでは必ず certifi.where() を使用してください

`requests` ライブラリとの連携 🤝

前述の通り、requests ライブラリは certifi と密接に連携しています。certifi がインストールされている環境では、requests はデフォルトで certifi.where() が指すCAバンドルファイルを使用してSSL証明書の検証を行います。


import requests

try:
    # requests は自動的に certifi の CA バンドルを使用する (verify=True がデフォルト)
    response = requests.get("https://httpbin.org/get")
    response.raise_for_status() # エラーがあれば例外を発生させる
    print("Request successful!")
    # print(response.json()) # レスポンス内容を表示
except requests.exceptions.SSLError as e:
    print(f"SSL Error: {e}")
except requests.exceptions.RequestException as e:
    print(f"Request failed: {e}")
                

通常、requests を使う際に certifi のパスを明示的に指定する必要はありません。しかし、何らかの理由で特定のCAバンドルファイルを使用したい場合は、verify パラメータにそのファイルのパスを文字列で指定します。


import requests
import certifi

# 明示的に certifi の CA バンドルを指定する場合 (通常は不要)
ca_bundle = certifi.where()
response = requests.get("https://httpbin.org/get", verify=ca_bundle)
print(f"Using CA Bundle: {ca_bundle}")

# 別のカスタム CA バンドルファイルを使用する場合
custom_ca_bundle_path = "/path/to/your/custom/ca_bundle.pem"
try:
    response = requests.get("https://internal.example.com", verify=custom_ca_bundle_path)
    print("Request with custom CA bundle successful!")
except FileNotFoundError:
    print(f"Custom CA bundle not found at: {custom_ca_bundle_path}")
except requests.exceptions.SSLError as e:
    print(f"SSL Error with custom CA bundle: {e}")
except requests.exceptions.RequestException as e:
    print(f"Request failed with custom CA bundle: {e}")
                

環境変数 REQUESTS_CA_BUNDLE を設定することでも、requests が使用するCAバンドルファイルを指定できます。この環境変数が設定されている場合、verify=True であっても、そのパスのファイルが優先的に使用されます。

証明書の更新 🔄

ルート証明書は、セキュリティを維持するために定期的に更新することが非常に重要です。古い証明書リストを使い続けると、以下のような問題が発生する可能性があります。

  • 新しい信頼できる認証局によって発行された証明書を持つウェブサイトに接続できなくなる。
  • セキュリティ上の問題が発見された古いルート証明書を信頼し続けてしまい、潜在的なリスクに晒される。
  • ルート証明書の有効期限切れによる接続エラー。(例: 2021年9月には Let’s Encrypt の古いルート証明書である “DST Root CA X3” が期限切れとなり、古いクライアントで問題が発生しました。)

certifi の更新は非常に簡単です。pip を使ってアップグレードするだけです。


pip install certifi --upgrade
                

これにより、certifi パッケージ自体が最新版になり、それに含まれる cacert.pem ファイルも最新のMozilla CAバンドルに更新されます。

アプリケーションのデプロイプロセスやCI/CDパイプラインに、certifi を定期的に更新するステップを含めることを強く推奨します。📅

注意点とトラブルシューティング 🧐

PythonでHTTPSリクエストを行った際に [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed というエラーに遭遇することがあります。これは、クライアント(Pythonアプリケーション)がサーバーのSSL証明書を検証できなかったことを意味します。主な原因としては以下が考えられます。

  • 古い certifi: CAバンドルが古く、サーバー証明書を発行した認証局が含まれていない。
    • 解決策: pip install certifi --upgradecertifi を更新する。
  • 自己署名証明書またはプライベートCA: サーバーが自己署名証明書や、公的に信頼されていないプライベート認証局によって発行された証明書を使用している。
    • 解決策:
      • サーバー管理者に連絡して、公的に信頼された証明書を取得してもらう。
      • 信頼できることがわかっている場合は、その証明書またはプライベートCAのルート証明書を requestsverify パラメータに指定する。
      • (非推奨)開発環境など限定的な状況下で、一時的に検証を無効にする (verify=False)。
  • 企業内プロキシ/ファイアウォールによるSSLインスペクション: 企業ネットワーク内などで、プロキシサーバーがSSL/TLS通信を復号・検査し、独自の証明書で再暗号化している場合(SSLインスペクション/SSLインターセプト)。この場合、プロキシが発行した証明書を信頼する必要があります。
    • 解決策: ネットワーク管理者に連絡し、プロキシが使用しているルート証明書(または中間証明書)を入手し、それをCAバンドルとして指定する。環境変数 REQUESTS_CA_BUNDLESSL_CERT_FILE を設定するか、requests.get(..., verify='/path/to/proxy/cert.pem') のように指定する。
  • システム時刻のずれ: クライアントPCのシステム時刻が大幅にずれていると、証明書の有効期間の検証に失敗することがある。
    • 解決策: システム時刻を正確に合わせる。NTPサーバーと同期させるのが一般的。
  • サーバー設定の問題: サーバー側で証明書チェーンが正しく設定されていない(中間証明書が不足しているなど)。
    • 解決策: サーバー管理者に連絡して設定を確認してもらう。オンラインのSSLチェックツール(例: SSL Labs Server Test)で確認することも有効。

Windowsでは、OS自体の証明書ストアと certifi が管理する証明書ストアは別個に扱われます。OSの証明書ストアに企業内のルート証明書などを追加しても、デフォルトではPythonの requests などはそれを参照しません。

もしWindowsの証明書ストアにある証明書をPythonアプリケーションから利用したい場合、python-certifi-win32 というライブラリをインストールする方法があります。このライブラリは、certifi がロードされる際にモンキーパッチを適用し、Windowsの証明書ストアの内容を certifi のCAバンドルに(一時的に)マージするような挙動をします。


pip install python-certifi-win32
                

インストールするだけで、import requests などをした際に自動的にWindowsの証明書ストアが考慮されるようになります。ただし、これはWindows固有の解決策であることに注意が必要です。

前述の通り、certifi は直接的な証明書の追加・削除をサポートしません。カスタム証明書を使いたい場合は、以下の方法が一般的です。

  1. `verify` パラメータで指定:
    
    requests.get(url, verify='/path/to/custom_cert.pem')
                            
  2. 環境変数で指定:
    • REQUESTS_CA_BUNDLE: requests が使用するCAバンドルを指定。
    • SSL_CERT_FILE: Pythonの ssl モジュールが参照するデフォルトのCAファイルパスを指定。
    これらの環境変数に、カスタム証明書を含むファイルのパスを設定します。既存の certificacert.pem にカスタム証明書の内容を追記したファイルを作成し、そのパスを指定することが多いです。
    
    # certifiのバンドルとカスタム証明書を結合する例
    cat $(python -m certifi) /path/to/your/custom_cert.pem > combined_bundle.pem
    export REQUESTS_CA_BUNDLE=/path/to/combined_bundle.pem
    python your_script.py
                            
  3. Sessionオブジェクトで設定: 複数のリクエストで同じカスタム証明書を使う場合、requests.Session オブジェクトを使うと効率的です。
    
    import requests
    
    session = requests.Session()
    session.verify = '/path/to/custom_cert.pem'
    
    response1 = session.get('https://internal.example.com/api1')
    response2 = session.get('https://internal.example.com/api2')
                            

セキュリティについて 🛡️

certifi 自体は、信頼できるCA証明書のリストを提供するというシンプルな役割のため、ライブラリ自体に複雑な脆弱性が潜む可能性は比較的低いと考えられます。しかし、セキュリティにおいて重要な役割を担っているため、以下の点に注意が必要です。

  • 常に最新版を使用する: 最も重要なのは、certifi を常に最新の状態に保つことです。古いバージョンを使い続けると、既知の脆弱性を持つ可能性のあるルート証明書を信頼し続けたり、新たに信頼されたCAの証明書が含まれていなかったりするリスクがあります。
  • 脆弱性情報の確認: Snyk や GitHub Advisory Database などの脆弱性データベースで、certifi に関連する脆弱性情報が報告されていないか定期的に確認することも有効です。過去には、特定のルート証明書の信頼性に関する問題(コンプライアンス違反など)から、該当する証明書がバンドルから削除されるといった更新が行われています(例: 2024年のGLOBALTRUSTルート証明書の削除)。これらの更新に対応するためにも、最新版への追従が重要です。
  • 1024ビット鍵の証明書: かつては1024ビット鍵のルート証明書も含まれていましたが、現在では強度が不十分とされており、MozillaのCAバンドルからも削除されています。certifi もこれに追従しており、古いバージョン (certifi.old_where() が有効だった時代) のような弱い鍵の証明書は含まれていません。

🚨 過去の脆弱性例 (参考)

あくまで例ですが、過去には以下のような問題に関連して certifi が更新されたことがあります。

  • CVE-2024-39689 (2024年7月頃): GLOBALTRUST のルート証明書がコンプライアンス問題により Mozilla の信頼リストから削除される動きがあり、certifi のバージョン 2024.7.4 で該当証明書が削除されました。
  • 特定のバージョン範囲で、証明書チェーンの検証に関する問題や、特定の証明書の不適切な検証に関する脆弱性が報告されたこともあります (例: CVE-2023-37920, CVE-2022-23491)。

これらの問題は、certifi を最新版にアップデートすることで修正されています。依存関係スキャンツールなどを活用し、常に安全なバージョンを使用するように心がけましょう。

まとめ 🌐

certifi は、Pythonアプリケーションで安全なHTTPS通信を行う上で、縁の下の力持ちとも言える重要なライブラリです。その主な役割と利点をまとめると以下のようになります。

  • ✅ Mozillaが管理する信頼性の高いルート証明書(CAバンドル)を提供します。
  • ✅ OS環境に依存しない、一貫した証明書検証の基盤を提供し、アプリケーションの移植性を高めます。
  • requests などの主要なHTTPクライアントライブラリと連携し、デフォルトで安全な証明書検証を実現します。
  • pip で簡単にインストール・更新でき、常に最新の信頼できる証明書リストを維持することが可能です。

SSL/TLS証明書の検証は、インターネット上の通信を安全に保つための基本的な要素です。certifi を適切に利用し、常に最新の状態に保つことで、中間者攻撃などのセキュリティリスクからアプリケーションを保護することができます。

Pythonでネットワーク通信を行うアプリケーションを開発する際は、certifi の存在と役割を理解し、安全な通信の実装を心がけましょう! ✨🚀

コメント

タイトルとURLをコピーしました