hamakou108 blog

自己認証局による証明書を用いた HTTPS 通信を Docker 環境下で試してみる

October 17, 2019

サーバ証明書・クライアント証明書を自己認証局を用いて作成し、 Docker コンテナ上で立ち上げた Apache でそれらの証明書を利用した HTTPS 通信を試す。

環境は以下の通り。

  • MacOS 10.14.3
  • Docker Desktop Community 2.0.0.3
  • Docker Compose 1.23.2

Git リポジトリの作成

今回の検証用に Git リポジトリを作成した。

README に記載したように docker-compose up -d で CA や Web サーバなどの用途のコンテナが立ち上がる。 なお後述の手順で作成した成果物は全てリポジトリに含まれる。

自己認証局(オレオレ認証局)を用いた証明書の作成

自身で CA を立て、その CA の秘密鍵で署名する。 因みにこの方法で作成した証明書は自己署名証明書とは呼ばないらしい(単に公に信頼されていない CA によって署名されたデジタル証明書ということになる)。

証明書の作成手順は基本的に以下を参考にした。

ちなみに認証局を構築せずに openssl コマンドを直で実行して自己署名証明書(オレオレ証明書)を作成する方法に関しては以下が参考になる。

認証局 (CA) を構築

ホストに配置した修正済みの CA スクリプトと openssl.conf をコンテナの /etc/pki/CA にコピーするように Dockerfile に記述した。 証明書のシリアル連番を記載したファイル serial も必要となるため、併せて Dockerfile に記述してある。 serial に記載した番号は /etc/pki/CA/newcerts に作成される CA の証明書ファイルの名前として使用される。

WORKDIR /etc/pki/CA
COPY CA .
COPY openssl.cnf .
RUN echo 00 > ./serial

Docker Compose で CA 用のコンテナを立ち上げたら bash などを通して CA スクリプトを実行し、認証局の証明書を作成する。

$ ./CA -newca

CA certificate filename (or enter to create) は特に指定せずに Enter を押し、その後 passphrase を入力すると秘密鍵が作成される。 証明書の作成では基本的に Common Name を各コンテナに割り振ったドメイン名と一致させる(この場合は my-ca )。 秘密鍵と証明書の作成後は以下のような状態になる。

[root@f9a7209051fa CA]# ls -R
.:
CA  cacert.pem  careq.pem  certs  crl  index.txt  index.txt.attr  index.txt.old  newcerts  openssl.cnf  private  serial  serial.old

./certs:

./crl:

./newcerts:
00.pem

./private:
cakey.pem

サーバ証明書の発行

サーバ用のコンテナで秘密鍵と CSR を作成する。 CA 用のコンテナで CA スクリプトを実行して作成することもできるが、各コンテナの役割を意識してこの手段を取っている。

$ cd /usr/local/apache2/conf/ssl/
$ openssl genrsa -aes128 -out server-key.pem 2048
$ openssl req -sha256 -new -key server-key.pem -out newreq.pem

作成した CSR を CA 用のコンテナに転送し、それを利用して証明書を作成する。

$ ls newreq.pem
newreq.pem
$ ./CA -sign
この証明書の作成時に `unable to load CA private key` というエラーが発生するケースがあった。 原因は不明だが、認証局を再構築したら直った(最初の構築時にパスフレーズの再入力が生じたのが良くなかった...?)。
[root@f9a7209051fa CA]# ./CA -sign
Using configuration from /etc/pki/CA/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
unable to load CA private key
139963134875536:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:592:
139963134875536:error:23077074:PKCS12 routines:PKCS12_pbe_crypt:pkcs12 cipherfinal error:p12_decr.c:108:
139963134875536:error:2306A075:PKCS12 routines:PKCS12_item_decrypt_d2i:pkcs12 pbe crypt error:p12_decr.c:139:
139963134875536:error:0907B00D:PEM routines:PEM_READ_BIO_PRIVATEKEY:ASN1 lib:pem_pkey.c:141:
cat: newcert.pem: No such file or directory

作成した証明書はサーバ用のコンテナに転送する。 newreq.pemnewcert.pem は次回以降に証明書を作成するときに邪魔なので削除して良い。

クライアント証明書

そもそもクライアント証明書に関して無知だったので、少し勉強した。

サーバ証明書の場合と同様にクライアント用のコンテナで秘密鍵と CSR を作成し、それを元に CA 用のコンテナで証明書を作成する。 curl や Apache (プロキシサーバ用途など)から利用する場合は PKCS #12 形式に変換する必要はない。

Apache を用いた SSL/TLS 通信の検証

サーバ証明書による認証

サーバ証明書と秘密鍵をサーバ用のコンテナに配備する。 このとき、秘密鍵は復号化し、証明書はメタ情報を取り除いておく。

$ cd /usr/local/apache2/conf/ssl
$ openssl rsa -in server-key.pem -out server-key-dec.pem
$ openssl x509 -in server-cert.pem -out server-cert-dec.pem

Apache では以下の設定が必要。

SSLCertificateFile /usr/local/apache2/conf/ssl/server-cert-dec.pem
SSLCertificateKeyFile /usr/local/apache2/conf/ssl/server-key-dec.pem

クライアント用のコンテナには(サーバ証明書に署名した)認証局の証明書を配備し、以下コマンドをホストで実行して動作確認する。

$ docker exec -it docker-httpd-proxy_client_1 curl https://my-server --cacert ./ssl/cacert.pem

クライアント証明書による認証

サーバの秘密鍵と証明書に加え、(クライアント証明書に署名した)認証局の証明書をサーバ用のコンテナに配備する。 Apache では以下の設定が必要。

SSLCACertificateFile /etc/pki/CA/cacert.pem
SSLVerifyClient require
SSLVerifyDepth 1

クライアント用のコンテナにはクライアント証明書と秘密鍵を配備し、以下コマンドをホストで実行して動作確認する。

$ docker exec -it docker-httpd-proxy_client_1 curl https://my-server --cert ./ssl/client-cert-dec.pem --key ./ssl/client-key-dec.pem --cacert ./ssl/cacert.pem