鱒身(Masu_mi)のブログ

知った事をメモする場所。

TLSを設定した

HTTPS対応を行った。ついでにHTTP/2にも対応しようと試みた。 結果としてはis-http2 は通過するがh2spec のテストは大半が失敗したため有効にしなかった。

tl;dr;

TLSの1次情報はRFC5246で関連RFCはいっぱいあった

TLSはtlsワーキンググループで検討されている。 IETFのワーキンググループ は他にもたくさんあっても公開されているので気になるのを購読すると良い。 だけど非公式なスレッドごとに分類表示するサイト がありこっちの方が眺めやすいのでときどき使っている。

現在の設定一覧

今回やった設定。Session ticket, OCSP staplingは対応したい。

項目 選択
HTTP/2 no
Protocol TLSv1.2
Session cache no
Session ticket no
OCSP stapling no
Strict Transport Security(HSTS) yes
Public Key Pinning HPKP(no)

現在の設定サンプル(コメント付き)

TLS関連設定はserverディレクティブ内に下のように設定した。

server {
  listen 80;
  listen 443 ssl;

  ssl_certificate_key     {{ keys_dir_path }}/privkey.pem;
  ssl_certificate         {{ keys_dir_path }}/fullchain.pem;
  ssl_trusted_certificate {{ keys_dir_path }}/chain.pem;
  # ssl_dhparam     {{ common_key_dir_path }}/dhparam.pem; #don't use DCE

  ssl_prefer_server_ciphers on;
  ssl_protocols TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';

  ssl_stapling on;
  ssl_stapling_verify on;
  resolver            8.8.8.8 8.8.4.4 valid=300s;

  ssl_session_timeout 20m;
  ssl_session_cache shared:SSL:20m;
  ## TODO まだ未設定 Makefile修正と共に必要
  # ssl_session_ticket_key {{ keys_dir_path }}/ticket.key
  ssl_session_tickets off; # on にしてない
  add_header Strict-Transport-Security max-age=15768000;
}

設定の説明

TLSに必要な秘密鍵と証明書はssl_certificate_key, ssl_certificateで指定している。 暗号スイートの選択時にサーバー側の指定を優先しないと危ないのでssl_prefer_server_ciphersで指定している。

ssl_dhparamDHE向けのDHパラメータ を指定する。 許可した暗号スイート(ssl_ciphers)にはDHEを利用するものが無いためssl_dhparamはコメントアウトしている。

ssl_staplingからresolverまではOCSP staplingの設定。 TLSではサーバー証明書の失効を検証する。 失効確認手段として、CRL, OCSP, OCSP staplingがある。 OCSP staplingはサーバーでOCSPを行い証明書と共にクライアントへ提供するため効率がいいとされている。

ただしOCSP staplingが有効にできず調べている途中

ssl_stapling, ssl_stapling_verifyはOCSP staplingを有効にするパラメータ。 またOCSP staplingを有効化するとNginxがOCSPレスポンダーと通信する。そこで使われるDNSリゾルバをresolverで指定している。

TLSではハンドシェイクを一部簡略するSession resumptionと呼ばれる機能がある

サーバーでキャッシュする方式と暗号化してクライアント側に保持させる方式の2つある。 どちらも再開時にサーバー認証を(場合によって鍵交換を)省略できる。

ssl_session_timeoutは上記2つのどちらにも有効なパラメータで名前の通りタイムアウトを決めます。 ssl_session_cacheパラメタはTLSのセッション再開を行うためのキャッシュサイズを決めています。幾つかのパラメタがありますがsharedの効率が良さそうでした

現段階ではssl_session_ticketsoffにしている。 Nginxのドキュメント によるとAES256, AES128で暗号化されるらしいものの、 鍵が使いまわされるらしく暗号利用モードやIV管理の実装を確認しないと安全か判断できず怖かったため。

add_headerはTLSではなくHTTPの設定だが、今回はTLSに関わるのでメモを残した。

ここではStrict-Transport-Securityヘッダを追加している。 これで一度HTTPSで通信したブラウザに同一ドメインへの通信をHTTPSに限定するように伝えている。

できてないこと(今後の対応と順序)

少なくとも下は自覚している。

  • OCSP staplingが有効になっていない
  • TLS session ticketsを活かせていない
  • ALPNが無効になっている
  • HTTP/2を有効にできていない
  • HSTS Preloading(hstspreloadへ申請 )してない
  • HPKP対応できてない

今回、HTTP/2を有効にできなかった理由は2つある。

httpsスキームを利用している場合にHTTP/1.1からHTTP/2へアップグレードするにはTLS拡張のTLS-ALPN を利用するけど、 環境が古くALPNを有効にできなかったため。 そしてALPNが有効な環境を再構築しても理解不足でh2spec のテストを通過させられなかったため。 (is-http2 は通過する)

ちなみに、アプリケーションのプロトコルネゴシエーションに関するTLS拡張としてALPNとNPNの名前がしばしば見つかるけれど、NPNは標準化されなかったみたい。

HPKP対応はしていません。理解しきれてないため調べてから利用しようと判断した。

呼んだ資料

詳しくないのでRFC5246 を読み始める前に、色々な記事をつまむところから始めた。

玉石混交だけど目を通したのを下に並べておく。

他には具体例としてMozillaのSSL Configuration Generator のモダン環境の設定を確認した。 この設定ではTLSv1.1も排除されている。

CVEを日常的に追い続けるのは辛いのでbotを仕掛けるのが大事なようだ。 とりあえずtwitterはフォローしてalfredのWeb検索にCVEのキーワード検索とweb検索を登録した。

いまは隙間をみてプロフェッショナルSSL/TLS を読んでいる。

動作検証

実際のサーバーの検証は下を参考にする。手段としてはssllabs(web service), sslscan(tool), nmap, opensslなどがある。

ssllabsでは以下の様になる。

../../../_images/example-tlslabs.png

nmapを使った脆弱性検査

またnseスクリプトは自分の環境(MBP)では/usr/local/share/nmap/scripts/ssl-enum-ciphers.nseにある。

$ nmap -v --script ssl-enum-ciphers -p 443 ${target_domain}

Starting Nmap 7.40 ( https://nmap.org ) at 2017-06-24 23:30 JST
NSE: Loaded 1 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 23:30
Completed NSE at 23:30, 0.00s elapsed
......
......
......
PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|     compressors:
|       NULL
|     cipher preference: server
|_  least strength: A

NSE: Script Post-scanning.
Initiating NSE at 23:30
Completed NSE at 23:30, 0.00s elapsed
Read data files from: /usr/local/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 2.73 seconds

HTTP/2へのアップグレード

TLS拡張でアプリケーションプロトコルのネゴシエーションを行う方式にNPN, ALPNがある。 RFCで仕様として固まったのはALPNだ。

opensslで確認してみる

$ openssl s_client -host ${host} -port 443 -nextprotoneg http/1.1,h2
...
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
Next protocol: (1) h2
No ALPN negotiated
...

$ openssl s_client -host ${host} -port 443 -alpn http/1.1,h2
...
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
...

ちなみにちゃんと準備すると下のようにALPNは有効になった。

$ openssl s_client -host ${host} -port 443 -alpn http/1.1,h2
...
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
ALPN protocol: h2
...

他ツールで確認する

is-http2は公式ドキュメントに従ってnpmでインストールして公式通りに使ってみると簡単に動く。

h2specは詳しくサポート範囲を確認してくれるツールです。ちなみに確認したら散々な結果だった。 なので今回はhttp2は無効にした。

$ docker run summerwind/h2spec http2 --strict -h ${host}
Finished in 9.3655 seconds
95 tests, 1 passed, 0 skipped, 94 failed

証明書取得の自動化を行った

TLS対応ではx.509証明書が必要になる。

全世界のHTTPS化を推進するにあたって重要なプロトコルとしてACMEが策定されている。 これは証明書のなかで最もレベルの低いドメイン証明書(DV)を自動で発行できる仕組みだ。 (他には企業証明書(OV), EVがある)

ACMEを利用することでDVを無料で発行できるLet’s Encryptという証明局がある。 またACMEクライアント実装にはcertbotというソフトウェアがある。

用語 説明
ACME IETFのworkingグループで検討されているプロトコルでX.509証明書の発行を自動で行える
Let’s Encrypt ACMEプロトコルによる非営利の認証局で無料でTLSのX.509証明書を発行する
certbot ACMEの有名なクライアントで自動で証明書の取得と配置を行う

ACMEではhttp://${target_domain}/.well-known/acme-challenge/配下へのアクセスを利用して対象ドメインが認証要求元の管理下か自動で判断する。 ワイルドカードドメインは認証オブジェクトに含んではならない(MUST NOT)。 しかしSANsオプション付きの証明書は発行できる。 certbotでは -d によって証明したいドメインを指定する。 SANsオプションもサポートされている。

/usr/local/bin/certbot-auto certonly --webroot -w {{ letsencrypt_root }} -d {{ _server_name }} -d {{ _hostname }}

ちなみに -w で取得した証明書の配置先を指定する。 Let’s Encryptでは証明書の期限が30日なのでcronなどで定期的実行する必要がある。

certbotでは、その際 –keep-until-expiring で不用意な更新を行わないようにできる。また –non-interactive フラグによって対話モードを切らないとcronで支障をきたす。 また –renew-hook フラグで更新後に実行するスクリプトを指定できるので証明書更新後に必要なnginxの再起動などを指定する。

# インストールの仕方によってcertbot, certbot-autoと名前が異なる
/usr/local/bin/certbot-auto certonly -n --webroot -w {{ letsencrypt_root }} -d {{ _server_name }} -d {{ _hostname }} --keep-until-expiring --renew-hook '/sbin/service nginx restart

crondで動かすためのサンプル設定を残しておく。