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

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

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


* 構成
<table><tbody>
  <tr>
    <th> OS</th>
    <td> Fedora 23</td>
  </tr>
  <tr>
    <th> OpenSSL</th>
    <td> 1.0.2f</td>
  </tr>
  <tr>
    <th> Apache</th>
    <td> 2.4.18</td>
  </tr>
</tbody></table>

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

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


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


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


```text:使える楕円曲線のリスト
# 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 を作成してみます。


```text: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 に対して署名を行ってもらい、証明書を作成します。


```text: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 コマンドの実行が完了すると、カレントディレクトリに以下のファイルが作成されます。


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


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


```text:ファイルの移動
# 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` によると...


```text: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`, `RC4`と`3DES` が使われている暗号化スイートを除いていきます。


```text
# 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` に変えておきます。


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


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


```text:除外リストの追加
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 を除去するようにしてください。
フィルタの定義例としては、例えば次のようになります。


```text:除外リストの追加
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 の適切な場所に次のように設定してください。


```text: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 させます。


```text:Apachereload
# systemctl reload httpd
```



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


```text: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` なものについては未検証。

# 参考 
- Eliptic Curve Cryptography (ECC) Support
    - https://community.letsencrypt.org/t/elliptic-curve-cryptography-ecc-support/34/159

- ECDSA対応CSRを生成して、Let's Encryptをつかう場合
    - https://http2.try-and-test.net/letsencrypt_ec_csr.html

- ECDSA testing on staging
    - https://community.letsencrypt.org/t/ecdsa-testing-on-staging/8809

- Enabling Perfect Forward Secrecy
    - https://www.digicert.com/ssl-support/ssl-enabling-perfect-forward-secrecy.htm

- Wikipedia - Transport Layer Security
    - https://ja.wikipedia.org/wiki/Transport_Layer_Security