LoginSignup
7
7

More than 5 years have passed since last update.

Rubyでクライアント認証をやってみる

Posted at

クライアント証明書を利用したHTTPS通信をやってみました。

準備

まずは、オレオレ認証局を立てて、サーバ証明書とクライアント証明書を発行します。
ここがとても参考になりました。
 → https://qiita.com/k-masaki/items/12b5e8a1874214308912
 
セキュリティーの関係でちょっと変わったところがあったので、その点を記録しておきます。

SHA1 → SHA256 に変更
OpenSSL::Cipher::Cipher.new('aes256') → OpenSSL::Cipher.new("AES-256-CBC")

くらいですか。最後のはまあおまけみたいなものですが。。。

各証明書ができたらサーバに設定します。
今回は nginx に設定します。
 → https://qiita.com/tukiyo3/items/71245d7263fed3601ebd

server {
  listen 443 ssl;

  ssl_certificate /domain.crt;
  ssl_certificate_key /domain.key;

  ssl_verify_client on;
  ssl_client_certificate /ca.pem;
}

そしたら再起動。$ /etc/init.d/nginx stop $ /etc/init.d/nginx start

クライアント証明書のインストール

client.pfx をダブルクリックすると、クライアント証明証のパスワードを聞いてくるので、入力します。するとキーチェーンアクセスが起動するので、「ログイン」のところにインストールします。

で、safari でアクセス!
いろいろ聞いてきますが、適宜対応します(^^
表示されればOKです。

Rubyでアクセス!

clienttest.rb
# -*- coding: utf-8 -*-
require "json"
require "openssl"
require "net/http"
require "uri"
require "cgi"

class ClientCertTest

  attr_accessor :use_client_cert

  def initialize
    @use_client_cert = false
    @client_pass = "password"
  end

  # --------------------------------------------------------------------------
  def request_post(postdata, endpoint, htmlret = false)
    uri = URI.parse(endpoint)
    response = nil
    begin
      if endpoint =~ /^https/
        https = Net::HTTP.new(uri.host, 443)
        https.use_ssl = true
        if @use_client_cert
          pkcs = OpenSSL::PKCS12.new(File.read("client.pfx"), @client_pass)
          https.verify_mode = OpenSSL::SSL::VERIFY_PEER
          https.ca_file = "ca.pem"
          https.key = pkcs.key
          https.cert = pkcs.certificate
        else
          https.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end
        https.start{|h|
          request = Net::HTTP::Post.new(uri.path)
          request.set_form_data(postdata)
          response = h.request(request)
        }
      else
        Net::HTTP.start(uri.host, uri.port){|http|
          request = Net::HTTP::Post.new(uri.path)
          request.set_form_data(postdata)
          response = http.request(request)
        }
      end
      p response.code
      jsondata = response.body.strip
      errflg = false
      errflg = true if htmlret == false && jsondata =~ /^<html>/
      if errflg
        raise ClientCertTestError.new(jsondata.force_encoding("utf-8"))
      end      
    rescue
      jsondata = nil
    end
    return jsondata
  end

  # --------------------------------------------------------------------------
  def request_get(getdata, endpoint, htmlret = false)
    dparams = URI.encode_www_form(getdata)
    uri = URI.parse(endpoint + "?" + dparams)
    response = nil
    begin
      if endpoint =~ /^https/
        https = Net::HTTP.new(uri.host, 443)
        https.use_ssl = true
        if @use_client_cert
          pkcs = OpenSSL::PKCS12.new(File.read("client.pfx"), @client_pass)
          https.verify_mode = OpenSSL::SSL::VERIFY_PEER
          https.ca_file = "ca.pem"
          https.key = pkcs.key
          https.cert = pkcs.certificate
        else
          https.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end        
        https.start{|h|
          request = Net::HTTP::Get.new(uri.request_uri)
          response = h.request(request)
        }
      else
        Net::HTTP.start(uri.host, uri.port){|http|
          request = Net::HTTP::Get.new(uri.request_uri)
          response = http.request(request)
        }
      end
      jsondata = response.body.strip
      errflg = false
      errflg = true if htmlret == false && jsondata =~ /^<html>/
      if errflg
        raise ClientCertTestError.new(jsondata.force_encoding("utf-8"))
      end
    rescue
      #jsondata = nil      
    end
    return jsondata    
  end
end

class ClientCertTestError < StandardError
end


# ----------------
# run

cct = ClientCertTest.new
cct.use_client_cert = true

endpoint = "https://localhost/index.html"
postdata = { "hoge" => "tara" }

jsondata = cct.request_get(postdata, endpoint, true)
p jsondata
p ""
jsondata = cct.request_post(postdata, endpoint, true)
p jsondata

#少々コードが冗長ですが、まあご愛嬌で(^^
はい。そしたら実行して見ます。

実行してみる

$ ruby ./clienttest.rb

"<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>"
""
"<html>\r\n<head><title>405 Not Allowed</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>405 Not Allowed</h1></center>\r\n<hr><center>nginx/1.10.3</center>\r\n</body>\r\n</html>"

前者が GET で、後者が POST です。
GET はうまくいっている感じですね。
POST は、 nginx の場合静的ファイルに対しての POST は 405 を返すらしいので、まあ正しい反応ということで。。。

nginx + fastcgi + bash で動作確認

#cgi に bash を使う場合は Shellshock攻撃 に注意しましょうね!

がしかし、それだとあまりにもあれなので、一応 POST もちゃんと返すか確認します。
というわけで、とりあえず fastcgi をインストールする。
 → https://qiita.com/hmikisato/items/c793ced0ba2695a89de6

$ apt-get install fcgiwrap

fastcgi の設定は、特に変更しなくても大丈夫でしたので、そのままで実行。

$ /etc/init.d/fcgiwrap start

nginx側の設定を修正。以下を追加。

  location ~ \.cgi$ {
    root /var/www/cgi;

    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

で受け側作成
 → http://www.koikikukan.com/archives/2014/04/11-015555.php

$ cd /var/www/cgi
$ vi test.cgi
#!/bin/bash
cat <<EOF1
Content-Type: text/html

<html>
<head><title>Test</title></head>
<body>
EOF1
cat $QUERY_STRING
cat <<EOF2
</body>
</html>
EOF2

ruby のコードも endpoint のところを index.htmltest.cgi に修正。

endpoint = "https://localhost/test.cgi"

で実行すると。。。

$ ruby ./clienttest.rb

"<html>\n<head><title>Test</title></head>\n<body>\n</body>\n</html>"
""
"<html>\n<head><title>Test</title></head>\n<body>\nhoge=tara</body>\n</html>"

エラーは出ないみたいですね。
GET(前者)の方、$QUERY_STRING が表示されないのはわかりませんが、とりあえず POST の方もいけてるみたいなので、これで OK ということで?

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7