CentOS6+NginxでECDHEを有効にしてForward Secrecy取得までのメモ

昨日は鍵共有を甘く見て爆死したためCentOS+NginxのサーバーをWindows環境に対応させてみる。

RPMに慣れていた都合上、主なサーバーにCentOS6を使っている。 CentOS/RHEL6.4では2013年10月時点で、標準のOpenSSLは1.0.0、Nginxは1.0系が導入される。Nginxには公式リポジトリもあるが、openssl-1.0.0を前提にビルドされている。

これらはコミュニティサポートのあるRPMで提供されているものの、NginxでHTTPSサーバーを建てようとすると、TLSはv1.0 まで、対応cipherはDHE-RSA-AES-SHAまでになり、QualsysのSSLテストでは色々と指摘される。

問題点

既存のパッケージで対策不能なのは、TLS1.2対応(兼BEAST対策)とRSA鍵共有。

BEASTは未対策のブラウザがTLSv1.0以前のCBCモードで通信する場合に発生しうる。一時的な対策としてストリーム暗号のRC4が使われているが、TLSv1.2ではCBCモードの問題が解消されているためTLSv1.2で導入された暗号モードを使えば問題は無い。しかし、TLSv1.2はopenssl-1.0.0には入っていない。

将来解読される可能性がある(RSA)鍵共有を使わない、というForward Secrecyは各所で既に行われている。RSA鍵共有に替わるものとして、多くのブラウザはDH鍵共有・ECDH鍵共有が実装されている。 CentOSのopensslでも有効なDH鍵共有を利用できればいいのだが、クライアント側、例えばWindows+IE環境ではDHE-RSAが無いためにRSA鍵共有が優先され、Forward Secrecyを保証できないことをご指摘頂いた。(Win7+IEでブラウザのCipherテストみるとDHE-DSSは実装されているのにDHE-RSAが無い)

Windows+IEに対応するためには、ECDH鍵共有を有効にする必要がある。Googleなど多くのサービスは既にECDHE-RSAを有効にしている。

ビルド手順

TLSv1.2と、多くのブラウザでRSA鍵共有以外を使おうとすると、OpenSSLとNginxをリビルドする必要があった。

システムのopensslをいじるのでVMなどのビルド用サンドボックス環境で要検証。

まず、1.0.1のopenssl-develをビルド環境にインストールする。 CentOS6でのopenssl-1.0.1e のビルド手順は http://www.ptudor.net/linux/openssl/ がとても詳しいのでそのまま。 生成されたRPMをインストールしてopenssl version が1.0.1eになり、openssl cipher -v 'HIGH' でKx=ECDHが含まれるかチェックしておく。

Nginxはmainline 1.5.6のSRPMをリビルド。自動インストールするのであれば、nginx.specファイルでRequires: openssl >= 1.0.1e のバージョンを指定しておく。 出来たバイナリはopenssl-1.0.1eのcipherに対応しているはず。 例えば

  ssl_protocol TLSv1.2;
  ssl_ecdh_curve secp384r1;
  ssl_ciphers EECDH+aRSA+AESGCM;

が通るか確認。

実際のciphers指定

次のように明示しておく。

    ssl on;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    # RC4無し
    ssl_ciphers EECDH+HIGH:EDH+HIGH:HIGH:+3DES:!RC4:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5;
    # RC4有り
    # ssl_ciphers EECDH+AESGCM:EECDH+SHA384:EECDH+SHA256:EECDH+RC4:EECDH+HIGH:EDH+AESGCM:EDH+SHA384:EDH+SHA256:EDH+RC4:EDH+HIGH:AESGCM:SHA384:SHA256:RC4:HIGH:+3DES:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5;
    ssl_ecdh_curve prime256v1;
    ssl_dhparam /path/to/dhparam2048.pem;
    ssl_certificate /path/to/server.crt
    ssl_certificate_key /path/to/private.key
  • ECDHE>DHE>RSAを明示する。OpenSSLのデフォルトのソートは、ソート順がEnc→KxなのでKxが交互に並んでしまう。
  • 将来証明書がRSAからECDSAに切り替わった時のために、grep Au=RSAgrep Au=ECDSAで順序が変わらように並べる。
  • GCM>SHA-2>RC4SHA-1なのは、SHA-2未対応のクライアントはTLSv1.0である可能性があり、BEAST対策なされていないかもしれないから。
  • MD5はSSL3.0ならSHA-1必須なので抜く。
  • RC4をそのまま抜くとWinXP+IEで接続できなくなるので、cipherの最後に+3DESを追加する。
  • EC関数のsecp384r1は互換性から減点される模様
  • ECDHは256bit以上、DHは2048bit以上が推奨されている。Nginxのデフォルトは1.5.6でもDH1024bitのままだった。
  • !aNULL以降は表示されるcipher削減のため。利用するクライアントは存在しないかそれ自体脆弱と考えていい
  • 性能上の問題は無視している。

これでほぼ全てのブラウザで対応できたはず。

Forward Secrecy   Yes (with most browsers)   ROBUST (more info)

レポジトリ

他人のビルドしたopensslを試す無謀な方は

http://yumrepo.usb0.net/GPG-KEY
pub   4096R/848C083D 2013-11-17
      指紋 = D11E 88FF 3FC0 4339 EE1D  BBA4 4F1E A7EE 848C 083D
uid                  causeless (myrepo sign key) <causeless@gmail.com>
[myrepo]
gpgcheck = 1
enabled = 0
name = My Repo - $basearch
baseurl = http://yumrepo.usb0.net/RPMS/$basearch/
[myrepo-noarch]
gpgcheck = 1
enabled = 0
name = My Repo - noarch
baseurl = http://yumrepo.usb0.net/RPMS/noarch/
[myrepo-source]
gpgcheck = 1
enabled = 0
name = My Repo - noarch
baseurl = http://yumrepo.usb0.net/RPMS/source/