信頼されない証明書を使ったHTTPSサーバーにBasic認証でPOST - Ruby, PHP, C#, Java, Groovy
信頼されないSSL証明書(自己証明書)を使ったサイトに対して、Basic認証を行い POST するサンプルを Ruby, PHP, C#, Java, Groovy で実装してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20111002/
サンプルは、第1引数に URL、第2引数と第3引数に Basic 認証のユーザー名とパスワード、第4引数に POST するデータを指定するようにしています。
なお、POST データを & で区切ると Windows のコマンドプロンプトなどで実行するには不都合があるので ; で区切っている点に注意。
サンプルの実行例
> jruby basic_post_novalidate_certificate.rb https://localhost:8443/ user1 pass1 mode=test;id=123 hello
HTTPS サーバープログラム作成
まずは、クライアントからの処理を受け付けるサーバープログラムを作成し、起動しておきます。
サーバープログラムは HTTPS・Basic認証・POST を処理する必要がありますので、今回は Sinatra + WEBrick で実現してみました。
実装が容易で証明書などの準備も不要なのでテスト用途にはお勧めな方法です。(証明書が未指定なら実行時に自動生成してくれる)
下記では ポート 8443 で実行、クライアントの検証を実施せず(:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE)、ユーザー名が user1 ならBasic認証を通るように実装しています。
https_server.rb (Sinatra を使った HTTPS サーバープログラム)
require "rubygems" require "sinatra/base" require "webrick/https" require "openssl" class SampleApp < Sinatra::Base #Basic認証 use Rack::Auth::Basic do |user, pass| user == 'user1' end #POST の処理 post '/' do p params 'hello' end end #WEBrick で SSL を使用するための処理 #(実行時に自己証明書が自動生成される) Rack::Handler::WEBrick.run SampleApp, { :Port => 8443, :SSLEnable => true, #クライアントを検証しないための設定 :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, :SSLCertName => [ ["CN", WEBrick::Utils::getservername] ] }
Ruby による Web クライアント
Ruby では、use_ssl を true にした Net::HTTP オブジェクトの verify_mode に OpenSSL::SSL::VERIFY_NONE を設定すれば自己証明書を処理できるようになります。
basic_post_novalidate_certificate.rb
require 'net/https' require 'uri' url = URI.parse(ARGV[0]) user = ARGV[1] pass = ARGV[2] postData = ARGV[3] https = Net::HTTP.new(url.host, url.port) #SSLの有効化 https.use_ssl = true #SSL証明書を検証しないための設定 https.verify_mode = OpenSSL::SSL::VERIFY_NONE res = https.start do req = Net::HTTP::Post.new(url.path) #Basic認証 req.basic_auth user, pass #POSTデータの設定 req.body = postData #POST https.request(req) end #結果の出力 print res.body
PHP による Web クライアント
PHP で file_get_contents を使えば HTTPS を特に意識しなくてもよいので簡単に実装できます。(自己証明書を意識する必要も無いみたいです)
ただし、php_openssl の extension を有効化する等、file_get_contents で HTTPS を処理するための環境設定が必要かもしれません。(今回は php.ini で php_openssl の extension を有効化しました)
Basic認証に関しては URL でユーザー名とパスワードを指定する方法もありますが、今回は Authorization ヘッダーで指定する方法をとっています。
- PHP 5.3.8
basic_post_novalidate_certificate.php
<?php $url = $argv[1]; $user = $argv[2]; $pass = $argv[3]; $postData = $argv[4]; $options = array('http' => array( 'method' => 'POST', 'header' => "Authorization: Basic " . base64_encode("$user:$pass") . "\r\n" . "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => $postData )); //POST $res = file_get_contents($url, false, stream_context_create($options)); //結果の出力 echo $res; ?>
C# による Web クライアント
C# では ServicePointManager.ServerCertificateValidationCallback に true を返すコールバックを設定するだけで自己証明書を処理できるようになります。
Basic認証も POST も WebClient クラスを使えば簡単に実装できます。
- .NET Framework 4.0
BasicPostNovalidateCertificate.cs
using System; using System.Net; class BasicPostNovalidateCertificate { public static void Main(string[] args) { var url = args[0]; var user = args[1]; var pass = args[2]; var postData = args[3]; //SSL 証明書を検証しないようにする設定 //(証明書を何でも受け入れるようにする) ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true; using (WebClient wc = new WebClient()) { //Basic認証 wc.Credentials = new NetworkCredential(user, pass); //POST var res = wc.UploadString(url, "POST", postData); //結果の出力 Console.Write(res); } } }
なお、上記を実行すると結果は一応返って来ますがサーバー側でエラー出力(既存の接続はリモートホストに強制的に切断されました)されます。(解決策は不明。サーバーの実行環境が JRuby・Ruby の違いに関わらず発生する)
Java による Web クライアント
Java では SSLContext を何もしない X509TrustManager で初期化し、どんなホスト名でも受け入れる HostnameVerifier を設定します。
- JavaSE 7
BasicPostNovalidateCertificate.java
import java.io.*; import java.net.*; import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.*; public class BasicPostNovalidateCertificate { public static void main(String[] args) throws Exception { URL url = new URL(args[0]); final String user = args[1]; final String pass = args[2]; String postData = args[3]; //Basic認証 Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, pass.toCharArray()); } }); // ホスト名を検証しないようにする設定 //(openConnection する前に設定しておく必要あり) HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String host, SSLSession ses) { return true; } }); HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); con.setDoOutput(true); con.setRequestMethod("POST"); //SSL 証明書を検証しないための設定 SSLContext sslctx = SSLContext.getInstance("SSL"); sslctx.init(null, new X509TrustManager[] { new X509TrustManager() { public void checkClientTrusted(X509Certificate[] arg0, String arg1) { } public void checkServerTrusted(X509Certificate[] arg0, String arg1) { } public X509Certificate[] getAcceptedIssuers() { return null; } } }, new SecureRandom()); con.setSSLSocketFactory(sslctx.getSocketFactory()); //POSTデータの出力 OutputStream os = con.getOutputStream(); PrintStream ps = new PrintStream(os); ps.print(postData); ps.close(); //結果の出力 InputStream is = con.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); int len = 0; byte[] buf = new byte[1024]; while ((len = bis.read(buf, 0, buf.length)) > -1) { System.out.write(buf, 0, len); } bis.close(); } }
なお、Java でも C# と同様にサーバー側でエラー出力(既存の接続はリモートホストに強制的に切断されました)されます。
Groovy による Web クライアント
Groovy は基本的に Java 版の内容をそのまま実装しました。
- Groovy 1.8.2(JavaSE 7)
basic_post_novalidate_certificate.groovy
import java.security.SecureRandom import java.security.cert.X509Certificate import javax.net.ssl.* def url = new URL(args[0]) def user = args[1] def pass = args[2] def postData = args[3] //Basic認証(getPasswordAuthentication() をオーバーライド) Authenticator.default = { new PasswordAuthentication(user, pass.toCharArray()) } as Authenticator // ホスト名を検証しないようにする設定 //(注)openConnection する前に設定しておく必要あり HttpsURLConnection.defaultHostnameVerifier = { host, session -> true } as HostnameVerifier def con = url.openConnection() con.doOutput = true con.requestMethod = "POST" //SSL 証明書を検証しないための設定 def sslctx = SSLContext.getInstance("SSL") //X509TrustManager インターフェースの実装 def tmanager = [ checkClientTrusted: {chain, authType -> }, checkServerTrusted: {chain, authType -> }, getAcceptedIssuers: {null} ] as X509TrustManager sslctx.init(null as KeyManager[], [tmanager] as X509TrustManager[], new SecureRandom()) con.SSLSocketFactory = sslctx.socketFactory //POSTデータの出力 con.outputStream.withWriter { it.print postData } //結果の出力 print con.inputStream.text