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

クライアント証明書を利用した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でアクセス！

```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.html``` → ```test.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 ということで？