WebサーバのHTTPS化において、SSL/TLS周りの各種設定はどう選択するべきか（2016年度版）

公開HTTPSサーバを立てるときに悩む点の一つが、SSL/TLS周りの設定です。

- SSL/TLSや暗号周りの知識がないと適切な設定を選ぶのは難しい
    - そもそも設定が何を意味しているのか読み取れない
- 「推奨される設定」が各種サイトでいくつも、微妙に違うものが紹介されていて、どれが良いのか選べない

とりわけ暗号が関連する選択肢は呪文めいていて、専門知識を持たないWebエンジニアにとっては、なかなか難しいものがあると思います。
筆者もSSL/TLSの専門家ではありません。
専門家でないなりに極力信頼できそうなソースや実例を参考にしつつ、まとめました。もともとは自分用調査資料集のような記事です。

すごく長くなってしまいました（若干力尽きている）。

## TL;DR

- [MozillaのTLS設定ガイド](https://wiki.mozilla.org/Security/Server_Side_TLS)をひな形にして設定する。[Config Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/)もある
    - とりあえず従っていれば酷いことにはならない
    - 現代的な多くのサイトでは "Modern" と "Intermediate" の中間的な設定がされている、気がする
- [SSL Server Tests](https://www.ssllabs.com/ssltest/)を活用する
    - 自サイトの設定確認
    - 他のサイトの結果も参考に
- 近年[^recent]登場した技術にも目を向ける
    - 例えば、OCSP StaplingやChaCha20-Poly1305

[^recent]: 近年と言いつつ、そんなに新しくもなかったりします……

## 事前知識
SSL/TLSはTCP上に暗号化された通信路を作るプロトコルです。その通信路の上をHTTP通信するプロトコルを指してHTTPSと呼びます。

SSL/TLSは歴史あるプロトコルなため、幾度ものバージョンアップ・プロトコルの機能拡張を経てきました。またそもそも後述する暗号スイートなど、複数のパラメータから通信方式を柔軟に選ぶことができるプロトコル設計になっています。

したがって、適切なパラメータでクライアントとSSL/TLS通信をするには、多少の知識が必要になります。

この記事では、サーバ側の観点から、決めなければならない主要なパラメータをいくつかに大別します。

- SSL/TLSのバージョンをどうするべきか
- 暗号スイートをどうするべきか
- その他の設定についてどう扱うべきか

これらについて、順次検討していきます。

### SSL/TLSのバージョン
現在考慮しなければならないプロトコルバージョンは、SSL3.0 / TLS1.0 / TLS1.1 / TLS1.2の4種類です。さらにTLS1.3（仮称？）が現在策定中で、おそらく来年以降に少しずつ広まっていくと思われます。

一般に、プロトコルバージョンが新しいほど安全で、かつ暗号スイートも新しいものが選択できます。しかし古いクライアントは古いプロトコルバージョンしかサポートしていないため、サーバがどのバージョンまでを許容するかは、セキュアさと互換性のトレードオフがあります。

### 暗号スイート
SSL/TLS上で使用される暗号技術の組のことを暗号スイートと呼びます。
より具体的には `鍵交換-認証-共通鍵暗号-メッセージ認証符号`の各アルゴリズムの組み合わせを定めるものです。

利用できる暗号スイートはプロトコルバージョンなどの制約を受けます。例えばOpenSSLで利用可能な暗号スイートを確認すると、沢山の種類が出て[^openssl-expr]きます（とても多いので`head`で省略しました）。

[^openssl-expr]: とても紛らわしいことに、OpenSSLの暗号スイート記法は、`RSA`などの表示を省略します。例えば`AES256-SHA256`は`RSA-RSA-AES256-SHA256`のことです。RFCの規格表記は`TLS_RSA_WITH_AES_256_CBC_SHA256`となるので、もうちょっと分かりやすい気がします。さらに共通鍵暗号にAES-GCMを使う場合は`AES256-GCM`などと書き、AES-CBCの場合は単に`AES256`という具合に書きます。これらの対応関係については例えば→のサイトにまとめられています（ https://testssl.sh/openssl-rfc.mappping.html ）

```
$ openssl ciphers RSA -v | head -5
AES256-GCM-SHA384       TLSv1.2 Kx=RSA      Au=RSA  Enc=AESGCM(256) Mac=AEAD
AES256-SHA256           TLSv1.2 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA256
AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
CAMELLIA256-SHA         SSLv3 Kx=RSA      Au=RSA  Enc=Camellia(256) Mac=SHA1
DES-CBC3-MD5            SSLv2 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=MD5 
```
SSL/TLSのバージョンと選択できる暗号スイートには制約があります。例えば`AES256-GCM-SHA384`はTLS1.2でしか利用できません。

クライアントとの通信に利用する暗号スイートは、クライアントとサーバが提示したリストによって定まります。したがってサーバ側で決めるべきなのは

- サーバがサポートする暗号スイート一覧
- 暗号スイートの優先順位

になります。多くの設定例でサーバ側の優先順位を使って暗号スイートを定めることになっているので、どの暗号スイートを選ぶかだけでなく、順序も重要です。

### オンラインスキャナ
オンラインでサーバのSSL/TLS設定を確認してくれるサイトはいくつもあります。

とりわけQualys SSL Labsのオンラインスキャンは、不適切な設定の発見や、各クライアントでどの暗号スイートが選択されるか等の情報も提示されるので、非常に高機能です。ちなみに、同サイトではクライアント別のデータベースが整備されており、SSL/TLSのサポート範囲なども確認できます。こちらも非常に有用です。

- https://www.ssllabs.com/ssltest/
- https://www.ssllabs.com/ssltest/clients.html

スキャナは自サーバの設定確認に役立ちますが、有名なサイトに向けてみることで各サイトの設定をのぞき見る用途にも使えます。さまざまなサイトの設定を見てみると、参考になるかもしれません。

## 検討箇所
SSL/TLSにおける設定選択は、こうすることが絶対に正しいというよりも、トレードオフから妥当な選択を導き出すという色合いが強いように思います。

いくつか検討するべきトピックについて、調べました。

### TLSバージョンサポート範囲は？
SSL/TLSは時代とともに改良が続けられていく一方で、古いバージョンには問題が見つかり非推奨・廃止になっていくという歴史をたどっています。近年においても：

- SSL3.0にPOODLE攻撃[^poodle]が発見された（2014年）
- TLS1.0にBEAST攻撃が発見された（2011年）。特定の条件下（CBCモード利用時）での危険性[^remove-cbc]が指摘された

[^poodle]: https://www.ipa.go.jp/security/announce/20141017-ssl.html

[^remove-cbc]: これの影響があったのか、TLS1.3 draftではCBCモードが廃止される方向性のようです。

こんな具合で、徐々に古いプロトコルは危険になっていっています。SSL3.0はほとんどの環境で廃止されており、TLS1.0は厳しく見て黄色信号[^tls1-yellow]という程度の状況のようです。

[^tls1-yellow]: [SSL Pulse](https://www.trustworthyinternet.org/ssl-pulse/)では黄色扱い。セキュリティ専門家からすると、黄色信号なのだろうか、程度の認識。

暗号スイート的な観点としては、新しいTLSのほうが選択肢が増えます。特にTLS1.2ではAES-GCMやSHA-2が利用できるようになっています。
したがって、セキュリティ的な観点だけで言えば、最新のTLS 1.2だけを使いたい[^tls12-only]ところです。

しかし、幅広い接続性を保っておきたいサーバの場合は、特に**4.x系までのAndroidが（デフォルトで）TLS1.0までしか対応していない**[^android-tls]という点がネックになり、TLS1.0の廃止は難しいかもしれません。

[^android-tls]: https://developer.android.com/about/versions/android-5.0-changes.html#ssl

[^tls12-only]: ちなみに、TLS1.1が使えるが1.2は利用できないというクライアントはだいぶ珍しい[^tls12-rare]ようです。

[^tls12-rare]: 例えばIPAの[SSL/TLS暗号設定ガイドライン](https://www.ipa.go.jp/files/000045645.pdf)によると、"実装に関しては、規格化された年が TLS1.2 とあまり変わらなかったため、TLS1.1と TLS1.2 は同時に実装されるケースも多く〜"とある

### 鍵交換アルゴリズムには何を選ぶべきか？
実質的な選択肢としては、 ~~ECDH~~ / ECDHE / ~~DH~~ / DHE / RSAがあります（追記: コメント欄のご指摘を反映し、ECDHとDHを削除しました）。
これらのうち、Perfect Forward Secrecy(PFS)[^pfs]を満たすために、ECDHE/DHEを暗号スイートリストに含めて、優先させるほうがよさそうです。

特にクライアントがiOS9以降の場合は、今後ATSで要求される暗号スイートを提供せねばならず、ECDHEは対応が必須[^apple-ats]のようです。

レガシーなクライアントとの通信を考慮する場合のみ、RSAによる鍵交換を選択肢に入れることになると思います。具体的にはAndroid2系やWindowsXPです[^dh-ecdh-support]。

[^dh-ecdh-support]: https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_and_ECDHE_support

[^apple-ats]: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW57

[^pfs]: ある段階でサーバ証明書の秘密鍵が漏洩しても、それ以前の通信の解読に影響しない性質のこと、らしいです。TLSセッションごとに一時鍵を作ってそれを使うので、通信解読のためには結局一時鍵を解読しなければならない、らしい。参考： https://www.cryptrec.go.jp/symposium/20150320_cryptrec-og.pdf

### 認証方式はどうするか
実質的な選択肢はRSA or ECDSAです（DSAはほとんど普及しておらず、TLS1.3でも削除されそうです）。サーバ証明書を作る時に選ぶことになります。

- **ほとんどのサイトはRSA証明書**を使っています
    - 参考: [SSL Pulseデータの推移 (SSL Pulse Trends) ](https://kjur.github.io/www/sslpulsetrend/index_j.html)
- ECDSA証明書は一部のサイトで利用されています。RSAと同等以上の強度でも性能が良いという報告[^ecdsa-perf]があるようです
    - 例えばFacebookはECDSAを採用しているようです
    - 利用する認証局・サービスによってECDSAは選べないことがあるようです。例えば[シマンテックのサーバ証明書ラインナップ](https://www.symantec.com/ja/jp/tls-ssl-certificates/)だと、特定のものしか楕円曲線証明書をサポートしていない

鍵長としては、RSAだと2048ビット、ECDSAではsecp256r1が最もよく使われる[^ecc-curve]ようです。

[^ecc-curve]: 楕円曲線暗号ではビット長だけでなく曲線というパラメータもあるようです。最もよく使われるのがsecp256r1(NIST P-256)のようです。OpenSSLではprime256v1の名前で扱われています。なお、とても紛らわしいことに、secp256k1という似て非なるものもあるようです。

[^ecdsa-perf]: https://www.symantec.com/content/ja/jp/enterprise/white_papers/wp_forward_secrecy_report.pdf

### 共通鍵はどれを？
事実上、**AESがデファクトスタンダード**です。最近のx86サーバであればAES-NIが有効化されているはずで、対応するOpenSSLなどの暗号ライブラリを使うことで効率よく暗号処理ができます。

AESの暗号利用モードとして、TLS1.2からAES-GCMが追加されました。
それ以前から導入されていたCBCモードに比して、一般にGCMモードのほうが高速[^gcm-fast]かつ安全[^gcm-safe]ではないかとされています。TLS1.2環境ではAES-GCMは積極的に使うべきなようです。

[^gcm-fast]: 例えば、AES-NI環境でのIntelの測定を参照: https://software.intel.com/en-us/articles/improving-openssl-performance

[^gcm-safe]: CBCモードに関して、前述のBEAST攻撃のような攻撃例が発生している点からも、安全性に疑問の声があるようです: http://www.jnsa.org/seminar/pki-day/2016/data/1-3_ootsu_.pdf

### レガシーな暗号の扱い
多くの設定例では、暗号スイートリストにAES-GCMのようなモダンなクライアント用の暗号スイートと、より優先度が低い形で（リストの下の方に）レガシーな暗号をサポートするようになっています。

互換性のための典型的なものとしては、最低水準のものとして3DESやRC4を未だに有効化している例が挙げられます。

- google.comやfacebook.comであっても、 `DES-CBC3-SHA`（SSLv3時代からの暗号スイートです）をサポートしている
- facebook.comは`RC4-SHA`をサポートしている。RC4はもはや安全ではなく、使用するべきではないとされている（参考： https://tools.ietf.org/html/rfc7465 ）

もちろん、これらはあくまで最大限の互換性を保つためのギリギリの配慮だと見ることができるため、多くのサーバではここまでレガシーな暗号スイートをサポートする必要はないかもしれません。

幅広いクライアントに対応するための暗号スイートの有名例としては、Mozillaの設定例のうち"Intermediate compatibility" / "Old backward compatibility" が挙げられます。
- https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
- https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility

### AESの鍵長をどうするべきか
SSL/TLSにおいて、AESの鍵長は128/256のどちらかを選べます。これをどちらにするかで暗号スイート選択の方針が変わります。

#### 有名サイトの多くはAES-128を優先している
google.com や facebook.com は、AES128-GCMを優先しています。
これにはいくつかの理由がありそうです。

理論的背景としては、Security Strength[^security-strength]による理由が考えられそうです。
[NISTの評価](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r4.pdf)によると、

| Security Strength | 共通鍵暗号 | RSA   | ECC     |
|:-----------------:|:---------:|:-----:|:-------:|
| 112               | 3TDEA     | 2048  | 224-255 |
| 128               | AES-128   | 3072  | 256     |
| 256               | AES-256   | 15360 | 512+    |

[^security-strength]: 異なる暗号方式をセキュリティビットという単位で評価したもの。たぶんそれを破るために必要そうな計算量、という理解でよさそう。SSL/TLSのような複数の暗号を組み合わせるプロトコルでは、全体の強度は最も弱い箇所の強度に等しくなる。

世の中の多くのサーバでは、サーバ証明書にRSA-2048を使用しています。security strength的に釣り合うのはAESでいうと128が近く、256は過剰です。

また、AES-128のほうがバランスが良い選択であるという議論もあります。Mozillaのセキュリティ専門家であるBrian Smith氏の視点です:

> Conversely, it isn't clear that AES-256 offers any significant security
advantage over AES-128, though it is clearly slower, even on my
AES-NI-equipped Core i7 processor. First, AES-128 has held up pretty well
so that it might just be "good enough" in general. Secondly, as I already
pointed out in my proposal, some research has shown that AES-256 doesn't
seem to offer much more security than what we understand AES-128 to offer.
See http://www.schneier.com/blog/archives/2009/07/new_attack_on_a.html and
https://cryptolux.org/FAQ_on_the_attacks. Thirdly, when non-constant-time
implementations are used, AES-256 seems to offer more opportunity for
timing attacks than AES-128 does, due to more rounds and larger inputs.
> http://www.mail-archive.com/dev-tech-crypto@lists.mozilla.org/msg11302.html

要約すると、AES-128は十分に安全で、かつAES-256よりも明らかに効率が良い（たとえAES-NIが有効な環境であっても同様）。しかも、鍵長が長いからといって、AES-256のほうが安全だとは必ずしも言えない、という議論です。
数年前（2014年）の話題であることに留意する必要はありそうです。

#### Mozillaの設定例では、クライアントの想定によって異なる
https://wiki.mozilla.org/Security/Server_Side_TLS では、設定例ごとにどちらを優先するかが異なります。

- Modern compatibilityでは256優先
- Intermediate compatibilityでは128優先

クライアント環境でAES-NIなどの高速化命令が使えると想定するかどうか、が判断の分かれ目になっているようにも読めます。

## それ以外の技術的選択肢
SSL/TLSは、規格に関連した周辺技術も発達しています。
ここでは、どれもある面で有用だと言われていますが、未だ普及途上にある技術について軽くまとめました。

### TLS Session Cache / TLS Session Ticket
TLS接続の、特に最初の接続はCPUコストをそれなりに使います。ハンドシェイク時の公開鍵暗号周りの処理が重いとされています。この負荷を軽減するために、TLSには一度セッションを使いまわす仕組み（セッション再開: session resumption）があります。セッション再開によってサーバの負荷が大幅に下がるだけでなく、接続のレイテンシ短縮にもつながります。

セッション再開はサーバサイドに状態を保存することでも実現できます（普通のセッションキャッシュ）。おそらく導入しない理由はあまりなく、単に有効化すればいいだけのようです。

ただし、キャッシュの期限には注意が必要です。[RFC4346](https://www.ietf.org/rfc/rfc4346.txt)に以下のような記述があります。

> An upper limit of 24 hours is suggested for
>   session ID lifetimes, since an attacker who obtains a master_secret
>   may be able to impersonate the compromised party until the
>   corresponding session ID is retired. 

要するに、あんまり長くしすぎるのはセキュリティ上のリスクになるので24時間以下にしておけ、ということのようです。実際には24時間でも長すぎてキャッシュとしての有効性が低いことも多いかもしれません[^session-lifetime]。

[^session-lifetime]: 例えばNginxの`ssl_session_timeout`はデフォルトが5分になっています。典型的なWebサイトを想定するなら、その程度でも十分なのでしょう: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout

TLS Session TicketはTLS Session Cacheとは異なり、暗号化されたセッションの状態をクライアントサイドに保持させる仕組みです。この仕組みだと、複数のサーバにまたがる形でセッションの再開ができるようになります。

参考：
[TLSセッション再開 (session resumption) のしくみ](http://qiita.com/n-i-e/items/bb0e582011a5b1f9c28d)
[細かすぎて伝わらないSSL/TLS](https://techblog.yahoo.co.jp/infrastructure/ssl-session-resumption/)

とても有用な技術……なのですが

- iOSが非対応
- サーバに秘密鍵を持たせる必要がある。かつ、この鍵は定期的に再生成してローリングしていかなければならない（同じ鍵を使い続けるのはセキュアでない）

という欠点というか制限があるので、選択的に用いられる局面が多い気がします。

### OCSP Stapling
サーバから送られてきたサーバ証明書が失効していないか（現在も有効なままであるか）は、クライアントがOCSPと呼ばれるプロトコルで認証局へ問い合わせることで確認されるのが標準的です。しかしながら問い合わせには通信というオーバヘッドが発生する上に、しばしば失敗すると言われています。

OCSP Staplingは「サーバがOCSPレスポンスをキャッシュする」ことで、クライアントと認証局間の通信によるオーバヘッドを削減する技術です。性能的な改善だけではなく、認証局にクライアントの情報が流出しないという意味でプライバシーを保護する効果[^ocsp-privacy]があるとされます。

[^ocsp-privacy]: 例えば、AppleはOCSP Staplingのプライバシー面での効果に着目している節があるように見えます。参考: [What's New in Security](https://developer.apple.com/videos/play/wwdc2016/706/)（WWDCのセッションです）

- サーバが認証局へと通信できるようにしなければならない
- そもそもクライアントによってはOCSPを使っていない（e.g. Chrome[^chrome-ocsp]）

といった側面はあるものの、有効にするメリットはありそうです。

[^chrome-ocsp]: https://scotthelme.co.uk/certificate-revocation-google-chrome/

参考:
[【翻訳】Firefox における OCSP Stapling](http://mozsec-jp.hatenablog.jp/entry/2015/12/14/214926)
[【翻訳】証明書失効確認の改善: OCSP Must-Staple と Short-lived 証明書](http://mozsec-jp.hatenablog.jp/entry/2015/11/24/171255)
[我々はどのようにして安全なHTTPS通信を提供すれば良いか](http://qiita.com/harukasan/items/fe37f3bab8a5ca3f4f92)

### HTTP2
HTTP2について書かれた記事は他にもたくさんあるため、HTTP2の特徴には触れません。
SSL/TLSの観点からは次の点が大切かなと思います。

- インターネット環境でHTTP2を利用するためには、SSL/TLSによる暗号通信が事実上必須になっている
- サーバ側は、単にSSL/TLS通信ができるというだけでなく、ALPN拡張にも対応している必要がある

特に後者は忘れられやすいです。悩ましいことに、**有力なLinuxディストリビューションであっても付属のOpenSSLがALPN対応していないことがある**ので注意が必要です。具体的にはRed Hat Enterprise Linuxとそのクローン（CentOS, Scientific Linux...）に標準添付されているOpenSSLは1.0.1系なため、ALPN対応していません。

参考：
 [EC2+nginxでhttp2対応できたとおもったらできてなかった話。（解決します）](http://qiita.com/pyonk/items/45626c712e4a9938c980)
 [nginxをHTTP/2対応にする方法(Chrome 51以降でも有効にする)](http://qiita.com/pyonk/items/45626c712e4a9938c980)

## まとめ
- SSL/TLSの設定項目のトレードオフを中心に調査しました
- 記事に書いた内容は、主に2016年末ごろに調べたものです。既に古くなっている箇所があるかもしれません