プライベート認証局でSSLサーバ証明書, nginxでの TLS設定

前回 Root CA, Issuing CA を作ったので、今回はサーバ証明書を作ってみる。クライアント証明書は次回。

Webサーバ用のSSL証明書を作り、nginx でTLSを有効にする。中間CA (Issuing CA) が挟まっていても動くようにする。

Web上には、認証局 (CA) などをすっ飛ばして, 単にオレオレ証明書を作ってそれでよし、としている解説ページが多い。現実と gap がある。

また、検索で上位にくるところでも, 用語の使い方が混乱していることがある。場合によっては、はっきり誤った使い方をしているサイトもあったりする。

環境: Fedora 22 Linux + nginx 1.8.0

ほかのサイトではどうなっているか

ちゃんとしたサイトがどうなっているか確認する。opensslコマンドで, どの証明書が使われているか、などが見える。

$ openssl s_client -connect www.google.co.jp:443
CONNECTED(00000003)
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com    Webサーバ証明書
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority  これがRoot CA
---
Server certificate
-----BEGIN CERTIFICATE-----
証明書(略)
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority G2
---
No client certificate CA names sent
Server Temp Key: ECDH, prime256v1, 256 bits
---
SSL handshake has read 10723 bytes and written 331 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 117B3D1810B9D8B4FD08509DA970EFB11460B870C68F7786DF94824BC2A3E4DA
    Session-ID-ctx: 
    Master-Key: FF18B2DAF61D699C372DE83478AC2DB66EF975A0AFAE721EF99AAF652AA9F3ADF6ED6AEC9C5EC276F8D2F9AC6E56E53E
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    TLS session ticket lifetime hint: 100800 (seconds)
    TLS session ticket:
    0000 - 9e 以下、略

Webブラウザなどの証明書ストアでサーバ証明書を表示し、いくつかの属性がどうなっているか。

Certificate Basic Constraints:
Critical
Is not a Certificate Authority
Certificate Key Usage:
Not Critical
Signing

別のサイトのSSL証明書で、次のようになっているものもあった;

Critical
Signing
Key Agreement
Extended Key Usage:
Not Critical
TLS Web Server Authentication (1.3.6.1.5.5.7.3.1)
TLS Web Client Authentication (1.3.6.1.5.5.7.3.2)

署名要求を作る

まずは, 認証局 (CA) に対する署名要求 (CSR) を作る。

openssl のための設定ファイルを作る。こんな感じ。自分自身では署名しないので,「x509_extensions = cert_type」はコメントアウト。

server-req.cnf

[ req ]
default_bits = 2048
encrypt_key = yes
distinguished_name = req_dn
#x509_extensions = cert_type
prompt = no

# これを追加
req_extensions = v3_req

[ req_dn ]
# country (2 letter code)
C=JP

# State or Province Name (full name)
ST=FUKUOKA

# Locality Name (eg. city)
#L=Helsinki

# Organization (eg. company)
O=Netsphere Laboratories

# Organizational Unit Name (eg. section)
#OU=IMAP server

# Common Name (*.example.com is also possible)
# "CN=*.fruits" だけではダメ。証明書は作れるが, Webブラウザが「不正なセキュリ
# ティ証明書」などのエラーを出して、Webサーバに接続できない
CN=*.kiwi.fruits

# E-mail contact
#emailAddress=hisashi.horikawa@gmail.com

[ v3_req ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = *.kiwi.fruits
DNS.2 = kiwi.fruits

Webサーバの SSL秘密鍵と署名要求 (CSR) を作る.

(2018.3) /etc/pki/tls/misc/CA ファイルは、もはやない。openssl-perl パッケージの /usr/bin/CA.pl になった。環境変数名も変更。

server$ SSLEAY_CONFIG="-config server-req.cnf" /etc/pki/tls/misc/CA -newreq
  OPENSSL_CONFIG="-config server-req.cnf" /usr/bin/CA.pl -newreq
Generating a 2048 bit RSA private key
......................................................................+++
..............................+++
writing new private key to 'newkey.pem'
Enter PEM pass phrase:               Webサーバの秘密鍵のパスフレーズ
Verifying - Enter PEM pass phrase:
-----
Request is in newreq.pem, private key is in newkey.pem

秘密鍵ファイル newkey.pem と署名要求ファイル newreq.pem ができる。

次のコマンドでCSRを確認すると、Requested Extensions セクション内に X509v3 Subject Alternative Name というフィールドができていて、DNS 制約がある。

$ openssl req -text < newreq.pem

Issuing CAで署名する

続けて、署名要求ファイルを中間CAのディレクトリにコピーしたうえで、中間CAで署名する。

設定ファイル /etc/pki/tls/openssl.cnf をコピーしてきて、修正する。

Requested Extensions で要求された X509v3 Subject Alternative Name をそのまま受け入れて署名する方法があるはずだが、分からなかった。ここでは、設定ファイルで同じ内容を書いている。

[ CA_default ]

# ここを変更
dir = /home/hori/my-ca/intermediate-ca      # Where everything is kept
# このセクションを追加
[ alt_names ]
DNS.1 = *.kiwi2.fruits
DNS.2 = kiwi2.fruits

[ usr_cert ]

# These extensions are added when 'ca' signs a request.

# ここを変更
basicConstraints=critical, CA:FALSE

# ここを変更. SSL サーバ
# deprecated: Netscape Certificate Type
#     client, server, email, objsign, sslCA, emailCA, objCA
nsCertType = server

# ここを変更
keyUsage = critical, digitalSignature, keyAgreement
keyUsage = critical, digitalSignature 

# これを追加
extendedKeyUsage = serverAuth, clientAuth

# これを追加. @xxx はセクション名
# TODO: リクエストどおりに追加する方法が分からない
subjectAltName=@alt_names

RFC 5280 によれば, digitalSignature だけでよい。www.google.com ほかいくつかのサイトはそのようになっている。keyEncipherment も付けているサイトも多いが。

今回は手を抜いて, /etc/pki/CA 以下にディレクトリレイアウトを合わせる。例えば, private/ に中間CAの秘密鍵を置いたりする。

CAスクリプトは, -signCA ではなく -sign のほうを呼び出す。

intermediage-ca$ SSLEAY_CONFIG="-config cert-ssl-req.cnf" /etc/pki/tls/misc/CA -sign
  OPENSSL_CONFIG="-config sign-tls-server-req.cnf" /usr/bin/CA.pl -sign
Using configuration from cert-ssl-req.cnf
Enter pass phrase for /home/hori/my-ca/intermediate-ca/private/cakey.pem:  中間CAの秘密鍵のパスワード
Check that the request matches the signature
Signature ok
Certificate Details:
        Webサーバ証明書の内容が表示される

Certificate is to be certified until Aug  2 05:17:08 2016 GMT (365 days)
Sign the certificate? [y/n]:y

Webサーバの証明書 newcert.pemファイルが生成される。

きちんと中間CAで署名できたか、確認する。Subject, Issuer, X509v3 extensions 辺りに注意。X509v3 Subject Alternative Name もあるか。

$ openssl x509 -text < newcert.pem

ここまでできたら、次は nginx への組み込み。

nginx に Webサーバ SSL証明書を組み込む

Webサーバ SSLの秘密鍵と証明書、それから中間CA (Issuing CA) の証明書を用意します。

Webサーバ証明書と中間CA証明書を一つのファイルにします。

$ cat ssl-server.cert.pem intermediate-ca.cert.pem > cert-chain.cert

また, Webサーバ秘密鍵PEMファイルからパスワードを外します。

$ openssl rsa -in ssl-server.encrypted_private.pem -out ssl-server.private.pem
Enter pass phrase for ssl-server.encrypted_private.pem: サーバ秘密鍵のパスワード
writing RSA key

パスワードで保護された秘密鍵は「-----BEGIN ENCRYPTED PRIVATE KEY-----」, 保護されていないほうは「-----BEGIN RSA PRIVATE KEY-----」で始まります。

(2020.5更新)

Fedora 32 には nginx v1.18.0 パッケージがある。こちらでは, 設定ファイルは /etc/nginx/conf.d/*.conf に置く。以下, /etc/nginx/tls以下に証明書ファイルを置く場合の設定。

TLS 1.0, TLS 1.1 は無効にすること。2020年9月8日に, Internet Explorer 11 でも TLS 1.1 が無効になる。nginx の設定では複数の server block を書くことが通常だが, どれか一つででもこれらが有効になっていると、すべての server について有効になってしまう。http block に書くのがよい。

http {
    # SSLv3 must be eliminated.
    # 2020.9.8 All browsers remove TLSv1.1 support.
    ssl_protocols TLSv1.2 TLSv1.3;

    # 暗号スイートを限定
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    ssl_prefer_server_ciphers off;

    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name www.kiwi.fruits;

        # サーバ証明書 + 中間CA証明書
        ssl_certificate tls/cert-chain.cert;

        # サーバ秘密鍵
        ssl_certificate_key tls/private/ssl-server.private.pem;

        location / {
            略...
        }
    }

参考: Qualys SSL LabsでスコアAがとれるnginxのTLS/SSLの設定方法 - kumaaaaaaa! やや内容が古いが、考え方は変わらず。

Webブラウザからアクセスする

単に https://... で接続すると次のようになる;

SEC_ERROR_UNKNOWN_ISSUER は、発行者が信頼できないエラー。サーバの証明書から辿っていって、ルート認証局の証明書が見つからないので表示される。

クライアントのWebブラウザで証明書ストアを開き、オレオレRoot CAの証明書をインポートする。中間CAの証明書や、Root CAの秘密鍵は不要。

(2024.02) Chrome v121 なら「証明書の管理」→「認証局」タブ→ [インポート] ボタン。"ウェブサイトの識別でこの証明書を信頼します" にチェックを入れる。

証明書ストアに認証局として登録されたことを確認する。

再度、Webサーバにアクセス。今度は成功。