Python3でSSLクライアント認証してみた

AWS

結構、SSL認証関連の業務をしてきたので、

普通のブラウザだけでなくプログラムからSSLクライアント認証接続してみたくなり試してみた結果を書いときます!

ざっくりと試した内容:

EC2インスタンス上にnginxを立てて、SSLクライアント認証の設定をする

ローカル環境でEC2インスタンスに向けてHTTPS接続リクエストをpythonプログラムから投げる

ステータスコード200が返ってきたらOK

では、やってみよう!!

1:EC2インスタンスを立てる

VPCは特にこだわってないので、デフォルトに立てた

SGはローカルからポート「22」と「443」のみ許可

2:nginxをインストールする

※yum updateしてからだと、パッケージ更新されるためかnginxが見つからないって怒られるため、今回はアップデートせずに実行した

3:ssl設定用にオレオレ認証局を同じインスタンス上に作る

作ると言ってもただ、このインスタンス上に認証局用の秘密鍵、証明書を作るだけ。

あとは、作った認証局用の秘密鍵、証明書でクライアント用の証明書とサーバ用の証明書を作る。

詳しい作り方は別記事参照(また、あげとく)

※コマンドだけ書くとこんな感じ

ちなみに署名要求作成時のcommonnameは認証局のものとサーバのものは合わせておいた。

commonnameには、HTTPS接続先のドメインと同じものが入っていなかった場合、エラーが発生して繋げない。。

結果的には、認証局の証明書では接続できず、サーバ証明書での接続なら成功したため、認証局の証明書とサーバ証明書のCommonnameを合わせる必要があるかどうかはよーわからんかった(笑)

ま、今回は合わせておきましょう!

4:nginxにSSL認証設定を行う(まずは、サーバ証明のみやってみる)

「/etc/nginx/nginx.conf」ファイルのSSL設定の箇所をコメントアウトする。

こんな感じ

サーバ証明書とサーバ用の秘密鍵を「/etc/nginx/ssl/」に配置する

以上で、サーバ側の設定は完了!

service nginx startコマンドを実施後、ブラウザでEC2のパブリックIPにHTTPS接続するとこんな画面が出てくるはず。

では、次にローカルからEC2インスタンスにHTTPS接続するpythonプログラムを作る

5:pythonプログラムからHTTPS接続を行う(サーバ証明のみ)

適当にディレクトリをデスクトップとかに作って下記コマンドを実施し、requestsモジュールをディレクトリ内にインストールする

pip install requests -t ./

index.pyファイルを作成し、こんな感じでコーディングする

そして、index.pyを実行するとこんなエラーが出る

どうやら、認証に失敗しているみたいだね

それもそのはず、pythonでHTTPS接続する場合にデフォルトで信用しているトラストストアにオレオレ認証局が署名したサーバ証明書なんて入っているはずがないからだ。

ってことで、verifyパラメータをrequests.get関数に渡してやる。こんな感じ

今回の例では、index.pyと同じディレクトリ内にkeysというディレクトリを作って、そこにサーバ証明書を配置している「./keys/server.crt」がそれだ。

で、index.pyを実行すると200番が返ってくる!!認証成功!!

※この時、サーバ証明書ではなく、ルート証明書(オレオレ認証局の証明書)をverifyパラメータに設定した場合、

というエラーが出た。証明書チェーンがうまくできていないのかなんなのか不明

また気が向いたら調べようと思う。知ってたら教えてちょ

6:pythonプログラムでHTTPS接続(クライアント認証あり)を行う

そのために、まずサーバのnginxにクライアント認証を要求するように設定を加える

こんな感じ

nginxを再起動したら、ローカルのindex.pyを実行してみる

するとこんなエラーになる

んーなんや、ようわからんけど、ステータスコード「400」が返ってきてるってこともあって、原因はクライアント認証の要求をサーバ側からされているのに、クライアント証明書をサーバ側に渡していないことにあると仮定(笑)

で、index.pyをこんな感じに修正

requests.get関数にcertパラメータを渡しているところがミソ

これに、タプル型で1つめにクライアント証明書、2つめにクライアント用の秘密鍵を格納したものを渡す。

これを踏まえて、index.pyを実行すると

んー、やはりWarningは消えないなー

なんか、証明書にsubjectAltNameがなかった場合に出るWarningっぽいなー

ま、でも200番が返ってきているので今回は良しとしまーす

また、暇があれば、この原因についても探っていければと。

ちな、リクエストをめちゃめちゃ投げる処理の場合、requestsではなく、Sessionオブジェクトを使ってセッション張る際にのみSSL認証をした方が処理速度が速くなるらしい。

ま、リクエストのたびにSSL認証するんやから、当然遅くなるよね。

ダメ押しにSessionオブジェクトを使った場合のSSL通信も載せときます

コードはこんな感じ

セッションオブジェクトを使用したHTTPS通信でSSLの設定をする方法は2通りあって、↑のようにセッションオブジェクトに設定する方法と、後述するメソッドの引数から設定する方法だ。両者の違いは後で説明するとして、まずはこのソースを実行したときのことを書く

セッションオブジェクトのget関数でHTTPS通信をしているのだが、セッションを張っているため、二回目のHTTPS通信時には認証処理をすっ飛ばしてレスポンスが返ってくる

次に一回目と二回目のHTTPS通信の間でセッションを切ってみる

こんな感じ

この場合、二回目のHTTPS通信時にセッションを張りなおすためSSL認証処理が行われる。ただ、セッションオブジェクトにSSLの設定をしているため、一回目の接続と二回目の接続の間でセッションを切ったとしてもSSL認証処理を逐一コーディングしなくても実行してくれる

次は、メソッドレベル(get関数の引数にてSSL設定を行うこと)でSSL設定をした場合の挙動を見てみる

この場合、一回目の接続時にはSSL設定を引数にて渡しているため、正常にSSL認証がされて200番が返ってくるが、二回目の接続前にセッションを切ると、二回目の接続時には、SSL認証が失敗する

このように、セッションオブジェクト自体にSSL設定する場合と違い、メソッドレベルで設定した場合はセッションを切ると逐一SSL設定を引数で渡してやる必要がある

ま、セッション切らんかったら二回目の200番返ってくるんやけどね。

close処理を書かんでいいようにwithを使ってる場合なんかは、セッションオブジェクトにSSL設定したほうがええんちゃうかなー

以上、Python3でSSLクライアント認証したみたでした! BYE!

コメント

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