クライアント認証

以前、apacheでhttpsを使うための設定を書いたが、今回はさらにhttpsのクライアント認証を行う方法について書いてみる。
  1. 準備。以前の内容に従って、ca.key, ca.crtができていること。
  2. クライアント用の証明発行要求の作成(クライアントのやること)
    % openssl req -new -keyout client.key -out client.csr
    Generating a 1024 bit RSA private key
    ......++++++
    ..++++++
    writing new private key to 'client.key'
    Enter PEM pass phrase:
    Verifying - Enter PEM pass phrase:
    -----
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:JP
    State or Province Name (full name) [Some-State]:Kanagawa
    Locality Name (eg, city) []:Yokohama
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:Wizard limit
    Organizational Unit Name (eg, section) []:client
    Common Name (eg, YOUR name) []:client.wizard-limit.net
    Email Address []:root@client.wizard-limit.net
    
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:
    
    これで、client.key, client.csr ファイルができる。パスフレーズは、適当に入れた。(後で使うので忘れないように)

  3. サインする。(CAのやること)
    % ./sign.sh client.csr
    CA signing: client.csr -> client.crt:
    Using configuration from ca.config
    Enter pass phrase for ./ca.key:
    Check that the request matches the signature
    Signature ok
    The Subject's Distinguished Name is as follows
    countryName           :PRINTABLE:'JP'
    stateOrProvinceName   :PRINTABLE:'Kanagawa'
    localityName          :PRINTABLE:'Yokohama'
    organizationName      :PRINTABLE:'Wizard limit'
    organizationalUnitName:PRINTABLE:'client'
    commonName            :PRINTABLE:'client.wizard-limit.net'
    emailAddress          :IA5STRING:'root@client.wizard-limit.net'
    Certificate is to be certified until Mar 27 05:31:42 2004 GMT (365 days)
    Sign the certificate? [y/n]:y
    
    
    1 out of 1 certificate requests certified, commit? [y/n]y
    Write out database with 1 new entries
    Data Base Updated
    CA verifying: client.crt <-> CA cert
    client.crt: OK
    
    これで、client.crt ができる。
  4. ブラウザに取り込めるように、crtファイルをPKCS#12形式に変換する。(クライアントの仕事)
    % openssl pkcs12 -export -inkey client.key -in client.crt -certfile ca.crt -name client -out client.p12
    Enter pass phrase for client.key:
    Enter Export Password:
    Verifying - Enter Export Password:
    
    Exportパスワードは、ブラウザにimportするときに必要になる。
  5. apache の設定
    WEBサーバの管理者に必要なファイルは、ca.crt のみ。httpd.conf で SSL の設定が正しく済んでいれば、以下の3行を変更すれば動くはず。(ca.crtを/usr/local/etc/apache2/ca/に置いた場合)
    SSLCACertificatePath /usr/local/etc/apache2/ca
    SSLCACertificateFile /usr/local/etc/apache2/ca/ca.crt
    SSLVerifyClient require
    
    とりあえず、ここまでの設定で apache を起動し、ブラウザに client の方の証明書をインストールしたところ、クライアント認証に成功した。
    ※ もし、特定のパスのみクライアント認証を行いたい場合は、SSLVerifyClientを<Location>の中に置く。

javaからアクセス

おまけに、javaからのhttpsアクセスについて書いてみる。
まず、普通にhttpsのURLにアクセスする方法。
import java.io.*;
import java.net.*;

public class Client {
    public static void main(String args[]) throws Exception {
	//System.setProperty("https.proxyHost", "プロキシーホスト名");
	//System.setProperty("https.proxyPort", "プロキシーポート");
	URL url = new URL(args[0]);
	URLConnection con = url.openConnection();
	con.connect();
	InputStream in = con.getInputStream();
	BufferedReader r = new BufferedReader(new InputStreamReader(in));
	String line;
	while ((line = r.readLine()) != null) {
	    System.out.println(line);
	}
	r.close();
    }
}
上のようなソースで、httpsにアクセスできる(はず)。
うまく行かない場合は、以下を疑ってみる。
  1. $JAVA_HOME/lib/security/cacerts が適切に更新されていない。
  2. httpsのサーバが、cacertsにある認証機関によって認証されていない。
おそらく、個人でやっている自前のサーバなどの場合は、ほとんど2に該当すると思うので、サーバの証明書又はサーバを認証しているCAの証明書をcacertsにインポートしてやる必要がある。
% cd $JAVA_HOME/lib/security
% keytool -import -keystore cacerts -alias サーバ名など -trustcacerts -file 証明書ファイル名
これで、再び上のソースを実行すれば、アクセスできるはず。あ、パスワードはデフォルトではchangeitらしい。

続いて、サーバがクライアント認証をかけていた場合。
  1. クライアントの鍵を作る。
    % keytool -genkey -alias client -keystore mystore
    キーストアのパスワードを入力してください:  password
    姓名を入力してください。
      [Unknown]:  client
    組織単位名を入力してください。
      [Unknown]:  client
    組織名を入力してください。
      [Unknown]:  sosiki
    都市名または地域名を入力してください。
      [Unknown]:  yokohama
    州名または地方名を入力してください。
      [Unknown]:  kanagawa
    この単位に該当する 2 文字の国番号を入力してください。
      [Unknown]:  JP
    CN=client, OU=client, O=sosiki, L=yokohama, ST=kanagawa, C=JP でよろしいですか?
      [no]:  yes
    
     の鍵パスワードを入力してください。
            (キーストアのパスワードと同じ場合は RETURN を押してください):
    
  2. 証明書要求を作る。
    % keytool -certreq -keystore mystore -alias client -file client.csr
    キーストアのパスワードを入力してください:  password
    
    これでできるclient.csrをサーバに送って、sign.shでサインする。
  3. 証明書をインポートする。
    サーバでできるclient.crtは、keytoolではそのまま読めないみたいなので(ひょっとしたらオプションの指定で読めるのかも・・・)、-----BEGIN CERTIFICATE----- から-----END CERTIFICATE-----までの部分を切り出してclient.cerとして保存する。
    % keytool -import -alias client -keystore mystore -trustcacerts -file client.cer
    キーストアのパスワードを入力してください:  password
    
ここまでで、クライアント用のキーとCAによって署名された証明書が入ったキーストア「mystore」が出来上がる。後は、javaを実行するときに、このファイルのパスとパスワードを教えてあげれば良い。
% java -Djavax.net.ssl.keyStore=mystore -Djavax.net.ssl.keyStorePassword=password https://www.wizard-limit.net/

おまけ

ブラウザとjavaクライアントで同じ秘密鍵-証明書を使いたい場合、javaのkeytoolでは秘密鍵のimport/exportができないので、javaでプログラムを書いて keystore から入出力してやれば同じ鍵が使えるようになる。
手順は、http://java-house.jp/ml/archive/j-h-b/051468.htmlからのメールのやり取りが参考になる。
false@wizard-limit.net