Let's Encrypt の証明書をECDSA(ECC: 楕円曲線暗号)で作成する


ECDSA で証明書を作成する

特に何も指定しない状態ではRSA 2048 bit 長の公開鍵ペアで証明書が作成されます。
そこを、ECDSA で作成するようにしてみます。
ECDSA で証明書を作成することで、RSA よりも短い鍵長でより高い暗号強度が得られると言われています。

構成

今回検証する環境は次のとおりです。

  • 構成
    OS Fedora 23
    OpenSSL 1.0.2f
    Apache 2.4.18

ECDSA で公開鍵ペア(CSR) を作成する

openssl コマンドを使用してECDSA なCSR(証明書要求) を作成します。
--csr フラグが追加され、こちら側で作成したCSR を指定できるようになりました。
そこに今回作成したECDSA なCSR を指定します。

それでは、ECDSA なCSR を作成してみましょう。
今回は/etc/letsencrypt/self ディレクトリを独自に作成して、そこにECDSA な秘密鍵と証明書を作成していきます。

格納用ディレクトリの作成
# mkdir -p /etc/letsencrypt/self/foo.example.com
# cd /etc/letsencrypt/self/foo.example.com

openssl コマンドで使える楕円曲線を確認してみます。

使える楕円曲線のリスト
# openssl version
OpenSSL 1.0.2f-fips  28 Jan 2016
# openssl ecparam -list_curves
  secp256k1 : SECG curve over a 256 bit prime field
  secp384r1 : NIST/SECG curve over a 384 bit prime field
  secp521r1 : NIST/SECG curve over a 521 bit prime field
  prime256v1: X9.62/SECG curve over a 256 bit prime field

今回は比較的、証明書発行機関でも使われているprime256v1 でCSR を作成してみます。

CSRを作成する
# openssl ecparam -out private_prime256v1.key -name prime256v1 -genkey
# openssl req -new -sha256 -key private_prime256v1.key -subj "/CN=foo.example.com" -reqexts SAN -outform der -out csr_prime256v1.der -config <(
cat <<-EOF
[req]
distinguished_name = dn
[dn]
[SAN]
subjectAltName = DNS:foo.example.com
EOF
)

CSR を作成したら、CSR に対して署名を行ってもらい、証明書を作成します。

letsencryptコマンドで証明書を作成する
# cd /root/letsencrypt
# ./letsencrypt-auto certonly --webroot --renew-by-default --agree-tos -w /var/www/html --csr /etc/letsencrypt/self/foo.example.com/csr_prime256v1.der
Checking for new version...
Requesting root privileges to run letsencrypt...
   /root/.local/share/letsencrypt/bin/letsencrypt --no-self-upgrade certonly --webroot --renew-by-default --agree-tos -w /var/www/html --csr /etc/letsencrypt/self/foo.example.com/csr_prime256v1.der

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /root/letsencrypt/0001_chain.pem. Your cert will expire on
   YYYY-MM-DD. To obtain a new version of the certificate in the
   future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

letsencrypt コマンドの実行が完了すると、カレントディレクトリに以下のファイルが作成されます。

作成されるファイル
0000_cert.pem           # サーバ証明書
0000_chain.pem          # 中間証明書
0001_chain.pem          # サーバ証明書と中間証明書の連結ファイル

これらのファイルを/etc/letsencrypt/self/foo.example.com ディレクトリに移動し、それぞれファイル名をcert.pem, chain.pem, fullchain.pem というファイル名に変更します。

ファイルの移動
# mv 0000_cert.pem /etc/letsencrypt/self/foo.example.com/cert.pem
# mv 0000_chain.pem /etc/letsencrypt/self/foo.example.com/chain.pem
# mv 0001_chain.pem /etc/letsencrypt/self/foo.example.com/fullchain.pem

以上で証明書の取得は完了です。

暗号化スイートの決定

今回作成した証明書で使用できる暗号化スイートとしては、現在使用しているopenssl 1.0.2f によると...

opensslciphers
# openssl ciphers -v 'ECDSA'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA256
ECDHE-ECDSA-AES128-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA1
ECDHE-ECDSA-RC4-SHA     SSLv3 Kx=ECDH     Au=ECDSA Enc=RC4(128)  Mac=SHA1
ECDHE-ECDSA-DES-CBC3-SHA SSLv3 Kx=ECDH     Au=ECDSA Enc=3DES(168) Mac=SHA1
ECDHE-ECDSA-NULL-SHA    SSLv3 Kx=ECDH     Au=ECDSA Enc=None      Mac=SHA1

のようです。
ここではさらにデータの暗号化方式としてNone, RC43DES が使われている暗号化スイートを除いていきます。

# openssl ciphers -v 'ECDSA !eNULL !3DES !RC4'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA256
ECDHE-ECDSA-AES128-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA1

上記に表示されているものを今回採用することにします。
現段階ではECDHE-ECDSA な鍵交換 + 認証 しかないので、フィルタの書き方をECDSA からECDHE+ECDSA に変えておきます。

ECDHE+ECDSA追加
openssl ciphers -v 'ECDHE+ECDSA !eNULL !3DES !RC4'

また、保険の意味も込めて除外する暗号化スイートのフィルタをもっと追加しておきます。
結果以下のようなフィルタ定義を採用することとします。

除外リストの追加
openssl ciphers -v 'ECDHE+ECDSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4'

今回、Apache を使って設定をしていきますが、このフィルタ定義はnginx でも使えます。

ひとりごと1: RSA な証明書が対応している暗号化スイートについて

RSA な証明書が対応している暗号化スイートを確認する場合はopenssl ciphers -v 'aRSA' を実行してみてください。
こちらのほうが、だいぶ多くの暗号化スイートの選択肢があることがわかるはずです。
そのため、古いクライアントがあなたのWeb サイトにアクセスする可能性がある場合、そのクライアントはECDSA な暗号化スイートに対応しいない可能性もある点に注意してください。

ひとりごと2:

暗号化スイートとして、より強固に、TLS1.2 系のみを使用したい場合は、メッセージ認証コード(Mac) としてSHA1 を除去するようにしてください。
フィルタの定義例としては、例えば次のようになります。

除外リストの追加
openssl ciphers -v 'ECDHE+ECDSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4 !SHA1'

ひとりごと3: Enc=None な暗号化スイート

本記事の調べごとをしていて知ったことですが、通信内容を暗号化しない通信方式も立派なSSL/TLS 通信なんですね。しかもTLS1.2 対応だしTLS1.3 の草稿にも含まれています!
「ログ見ればちゃんとTLS1.2 でやっているし安心安心〜(´ー)y-~~」とか余裕こいていると、痛い目見そうですね…。
Enc=None` な暗号化スイートは(あまり用途は思いつきませんが)、通信の暗号化は行わないが接続先の認証はしっかり行うようなケースで使用されるようです。

Web サーバへの設定

細かい手順については割愛しますが、例えばApache を使用している場合、httpd.conf の適切な場所に次のように設定してください。

SSL/TLS周りの設定例
<VirtualHost *:443>
    ServerName foo.example.com

    ......
    SSLEngine on
    SSLProtocol all -SSLv2 -SSLv3
    SSLHonorCipherOrder on
    SSLCipherSuite "ECDHE+ECDSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"

    ## あなたのサイトが常時SSL (http -> https にリダイレクトする)な設定になっている場合、
    ## 任意でHSTS (HTTP Strict Transport Security)の設定も追加しておいてください。
    # Header set Strict-Transport-Security "max-age=315360000; includeSubDomains"

    SSLCertificateFile /etc/letsencrypt/self/foo.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/self/foo.example.com/private_prime256v1.key

    ......
</VirtualHost>

設定ファイルを書き換えたら、Apache をreload させます。

Apachereload
# systemctl reload httpd

確認

Web サーバに証明書を設定したら、openssl コマンドで確認してみましょう。

openssl
# openssl s_client -servername foo.example.com -connect foo.example.com:443 < <(echo QUIT)
......
New, TLSv1/SSLv3, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Server public key is 256 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-ECDSA-AES256-GCM-SHA384
......
    Verify return code: 0 (ok)
......

上記の出力結果を確認するとECDHE-ECDSA-AES256-GCM-SHA384 な暗号化スイートが使われて、署名検証にも成功していることがわかります。
上記のような結果になれば成功です。

その他

Letsencrypt 自体がまだ開発段階にあるので、おそらくここで実施した手順は今後、まだまだ変わっていくと思います。
今回、prime256v1 で公開鍵ペアを作成しましたが、secp256k1, secp384r1, secp521r1 なものについては未検証。

参考