Apache httpdで自己署名CAを使ってhttpsを行う

【環境】

  • CentOS 5.5 (2.6.18-194.3.1.el5.028stab069.5 #1 SMP Tue May 18 17:48:31 MSD 2010 i686 i686 i386 GNU/Linux)
  • Apache httpd 2.2.3
  • OpenSSL 0.9.8e-fips-rhel5 01 Jul 2008

【目的】

  • 自前サーバ環境でSSLを試す。

【手順】

(1) 関連パッケージのインストール

 
SSL関連のrpmが入っていること

# rpm -qa | grep ssl
openssl-0.9.8e-12.el5_5.7
mod_ssl-2.2.3-43.el5.centos.3

(2) 自己署名CAの構築

CA(認証機関)というのは別に「CAサーバー」があるわけじゃない。
「俺が誰だか証明してくれ!」という対象に対して、「私はXXをXXと認めます」とハンコを押す。それがCA。

このあたりはPKI(公開鍵認証)の概念を理解できないと「何でサーバじゃないのに対象をそのものだと証明できるんだよ!」と思うだろうが、例えば身分証明としての運転免許証は、それを発行している公安委員会を信用することによって、その公安委員会が認めた個人を信用する。
実際に免許証を一緒に住民票とかパスポートだとかを取り出してその場で一々詳細な証明をすることを省略している。
公開鍵認証というのは信用のチェーンで成り立つ。

具体的には、CAとは「CA秘密鍵」と「CA証明書」で成り立つ。
CA秘密鍵はCA自身しか知らない、決まった長さを持つランダムな文字列のこと。これはCAの上位のCAですら知らなくて良い。CA自身だけが知っていれば良い。(逆にこれが漏れると大変なことになる。)

CA証明書とは、「このCAは確かにXXという属性を持つCAです」と他のCAが署名をしたファイルのことだ。
署名には他のCAの秘密鍵が使用され、署名する前の内容に改変を加えると改ざんすることがわかるようになっている。
なので、他のCAの秘密鍵が漏洩しない限り、または他のCAが定めた証明期間が経過しない限り署名は有効である。
(署名が無効になったことを表す仕組みもあるのだが、今回は省略。)

今回はCentOSのOpenSSLに付属のCA作成スクリプトを使用する。
(作業手順はhttp://itochif.com/contents/Linux/centos5/other/ca_00100.htmlを参考にさせていただきました。)

まずはCAを構築するためのパラメータを設定する。

# cd /etc/pki/tls

# cp openssl.cnf openssl.cnf.bak
#
vi openssl.cnf

# diff openssl.cnf.bak openssl.cnf
76c76
< default_days  = 365                   # how long to certify for
---
> default_days  = 3650                  # how long to certify for ← 期間が短いとメンドイのでCAの寿命を10年にしている
78c78
< default_md    = sha1                  # which md to use.
---
> default_md    = sha512                # which md to use. ← どうせナンチャッテCAなのでハッシュ方式ぐらい贅沢にしてみる
109,110c109,110
< default_bits          = 1024
< default_md            = sha1
---
> default_bits          = 2048 ← (同上)
> default_md            = sha512 ← (同上)
136c136
< countryName_default           = GB
---
> countryName_default           = JP ← ここから下はCAの属性を何度も入力しなくても良いようにしたもの
141c141
< stateOrProvinceName_default   = Berkshire
---
> stateOrProvinceName_default   = Ibaraki
144c144
< localityName_default          = Newbury
---
> localityName_default          = Kitasouma-gun
147c147
< 0.organizationName_default    = My Company Ltd
---
> 0.organizationName_default    = Himajin2001 co.ltd.
178c178
< basicConstraints=CA:FALSE
---
> basicConstraints=CA:true ← このCAはCAとして振舞えるようにする。
226c226
< basicConstraints = CA:FALSE
---
> basicConstraints = CA:true
280c280
< basicConstraints=CA:FALSE
---
> basicConstraints=CA:true

次に、CA秘密鍵の作成とCA証明書の作成をスクリプトで一気に行う。
詳しい作業内容はこの後のサーバ証明書の作成で解説する。
どうせCA証明書の構築なんてそうそうするものじゃないし~。

# cd /etc/pki/tls/misc/
# ./CA -newca
mkdir: ディレクトリ `../../CA' を作成できません: ファイルが存在します
mkdir: ディレクトリ `../../CA/private' を作成できません: ファイルが存在します
CA certificate filename (or enter to create) ← そのまま[enter]

Making CA certificate ...
Generating a 2048 bit RSA private key
...+++
............................................+++ ← CA秘密鍵をランダムに作成中
writing new private key to '../../CA/private/./cakey.pem'
Enter PEM pass phrase: ← CA秘密鍵を使うためのパスワードを設定するために入力して[enter]
Verifying - Enter PEM pass phrase: ← 確認のためにもう一度同じパスワードを入力
-----
You are about to be asked to enter information that will be incorporated ← CAの署名要求を作成する
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) [JP]: ← そのまま[enter]
State or Province Name (full name) [Ibaraki]: ← そのまま[enter]
Locality Name (eg, city) [Kitasouma-gun]: ← そのまま[enter]
Organization Name (eg, company) [Himajin2001 co.ltd.]: ← そのまま[enter]
Organizational Unit Name (eg, section) []: ← そのまま[enter]
Common Name (eg, your name or your server's hostname) []:hoge1.himajin2001.com ← CAを作るサーバのFDQNを入力して[enter]
Email Address []: ← そのまま[enter]

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: ← そのまま[enter]
An optional company name []: ← そのまま[enter]
Using configuration from /etc/pki/tls/openssl.cnf ← CAの署名要求にCA自身が署名する(自己署名)
Enter pass phrase for ../../CA/private/./cakey.pem: ← 先ほど設定したCA秘密鍵パスワードを入れて[enter]
Check that the request matches the signature
Signature ok
Certificate Details: ← 作成された証明書の概要が表示される
        Serial Number: 0 (0x0)
        Validity
            Not Before: Feb  1 02:12:05 2011 GMT
            Not After : Jan 31 02:12:05 2014 GMT
        Subject:
            countryName               = JP
            stateOrProvinceName       = Ibaraki
            organizationName          = Himajin2001 co.ltd.
            commonName                = vps1.himajin2001.com
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:TRUE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                DF:76:B6:87:C4:E2:07:≪セキュリティー的に中略≫
            X509v3 Authority Key Identifier:
                keyid:DF:76:B6:87:C4:≪セキュリティー的に中略≫

Certificate is to be certified until Jan 31 02:12:05 2014 GMT (1095 days)

Write out database with 1 new entries
Data Base Updated

CA秘密鍵と自己署名証明書ができたはずなので確認。

# ls -la /etc/pki/CA/private/cakey.pem
-rw-r--r-- 1 root root 1751  2月  1 11:11 /etc/pki/CA/private/cakey.pem
# ls -la /etc/pki/CA/cacert.pem
-rw-r--r-- 1 root root 4465  2月  1 11:12 /etc/pki/CA/cacert.pem

CA秘密鍵はroot以外読み書き禁止に。

# chmod 400 /etc/pki/CA/private/cakey.pem

(3) サーバ証明書の作成

PKIにおける証明書の作成手順の流れは以下のような感じ。

  1. 証明したいものの秘密鍵を作る
  2. 秘密鍵を元に署名要求を作る
  3. 署名要求をCAに渡して署名してもらう → 証明書

後は、相手がCAを信用する限りにおいて証明書は有効になる。

サーバ秘密鍵の作成

無駄に豪華な鍵を作ってみる。
当然バイト数が大きかったり、ハッシュに使う長さが長いほうが改ざんされにくい鍵になる。
ハッシュ方式の宿命として、異なる対象のハッシュが同じ値をとることは確率的に有り得るので。

# cd /etc/httpd/conf
# openssl genrsa -aes256 -out /etc/httpd/conf/server.key 2048
Generating RSA private key, 2048 bit long modulus
...+++
.....................+++ ← サーバの秘密鍵を作成中
e is 65537 (0x10001)
Enter pass phrase for /etc/httpd/conf/server.key: ← サーバ秘密鍵用のパスワードを入力して[enter]
Verifying - Enter pass phrase for /etc/httpd/conf/server.key: ← もう一度サーバ秘密鍵用のパスワードを入力して[enter]

サーバ秘密鍵ファイルができたことを確認。

# ls -la /etc/httpd/conf/server.key
-rw-r--r-- 1 root root 1766  2月  1 11:31 /etc/httpd/conf/server.key

サーバ署名要求の作成

署名要求を作成する時は、証明される対象がどのようなものなのかを表す属性をセットしておく必要がある。
属性は署名者の要求や証明書の使い道などによって必要とされる属性が異なる。

# openssl req -new -key /etc/httpd/conf/server.key -out /etc/httpd/conf/server.csr
Enter pass phrase for /etc/httpd/conf/server.key: ← サーバ秘密鍵用のパスワードを入力して[enter]
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) [JP]: ← そのまま[enter]
State or Province Name (full name) [Ibaraki]: ← そのまま[enter]
Locality Name (eg, city) [Kitasouma-gun]: ← そのまま[enter]
Organization Name (eg, company) [Himajin2001 co.ltd.]: ← そのまま[enter]
Organizational Unit Name (eg, section) []: ← そのまま[enter]
Common Name (eg, your name or your server's hostname) []:www.himajin2001.com ← WebサーバのFDQNを入力して[enter]
Email Address []:
 ← そのまま[enter]

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: ← そのまま[enter]
An optional company name []: ← そのまま[enter]

署名要求ファイルができているので確認する。

# ls -la /etc/httpd/conf/server.csr
-rw-r--r-- 1 root root 1021  2月  1 11:34 /etc/httpd/conf/server.csr

サーバ署名要求に自己署名CA局で署名する

先ほど作ったナンチャッテCAにサーバ署名要求ファイルに署名してもらう。

# cd /etc/pki/tls/misc
# openssl ca -out /etc/pki/CA/certs/apache_ceart.pem -infiles /etc/httpd/conf/server.csr
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for ../../CA/private/cakey.pem: ← 自己署名CA局の秘密鍵のパスワードを入力して[enter]
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Feb  1 02:43:20 2011 GMT
            Not After : Jan 29 02:43:20 2021 GMT
        Subject:
            countryName               = JP
            stateOrProvinceName       = Ibaraki
            organizationName          = Himajin2001 co.ltd.
            commonName                = www.himajin2001.com
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:TRUE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                26:95:14:B2:5E:C5:≪セキュリティー的に中略≫
            X509v3 Authority Key Identifier:
                keyid:DF:76:B6:87:≪セキュリティー的に中略≫

Certificate is to be certified until Jan 29 02:43:20 2021 GMT (3650 days) ← 署名する前にこれから署名する対象が表示される
Sign the certificate? [y/n]:y ← 「y」を入力


1 out of 1 certificate requests certified, commit? [y/n]y ←「y」を入力
Write out database with 1 new entries
Data Base Updated

署名済みのサーバ証明書は/etc/pki/CA/certs/apache_ceart.pemなので、これを所定の場所にコピーする。
(「署名する」というと署名要求ファイルに直接更新がかかりそうなイメージがあるが、実際は別のファイルが作られる。)

# cp /etc/pki/CA/certs/apache_ceart.pem /etc/httpd/conf/server.crt

後はhttpd.conf関連の設定をすればOK。

SSLCertificateFile /etc/httpd/conf/server.crt ← 署名済みのサーバ証明書
SSLCertificateKeyFile /etc/httpd/conf/server.key ← サーバ秘密鍵

また、ブラウザにいちいち自己署名だと怒られないようにインポートさせる為のDER形式のCA証明書を作成する。
既存のCA証明書の形式を変更する。
証明内容は変えないで「証明書を別の封筒に入れる」程度だと思ってもらって間違いは無い。

# openssl x509 -inform pem -in /etc/pki/CA/cacert.pem -outform der -out /etc/pki/CA/himajin2001_ca.der

これで/etc/pki/CA/himajin2001_ca.derにブラウザでインポートできるCA証明書ができるので、ブラウザでアクセスできる所に置く。
サーバにSSLでアクセスする人はこのhimajin2001_ca.derをブラウザ経由でダウンロードしてインポートする。
それでも「自己署名のCAが信用した証明書なんて!」とIEはグダグダ言うけど気にしない。

サーバ秘密鍵のパスフレーズを消去する

apacheの起動時に一々秘密鍵のパスワードを聞いてきてウザいのでパスフレーズを消去したファイルを作る。

# openssl rsa -in server.key -out server.key_nopass
Enter pass phrase for server.key: ← サーバ秘密鍵のパスワードを入力する。
writing RSA key

# ls -la server.key_nopass

できていることを確認する。

httpd.confのサーバ秘密鍵のファイル名設定を変える。

SSLCertificateKeyFile /etc/httpd/conf/server.key_nopass

(4) httpdのSSL設定

基本的には/etc/httpd/conf.d/ssl.confとか見るとわかりやすくコメントがついてますが…。(英語だども。)
最低限こんなものが書いてあればOKっぽい。

# httpdのmod_sslモジュールの読み込み
LoadModule ssl_module modules/mod_ssl.so

# port 443 = httpsをListenさせる
Listen 443

# これは無くても動くような気はするけど、
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl    .crl

# SSLでパスフレーズを聞いてくる時のダイアログのUI形式(今回は未使用)
SSLPassPhraseDialog  builtin

# SSLのセッションのキャッシュ設定
SSLSessionCache         shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout  300
SSLMutex default

# SSLで使う乱数のタネ
SSLRandomSeed startup file:/dev/urandom  256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin

# SSLでサービスを提供するバーチャルホスト。ポートは先ほど指定した443。
<VirtualHost 123.234.111.222:443>
   
    # SSLで通信を暗号化する事を宣言
    SSLEngine on
   
    # 弱いSSLv2は使わない
    SSLProtocol all -SSLv2
    
    # 弱いハッシュ方式を使わない
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
   
    # 先ほどのサーバ証明書とサーバ秘密鍵
    SSLCertificateFile /etc/httpd/conf/server.crt
    SSLCertificateKeyFile /etc/httpd/conf/server.key_nopass
   
    # CGI/SSIがある場合は、SSL独自の環境変数をセットするようにする
    <Files ~ "\.(cgi|php|pl)$">
        SSLOptions +StdEnvVars
    </Files>
   
                    ≪中略≫
   
</VirtualHost>

これでhttpdを再起動してみて、httpsでアクセスできることを確認する。

なお、httpdの再起動が失敗する時、コンソールから見ていると何やらパスワードを聞いてくる時がある。
その場合はサーバ秘密鍵にパスフレーズがかかったままになっていることが考えられるので前述の鍵外しを実行しているか、鍵外し後のファイルへのパスが設定ファイルに書かれているかを確認してほしい。