title: つよいSSLサーバを作る
author: Amane Katagiri
summary: 自分用メモ
date: 2016-02-24T01:54:00+09:00
modified: 2016-11-05T03:22:00+09:00
category: tech
license: CC BY 4.0
rating: sfw
ai_summary: Tornadoを用いてSSLサーバーを構築し、SSL LabsのテストでA+評価を獲得するための設定方法を解説した記事です。具体的には、`HTTPServer`でのTLS 1.2の指定やセキュアな暗号スイートの選択、証明書の適切な配置方法を説明しています。また、高評価に不可欠なHSTS（Strict-Transport-Security）ヘッダーをTornadoのハンドラで実装するコード例や、HSTSプリロードリストへの登録要件についても触れています。
ai_keywords: Tornado, SSL/TLS, SSL Server Test, HTTPServer, 暗号スイート, HSTS, Strict-Transport-Security, セキュリティ設定, Python, サーバー証明書

Tornadoでは簡単にSSLサーバを構築できるということで、指定できるオプション等について調べるついでに[SSL Server Test](https://globalsign.ssllabs.com/)でA+を取れるように頑張りました。現在の評価は[SSL Server Test: ama.ne.jp](https://globalsign.ssllabs.com/analyze.html?d=ama.ne.jp)で見ることができます。

++(2016-11-05) **もうこのサイトのバックエンドはTornadoではありません。**上記のサーバーテストはA+を返すかもしれませんし、返さないかもしれません。++

なお、A+を獲得するには、適切な暗号スイートの指定はもちろんのこと、`Strict-Transport-Security`レスポンスヘッダが必要です。

[TOC]

# SSLサーバの設定
さて、ama.ne.jpは今のところ以下のようなHTTPServerで動作しています。

```python
server = HTTPServer(app, ssl_options={
    "certfile": options.ssl_crt,
    "keyfile": options.ssl_key,
    "ssl_version": ssl.PROTOCOL_TLSv1_2,
    "ciphers": "EECDH+HIGH+AES!SHA:" +
               "!ADH:!RC4:!MD5:!aNULL:!eNULL:!SSLv2:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5",
})
```

ここでは、Tornadoの[netutil.py](http://www.tornadoweb.org/en/stable/_modules/tornado/netutil.html)を元に、おそらくHTTPServerに`ssl_options`として与えられるであろうと思われるオプションについて書いていきます。

定数やオプションなどの情報はPythonの[ssl](http://docs.python.jp/3.5/library/ssl.html)モジュールやopensslの[ciphers](https://www.openssl.org/docs/man1.1.1/man1/ciphers.html)に詳しいです。

## ssl_version
使用するSSL/TLSのバージョンを指定します。`PROTOCOL_TLSv1_2`でTLS1.2のみを使用できる状態になります。

### TLS1.0について
TLS1.0にはBEAST攻撃に対する脆弱性があります。ただし、古いブラウザをサポートしなければならない地獄のような状況下なら有効にしなければならないかもしれません。`PROTOCOL_SSLv23 | OP_NO_SSLv2 | OP_NO_SSLv3`で、TLS1.0、TLS1.1、TLS1.2を使用できる状態になります。

## ciphers
oepnsslの記法に倣って使用する暗号スイートを指定します。

### 暗号スイート指定で用いる記号
* `:`は暗号スイート指定の区切りです。
* プレフィクス`+`でその暗号スイートを追加します。ただし、1つの暗号スイート指定内で接続されて用いられた場合はAND条件を指します。
* プレフィクス`-`でその暗号スイートを取り除きます。ただし、1つの暗号スイート指定内で接続されて用いられた場合はNAND条件を指します。また、これより後ろの暗号スイート指定で再び追加可能です。
* プレフィクス`!`でその暗号スイートを使用しません。これより後ろの暗号スイート指定でも追加することはできません。

### 今回指定した暗号リスト
[FS(Forward secrecy)](https://ja.wikipedia.org/wiki/Forward_secrecy)を満たすECDHE-RSAのうち、鍵長の大きなものを指定しました。一応後ろで、弱いものや使わないものを取り除いています。

## certfile
自身の公開鍵の格納場所を指定します。CA証明書を含んでいてもかまいません。CA証明書を含む場合は、必ず上から

1. 自身の証明書
2. 中間CA証明書
3. より上位の中間CA証明書 ...

のように、より下位の証明書から順に並べます。ルート証明書は各ブラウザが持っているはずです。

## keyfile
自身の秘密鍵の格納場所を指定します。

## cert_reqs
証明書の妥当性チェックを行うかを指定します。サーバー側には必要ないので特に指定しません。

## ca_certs

++(2016-05-29) 証明書の妥当性チェックに使うCA証明書を指定します。サーバー側には必要ないので特に指定しません。++

# Strict-Transport-Securityヘッダの設定
[HTTP Strict Transport Security(HSTS)](https://developer.mozilla.org/ja/docs/Web/Security/HTTP_Strict_Transport_Security)は、「次からウチのサイトとはHTTPSで通信してくれ」というメッセージを伝える機能です。

幸いTornadoの*Handlerには`set_default_headers`メソッドが用意されているので、以下のようなクラスを作ってアプリケーション内の各ハンドラで継承して使うとよいでしょう。

```python
class HstsRequestHandler(RequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def set_default_headers(self):
        self._headers.add("Strict-Transport-Security",
                          "max-age=31536000;includeSubdomains;preload")
```

なお、[HSTS preload listに登録する](https://hstspreload.appspot.com/)には、長い`max-age`（1年=31536000秒くらい？）や`includeSubDomains`および`preload`の設定が必要です。

++(2016-02-25) Chromeの[HSTS preload list](https://code.google.com/p/chromium/codesearch#chromium/src/net/http/transport_security_state_static.json)にama.ne.jpが入ったようです。++

# 終わりに
自己満足しか得るものがない努力だった。
