昨日は鍵共有を甘く見て爆死したため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=RSA、grep Au=ECDSAで順序が変わらように並べる。
- GCM>SHA-2>RC4>SHA-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/