Python requests で SSLError が起きて毎回ググってるのでまとめた

Python で REST API を叩く時は requests ライブラリを使うが、最近の REST API は HTTPS をメインに使うようになった。これに伴い SSLError というよくわからんエラーが出るように。

このエラーは何を意味するのか。どう回避すればいいのか。その辺を知るヒントとして、requests 公式ドキュメントを意訳したもの+α をまとめてみた。

SSL Cert Verification

Advanced Usage — Requests 2.18.4 documentation

SSL 認証に絡む挙動や使い方。

  • requests はウェブブラウザみたくSSL認証(SSL証明書を用いた認証)を行う
  • デフォでは SSL 認証は有効になってて、認証にしくじったら SSLError 例外を投げる

SSL 認証の指定は verify 引数にて行う。

  • verify 引数が False だと認証を行わない
  • verify 引数が True だと認証を行う(デフォルト)
  • verify 引数が「認証局によって認証された CA_BUNDLE ファイルパス」だとそれを使って認証を行う
    • あるいは REQUESTS_CA_BUNDLE 環境変数に指定してもいい
  • verify 引数が「認証局によって認証された CA_BUNDLE ファイル、のあるディレクトリ」だと OpenSSL の c_rehash 関数 と同じ動きで認証を行う
    • 要するにディレクトリ中の証明書ファイル達を使う、というやり方もできるということ

Client Side Certificates

Advanced Usage — Requests 2.18.4 documentation

SSL 認証とは話が逸れるが「クライアント認証」についての話。

SSL 認証は「クライアントが、サーバーに対して、"あなたは信頼できる相手なんですよね?"」を知るための仕組みだが、クライアント認証はその逆で「サーバーが、クライアントに対して "あなたは信頼できる利用者なんですよね?"」を知るための仕組み。つまりは相互認証という、より堅固な認証のお話であって、SSL 認証とは関係が無い話題。

CA Certificates

requests が証明書ファイルをどうやって管理しているかという話。

まず昔の話:

  • 昔(v2.16以前) は Mozilla が作ってる証明書ファイルをバンドルしていた
  • でもこの方法だと、Mozilla 側の更新を取り込めない(requests にバンドルしてる側の証明書ファイルはどんどん古くなっていく)

更新を取り込むにはどうしたらいい?と考えて、requests が取ったのは以下の仕組み。

  • 証明書ファイル更新の仕組みを certifi という別パッケージに分離した
  • requests は certifi を使って証明書ファイルにゲット&更新している
  • 証明書ファイルを更新したい場合、自分で certifi パッケージをアップデートしてね
    • コマンドで言うと pip install -U certifi

Q: 結局 SSLError を逃れるためには要するにどうすればええの?

解1: verify=False をセットする

てっとり早い方法。

ただしこれは「SSL 認証はしません」というやり方であるため、通信先が本当に信用できる場合以外は使わないこと。もし信用しちゃいけない相手に対して verify=False しちゃうと(もっと言うとその先の HTTPS 通信でプライベートなデータを渡しちゃうと)最悪、通信先の悪者に悪用されちゃう。

解2: verify='SSL証明書のパス' をセットする

SSL証明書、サーバ証明書、ルート証明書、など呼び方は色々あるが、とにかく「SSL認証で使う証明書」を指定する。そうしたら、指定したその証明書の範囲で SSL 認証をしてくれる。

証明書は CA_BUNDLE という「信頼できる証明書のリスト」的なデータを使うのがてっとり早い。もし会社業務などで別途証明書のインポートを指示されてるなら、その証明書も追加してやる。

証明書ファイルは大体 .pem ファイルか .crt ファイル。中身は公開鍵の羅列。

ああ、あと CA_BUNDLE の入手先はたとえば以下。

おわりに

うん、もう迷わない(と思う)。