Let's Encrypt で SSL/TLS 証明書をインストールして自己証明書から脱却

# はじめに - 背景

HTTP 通信を暗号化する場合、SSL/TLS 証明書を発行する必要がある。証明書は認証局により発行され、有名な証明局には Verysign や Cybertrust 等がある。

証明書の発行には費用がかかるため、検証環境や社内ツールではサーバ自身を認証局として、証明書を発行する自己証明書（通称オレオレ証明書）が使われることが多い。

しかし、自己証明書はブラウザにより信頼された認証局として見なされず、セキュリティの問題がある可能性が高い証明書として扱われ、ページが表示できなくなる場合がある。

無料・自動で証明書を発行する Let's Encrypt という認証局が登場したためこれを使ってみることにした。

# はじめに - Let's Encrypt とは

Let's Encrypt は認証局であり、無料・自動で証明書を発行するサービスを提供している。非営利団体 ISRG (Internet Security Research Group) により運営されている。

## Let's Encrypt が発行したサーバ証明書は全ての主要ブラウザで信頼された証明書として扱われる

Let's Encrypt は認証局（CA）としてアメリカ合衆国の大手認証局の IdentTrust からのクロス署名を受け取ったため、全ての主要なブラウザで Let's Encrypt が発行された証明書は信頼された証明書として扱われる。

次のとおり FireFox で確認すると IdentTrust のルート証明書があることが分かる。

  ![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/205b1fe7-29cd-58f7-e49f-ca19ab57f2b8.png)

IdenTrust によってクロス署名された中間証明書は「**Let's Encrypt Authority X3**」（※注）と「**Let's Encrypt Authority X4**」（※注）である。この中間証明書により、<https://helloworld.letsencrypt.org/> のとおり信頼されたサイトとして扱われる。

（※注）2017/10/21 修正：Let's Encrypt Authority X1, X2 は Let's Encrypt が開始された当初に利用していたが、10/21 現在 Let's Encrypt Authority X3, X4 が使われていたため修正した。（参考情報：[Let's Encrypt - Chain of Trust](https://letsencrypt.org/certificates/)）

Let's Encrypt は「DV(Domain Validation)：**ドメインの所有を確認して発行する認証局**」である。ユーザとしては、Let's Encrypt で認証されたサイトはブラウザ上で「信頼されるサイト」であると表示されるものの、信頼されているのは**閲覧しているサイトがドメインの所有者のものであること**という点を念頭におくとよいだろう。

## Let's Encrypt が発行する証明書についてと、発行に関する動作概要

Let's Encrypt が発行する証明書と、証明書発行に利用する Certbot ツール動作の概要は次のとおりである。

- 無料・自動で SSL/TLS 証明書が発行される（証明書を発行する Certbot ツールが提供されており、サーバの認証は [ACME プロトコル](https://github.com/ietf-wg-acme/acme/) により行われる）
- Certbot は証明書を自動でインストールし、apache や nginx の設定ファイルを書き換えて証明書へのパスを更新する。
- サーバ証明書が基本であり、ワイルドカードの証明書は発行できない
  - 但し、バーチャルホストを利用している場合等、複数の FQDN を持つサーバを証明するために、複数（最大 100）のサーバ名が指定できる。
- 証明書の有効期限は90日（約3カ月）で切れる （変更不可）


## Let's Encrypt がドメイン管理者を認証する仕組み

Let's Encrypt は証明書の発行を要求してきた相手が、ドメイン管理者であることを認証するために、ACME プロトコルを利用している。（参考情報：[Let's Encrypt 総合ポータル - Let's Encrypt の仕組み - ドメインの認証](https://letsencrypt.jp/technology/#DomainValidation)）



## Lets' Encrypt のサーバ証明書を発行する （標準的な方法：Docker を使わない場合）

Lets' Encrypt では先に述べた方法を以って認証を行うため、証明書の発行時に、手動で CSR（Certificate Signing Request）を作成する必要はない。Certbot ツールを利用して証明書を発行する。

Certbot ツールは Python で作成されており、動作させるためにいくつかのパッケージが必要となる。これらのパッケージのいくつかは各 OS の拡張パッケージリポジトリにあるため、インストール前にパッケージリポジトリを追加する設定が必要となる。（10/16 現在、CentOS 6.6 環境において EPEL リポジトリを使ってインストールしたパッケージは python-pip だけであった）
（参考情報：[Let's Encrypt 総合ポータル - Certbot クライアントのインストール - CentOS 6 / RHEL 6](https://letsencrypt.jp/usage/install-certbot.html#CentOS6)）


1. Certbot のインストール（参考情報：[Let's Encrypt 総合ポータル - Let's Encrypt の使い方](https://letsencrypt.jp/usage/)）
例では CentOS6 でインストールした時のもの。
    - ```sudo yum install epel-release```
    - ```wget https://dl.eff.org/certbot-auto```
    - ```chmod a+x certbot-auto```
1. Certbot(-auto) を実行する。
    - ```./certbot-auto```
        - スクリプト内部で ```sudo su -``` を実行しているため権限が必要。
        - メールアドレスを登録することになる
        - virtual server の情報を読んで証明書のインストールを行う模様
        - DNS で正引きした IP アドレスで運用するサーバかどうか確認が行われる（証明書を発行したい対象の WEB サーバ以外で Certbot を実行する場合は、standalone サブコマンドを使うこと）

2017/10/17 現在、[ルート制限](#prd_ratelimit) によりインストールが失敗しているので、発行が成功したら改めて記載する予定。

2017/10/20 発行が成功した。

```shell-session:発行成功時の出力結果（ドメインはmyfakedomain.comへ修正）
% sudo ./certbot-auto -d myfakedomain.com
  : <snip>
Cleaning up challenges
Deploying Certificate for myfakedomain.com to VirtualHost /etc/httpd/conf.d/ssl.conf

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1

-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://myfakedomain.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=myfakedomain.com
-------------------------------------------------------------------------------
```

自動作成されたサーバ鍵と発行された証明書を確認したところ次のような内容であった。

|項目|内容|
| --- | --- |
|SSL/TLS プロトコルバージョン|TLS 1.2|
|署名アルゴリズム|SHA256withRSA|
|CA 鍵長（RSA）※Let's Encrypt Authority X3|2048 bits|
|サーバ鍵長（RSA）|2048 bits|
|[Weak key (Debian)](http://www.debian.or.jp/blog/openssl_problem_qanda.html)| なし|
|[EV 証明書](https://ja.wikipedia.org/wiki/Extended_Validation_%E8%A8%BC%E6%98%8E%E6%9B%B8)|ではない（Qualys判定結果）|
|[CT 証明書](https://www.symantec.com/ja/jp/page.jsp?id=ssl-certificate-transparency)|ではない（Qualys判定結果）|
|[OCSP Must Stapling](https://www.cybertrust.ne.jp/journal/ocsp-must-staple-ocsp-multi-stapling-onecrl.html)|ではない（Qualys判定結果）|
|OCSP サーバ| http://ocsp.int-x3.letsencrypt.org |
|認証パス| DST Root CA X3 <BR> > Let's Encrypt Authority X3 <BR> > myfakedomain.com|

DNS の CAA レコードで発行する認証局を指定できるそうなので、ついでに設定しておいた。こうすることで、第三者に自分が管理しているドメインに対して勝手に証明書を発行されるのを防げるようになる。（（対応している）認証局が証明書を発行する際に CAA レコードを確認するため）

また、apache の設定ファイルが次のとおり変更されており、証明書と秘密鍵、中間証明書の設定が上書きされていた。

```Certbot実行により変化したhttpd.conf（ドメインはmyfakedomain.comへ修正）
<VirtualHost *:443>
    : <snip>
  SSLCertificateFile /etc/letsencrypt/live/myfakedomain.com/cert.pem        # サーバ証明書へのパス
  SSLCertificateKeyFile /etc/letsencrypt/live/myfakedomain.com/privkey.pem  # サーバ秘密鍵へのパス
  Include /etc/letsencrypt/options-ssl-apache.conf
  SSLCertificateChainFile /etc/letsencrypt/live/myfakedomain.com/chain.pem  # 中間証明書へのパス
</VirtualHost>
```

```text:/etc/letsencrypt/options-ssl-apache.confの内容
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.

SSLEngine on

# Intermediate configuration, tweak to your needs
SSLProtocol             all -SSLv2 -SSLv3
SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
SSLHonorCipherOrder     on

SSLOptions +StrictRequire

# Add vhost name to log entries:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common

#CustomLog /var/log/apache2/access.log vhost_combined
#LogLevel warn
#ErrorLog /var/log/apache2/error.log

# Always ensure Cookies have "Secure" set (JAH 2012/1)
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
```

### Certbot が作成する証明書 等が保管されるディレクトリ

参考情報：https://certbot.eff.org/docs/using.html#where-are-my-certificates

Certbot を実行すると ```/etc/letsencrypt``` 配下にファイルが作成される。

- .updated-options-ssl-apache-conf-digest.txt
  - ファイル名から推測するに、更新した証明書を自動で Apache 設定に組み込む際に、組み込むファイル（options-ssl-apache.conf）が変更されていないかチェックするためのファイルと思われる。（公式情報が見つからず）
- accounts
  - 保管されているファイルのファイル名を見る限り ACME プロトコルで利用されるファイルと思われる。（公式情報が見つからず）
- archives : これまで取得した全ての証明書がインストールされるディレクトリ
  - 配下はドメイン名毎にディレクトリが作成され、その中に pem 形式で証明書ファイルが存在する。
- csr
  - 証明書発行時の CSR が保管されるディレクトリ
- keys ： これまで作成した全ての鍵がインストールされるディレクトリ
- live ： 最新の証明書へのショートカットが作成されるディレクトリ。Apache, Nginx からは live へのパスを記載すれば常に最新の証明書を参照できる。
- options-ssl-apache.conf
  - 更新した証明書を自動で Apache 設定に組み込むためのセキュリティパラメータ
- renewal
  - 証明書の更新時のために初回 Certbot 実行時の設定ファイルを保管する場所
- renewal-hooks
  - 証明書の更新実行時に特定の処理を動作させるスクリプトの保管場所

どのオプションで新しい証明書を作成したとしても、作成した証明書ファイルの実体は上書きも削除もされることはない。
新しく作成・更新した証明書は別のファイルとして保存される。
有効期限が切れた証明書も自動的に削除されることはありません。


## Lets' Encrypt 証明書を発行する （Docker を使う場合）

参考情報：[Let's Encrypt ユーザーガイド](https://letsencrypt.jp/docs/using.html) の項目「Docker を使う方法」


# Certbot の動作について

Certbot はプラグインに対応しており、デフォルトで動作する際はいくつかのプラグインの機能を実行する。

プラグインは「サーバ証明書の取得」や「インストール」等の機能毎に用意されており、Certbot 実行時のサブコマンドを指定することで実行できる。

```shell:Authenticatorsプラグインを実行する場合
% certbot run certonly
```

プラグインは一覧は次のとおり。詳細な動作については [Let's Encrypt ユーザーガイド](https://letsencrypt.jp/docs/using.html)の項目「証明書の取得方法 (プラグインの使い方)」を参照のこと。

|プラグイン名|サブコマンド名|説明|
|----------|-------------|---|
|Authenticators（オーセンティケータ）|certonly |SSL/TLS サーバ証明書を取得する|
|Installers（インストーラ）||SSL/TLS サーバ証明書をインストールする|
|Challenges(ドメイン使用権の認証方法 )||証明書の取得を申請したドメイン名を管理下においていることを、ACME プロトコルにおける [Challenges](https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7) を使用して認証する|


サードパーティ製

|プラグイン名|サブコマンド名|説明|
|----------|-------------|---|
|apache|--apache|SSL/TLS サーバ証明書の取得と、Apache 2.4 への証明書のインストール（Apache の設定ファイルの書き換え）を自動的に行う|
|webroot|certonly と ----webroot を両方指定する|動作中のウェブサーバのドキュメントルートディレクトリ以下に、ドメイン使用権者の認証用のファイルを自動的に設置することにより、SSL/TLS サーバ証明書の取得する|
|nginx|--nginx|SSL/TLS サーバ証明書を取得と、Nginx への証明書のインストール（Nginx 設定ファイルの書き換え）を自動的に行う|
|standalone|certonly と --standalone を両方指定する|SSL/TLS サーバ証明書の取得のみを行なう|
|manual|certonly と --manual を両方指定する|ドメイン使用権者であることの認証を手動で行って、SSL/TLS サーバ証明書を取得する|

プラグインを開発する場合は、[Certbot - Writing your own plugin](https://certbot.eff.org/docs/contributing.html#dev-plugin) を参照すること。また、レート制限があるため開発者は本番用 API の代わりに[ステージング環境](https://letsencrypt.org/docs/staging-environment/)を利用すること。ステージング環境にもレート制限はあるが、下記の点が異なる。

ステージング環境のレート制限は次のとおり。（10/16 時点）（参考情報：[Staging Environment - Rate Limits](https://letsencrypt.org/docs/staging-environment/)）

- 以下に書かれた制限以外は本番 API のレート制限と同じである
- 登録ドメインごとの証明書が作成できるのは 週 30,000 枚までである
- 重複した証明書の上限は週 30,000 枚である。大文字・小文字は区別し、複数のサブドメインをまとめた証明書の場合は組み合わせまで一致している場合に重複とみなす。更新操作も重複とみなされる。
- 認証失敗の上限は 60回/時間 である。
- 1 つの IP アドレスにおいて 3 時間の間に最大 50 アカウントが作成できる。


## Certbot(-auto) 実行時にエラーが発生した場合

### There were too many requests of a given type ～

```text:エラーメッセージ例
There were too many requests of a given type :: Error creating new cert :: too many certificates already issued for: example.com
```

レート制限にかかる頻度で証明書の発行・更新要求が行われたことが原因となるエラーである。

レート制限を一時的にリセットする方法はないため、制限にかかった場合の対象方法としては、レート制限を鑑みてしばらく待ってから再実行することである。

<a name="prd_ratelimit"></a>
本番環境のレート制限は次のとおり。（参考情報：[Let's Encrypt - Rate Limits](https://letsencrypt.org/docs/rate-limits/)）

- 登録ドメインごとの証明書が作成できるのは 週 20 枚までである
  - 登録されたドメインは一般にドメイン名レジストラから購入したドメインの一部である。例えば `www.example.com` という名前において登録されたドメインは `example.com` である。これらは [Public Suffix List](https://publicsuffix.org/) により判断される。
- サブドメインを 1 つの証明書にまとめられる上限は 100 である
- 1 週間に最大 2,000 （登録ドメイン毎の 20/週 × サブドメイン 100）の一意のサブドメインを含む証明書を発行できる
- 重複した証明書の上限は週 5 枚である。大文字・小文字は区別し、複数のサブドメインをまとめた証明書の場合は組み合わせまで一致している場合に重複とみなす。更新操作も重複とみなされる。
- 更新処理は新規作成時の制限にかからない。但し重複の制限にかかる。
- 「new-reg」、「new-authz」、「new-cert」エンドポイントに対する全体的なリクエストの上限は 20/秒 である。
- 「/directory」エンドポイントと、「/acme」ディレクトリ（サブディレクトリ）に対する全体的なリクエストの上限は 40/秒 である。
- 1 つの IP アドレスにおいて 3 時間の間に最大 10 アカウントが作成できる。
- 1 つの IPv6 アドレスレンジ（/48）において 3 時間の間に最大 500 アカウントが作成できる。
- 認証をペンディングできる上限は 300 までである。

尚、レート制限の時間枠はスライディングウィンドウのため、例えば月曜に10枚の証明書を発行して、金曜に10枚の証明書を発行した場合、月曜には制限は解除される。

### Certbot を再実行した場合の動作

Certbot をサブコマンドを指定しなかった場合、またはサブコマンド certonly もしくは run をつけて実行した場合には、同じドメイン名の証明書が既に作成済みであっても新し証明書を 1 枚作成するリクエストが行われます。


# 運用 - Certbot で取得した証明書を更新する

CentOS 6 での利用を想定しています。各 OS において必要となるコマンドの詳細は /etc/init.d/httpd と Apache のドキュメントを参照するとよい。

```shell-session:Certbotによる証明書の更新(証明書の更新のみ)
$ sudo certbot renew
```

```shell-session:Certbotによる証明書の更新(Apache自動再起動)
$ sudo certbot renew --post-hook="service httpd graceful"
```

(2017/12/21追記)

```sh:更新成功時のcron実行結果の出力内容
/opt/eff.org/certbot/venv/lib/python2.6/site-packages/cryptography/__init__.py:26: DeprecationWarning: Python 2.6 is no
longer supported by the Python core team, please upgrade your Python. A future version of cryptography will drop support
 for Python 2.6
  DeprecationWarning
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/myfakedomain.com.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator apache, Installer apache
Renewing an existing certificate
/opt/eff.org/certbot/venv/lib/python2.6/site-packages/acme/jose/jwa.py:110: DeprecationWarning: signer and verifier have
 been deprecated. Please use sign and verify instead.
  signer = key.signer(self.padding, self.hash)
Performing the following challenges:
tls-sni-01 challenge for myfakedomain.com
Waiting for verification...
Cleaning up challenges

-------------------------------------------------------------------------------
new certificate deployed with reload of apache server; fullchain is
/etc/letsencrypt/live/myfakedomain.com/fullchain.pem
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/myfakedomain.com/fullchain.pem (success)
-------------------------------------------------------------------------------
Running post-hook command: service httpd graceful
```

## 証明書の更新が必要ない場合のrenew動作

```shell-session:証明書の更新が必要なかった場合のCertbot動作（ドメインはmyfakedomain.comへ修正）
$ sudo /usr/local/sbin/certbot-auto renew --post-hook="service httpd graceful"
/opt/eff.org/certbot/venv/lib/python2.6/site-packages/cryptography/__init__.py:26: DeprecationWarning: Python 2.6 is no longer supported by the Python core team, please upgrade your Python. A future version of cryptography will drop support for Python 2.6
  DeprecationWarning
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/myfakedomain.com.conf
-------------------------------------------------------------------------------
Cert not yet due for renewal

-------------------------------------------------------------------------------

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/myfakedomain.com/fullchain.pem (skipped)
No renewals were attempted.
No hooks were run.
-------------------------------------------------------------------------------
```

# 最後に - 所感

これまで、認証局は社会的な信頼のある団体に対して証明書を発行するという前提があったために、ユーザは証明書が使われているサイトであれば一定の信頼をおいていたはずである。

尚、認証局は次のとおり何を信頼したことを以って証明したとするか異なることに注意。

- DV(Domain Validation)：ドメインの所有を確認して発行：実在性確認レベル1★
- OV(Organization Validation)：組織の実在の確認をして発行：実在性確認レベル2★★
- EV(Extended Validation)：より厳密な実在確認をして発行：実在性確認レベル3★★★

HTTP 通信で Basic 認証を使っている場合等、パスワードが暗号化されずにインターネット上を流れるのに比べれば、Let's Encrypt のような認証局が存在することで、あらゆるサーバの HTTP 通信が暗号化されることはユーザにとってうれしいことである。

ただし、証明書が使われているサイトだからといって信頼できるサイトであると思わないよう、証明書以外の情報を元にして信頼できるサイトであるか調べるよう心がけることが必要になるだろう。

また、証明書の更新時にもレート制限がある。Let's Encrypt 公式サイトではこのレート制限は大多数の利用者における正当な作成・更新要求に対して、十分高くなるように設定していると記載があるが、1,2 回は更新に失敗することを考慮に入れて余裕を持った更新をした方がよいと思われる。