MacOS に CertBot を入れて Let's Encrypt 証明書を作ってみる

時代の波には逆らえず、HTTPS 化が求められている。

今回は無料で発行できる Let's Encrypt というサーバ証明書を作ってみる。

ちょっとひと手間必要なので、真似する場合は先に全ての手順を読んでから真似してほしい。

目次

前提条件

Let's Encrypt 証明書を発行するための前提条件は以下のとおり。

一般公開しないつもりの Web API サーバで80番ポートを開けていないとか、FTP 環境もないし実装もイジれないしでサクッとファイルをアップロードできないサーバなんかだと、今回紹介する手順では証明書の発行が困難であろう。今回紹介していない別のやり方での証明書発行もあるようなので、その際は調べてみてほしい。

CertBot のインストール

まずは、Let's Encrypt の証明書を生成するためのコマンドラインツール、certbot をインストールする。MacOS の場合、Homebrew でインストールできる。

$ brew install certbot

$ certbot --version
certbot 0.31.0

こんな感じ。

証明書の発行申請を出す

続いて、証明書の発行申請を行う。

# 発行したいドメインを指定する
$ sudo certbot certonly --manual --domain 【ドメイン : 'example.com' みたいな】

Password:  # ★ sudo のパスワード

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel):  # ★メールアドレスを入力する

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A  # ★利用規約への同意、「A」を入力する

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N  # ★メールアドレスを公開して良いかどうか、「N」を入力し断る
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for 【ドメイン】

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y  # ★ IP アドレスのロギングをして良いか、「Y」を入力しておく

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ↑ランダムな文字列が表示される

And make it available on your web server at this URL:

http://【ドメイン】/.well-known/acme-challenge/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
# ↑末尾はランダムな文字列

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

ココで Press Enter to Continue と表示されたら、Enter を押さずに、次の処理を行う。

ACME チャレンジを行う

Let's Encrypt の証明書は、DV (Domain Validation)、すなわちそのドメインを所有していることを確認できれば発行してもらえる。先程の certbot コマンド入力時にオプション引数に渡したドメインが、今回サーバ証明書を導入したい「僕のドメイン」なワケだが、それをどうやって証明するか。

Let's Encrypt の場合は、ACME プロトコルという仕組みを使って、ドメインの所有確認を行っている。先程の対話プロンプトの最後に表示された、一見ランダムな文字列と URL が、この ACME 認証に必要な情報となる。

対話プロトコルの最後で言っているのはすなわち、「あなたがそのドメインの所有者なら、我々が発行したこの文字列を含むテキストファイルを、我々が指定した URL パスに配備できるはずだろう?」ということだ。

というワケで、その作業をしてやる。

And make it available on your web server at this URL:

http://【ドメイン】/.well-known/acme-challenge/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

このように示された URL に沿って、.well-known/acme-challenge/ ディレクトリを作り、その下に yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy という空ファイルを作る。このファイルの中に、

Create a file containing just this data:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

と示された文字列のみを書き込み、1行のテキストファイルとする。コレを FTP でアップロードすれば良い。

FTP を使わない場合でも、Node.js Express サーバを使用している場合なんかであれば、express.static() を使って以下のように静的ファイルを配信できれば良いだろう。

app.use('/.well-known/acme-challenge/', express.static('my-challenge'));

アップロードができて、手元のブラウザで

にアクセスして、

というデータがレスポンスされれば準備 OK だ。

対話プロンプトを進める

ファイルの準備ができたら、Press Enter to Continue で止めていた対話プロンプトに戻ろう。

Press Enter to Continue  # ★ Enter を押す

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/【ドメイン】/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/【ドメイン】/privkey.pem
   Your cert will expire on 2019-05-15. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

このように表示されたら OK だ。/etc/letsencrypt/ ディレクトリ配下に証明書ファイル等が格納されたようだ。

手元の Mac で試したところ、/etc/letsencrypt/ 配下のディレクトリにはアクセス権がなかったので、Finder 上で設定してファイルにアクセスできるようにしておこう。

先程の /etc/letsencrypt/live/ 配下のファイルはシンボリックリンクになっている。実際のファイルは以下にある。

$ ls -l /etc/letsencrypt/archive/【ドメイン名】
total 32
-rw-r--r--  1 root  wheel  1980  2 14 10:56 cert1.pem
-rw-r--r--  1 root  wheel  1647  2 14 10:56 chain1.pem
-rw-r--r--  1 root  wheel  3627  2 14 10:56 fullchain1.pem
-rw-------  1 root  wheel  1708  2 14 10:56 privkey1.pem

CI 環境なんかを整備して、サーバ証明書の有効期限が切れる際に自動で更新・再発行するような機会を見越して、/etc/letsencrypt/live/ パスにあるシンボリックリンクを参照するようにすれば、サーバの実装側は証明書の更新による影響を受けなくなる、という親切心みたい。

とりあえず、コレでサーバ証明書の発行は終わったので、あとは各サーバ環境に適用していけば良いだけ、ということになる。今回はサーバへの適用手順などは説明しない。

ファイルの中身・役割を理解する

僕はサーバ証明書とか HTTPS 対応とか初めてだったので、これらのファイルが何なのかを調べて、サーバ証明書に関する理解を深めてみたい。

理解するには以下の記事が役に立った。

Let's Encrypt に発行してもらったファイルは全て PEM ファイルなので、テキストエディタで開ける。中身は Base64 エンコーディングされているものの、秘密情報なので公開しないように。ココでは PEM ファイルの特徴である、1行目と最終行のみ掲載する。

cert.pem … サーバ証明書

サーバ証明書は、そのドメインの所有者を Let's Encrypt が証明してくれていることを表している。

-----BEGIN CERTIFICATE-----
MIIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxx... みたいな内容が数十行続く…
-----END CERTIFICATE-----

chain.pem … 中間 CA 証明書

中間 CA 証明書の中身は、「サーバ証明書」とよく似ている。-----BEGIN CERTIFICATE----------END CERTIFICATE----- で囲まれた Base64 エンコード文字列のファイルだ。

中間 CA 証明書の役割を理解するには、「認証局」「ルート認証局」の概念を理解する必要がある。

このような状況で、ブラウザが「サーバ証明書」の発行元を調べると、「Let's Encrypt」という認証局に行き着くワケだが、この認証局の証明書をブラウザが持っていないために、信頼できる認証局とは見なせないのだ。個人で作った「オレオレ証明書 (自己署名証明書)」も同じ原理で、「そのサーバ証明書の発行元が信用出来ないよ」と怒られてしまい、ブラウザ上では「信頼できない証明書です」みたいなメッセージが出てくることになる。

でも、実際は「Let's Encrypt」という認証局は、「IdenTrust」というルート認証局に認められている、信頼のおける認証局なワケだ。ブラウザからすると、「Let's Encrypt 認証局の証明書」が分かると、その発行元が IdenTrust だと分かり、「あぁルート認証局が認めているんですね、じゃあ大丈夫でしょう」とみなせるのだ。

ということで、それを示すのが、この「中間 CA 証明書」というワケである。CA は Certificate Authority の略。

例えば、予めこの中間 CA 証明書をブラウザにインストールしておけば、それが「ルート証明書」になるワケで、HTTPS アクセスした時に警告が出なくなる。勿論、うかつにルート証明書をインストールしまくっていては、信用ならない機関が発行した証明書にも気が付かない事態になって大変危険なので、適切に扱う必要がある。

fullchain.pem : cert.pemchain.pem を結合した証明書ファイル

それを実現するために、「サーバ証明書」に「中間 CA 証明書」を同梱させる、という手法がある。極めて簡単で、サーバ証明書と中間 CA 証明書の各ファイルを cat コマンドで結合するだけで良い。

-----BEGIN CERTIFICATE-----
MIIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxx... コレがサーバ証明書。
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyy... コレが中間 CA 証明書。
-----END CERTIFICATE-----

こんな感じ。コレを「サーバ証明書です」とブラウザに渡してやれば、ブラウザは「前半のサーバ証明書の発行元は Let's Encrypt だな、そして後半の Let's Encrypt 証明書はルート認証局の IdenTrust が発行元だな、じゃあ安全!」と判断できる、というワケだ。

このような証明書は、結合されているので「チェーン証明書」と呼ばれたりする。

privatekey.pem : 秘密鍵ファイル

サーバ証明書と中間 CA 証明書で、そのサーバの信頼性は確認できるようになった。クライアントは信頼できる HTTPS サーバであると確認が取れるようになった。

SSL・TLS 通信というモノは通信内容を暗号化している。初めにサーバから「公開鍵」を提供し、クライアント側でその公開鍵を使ってリクエストを暗号化してもらう。サーバは暗号化されたリクエスト情報を受け取ったら、「秘密鍵」を使って復号する。こういう仕組みによって、通信内容の盗聴を防いだり、改竄を困難にしたりしているワケだ。

ココで生成されたのは、サーバ側に格納しておく「秘密鍵」のファイルだ。中身はこんな感じ。

-----BEGIN PRIVATE KEY-----
MIIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxx... みたいな内容が数十行続く…
-----END PRIVATE KEY-----

「公開鍵」は「秘密鍵」から生成できるが、公開鍵からは秘密鍵を復元できない。クライアントに返す公開鍵は、この秘密鍵から生成している関係となる。手元でも openssl コマンドで公開鍵を生成したりできるので、試してみると良いだろう。

なお、この秘密鍵にはパスフレーズが付けられておらず、秘密鍵自体が暗号化されていない。パスフレーズを設定して秘密鍵を暗号化した場合は、以下のような内容になっている。

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,314C7F2F0684201A

onYxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxx... みたいな内容が数十行続く…
-----END PRIVATE KEY-----

2行目に ENCRYPTED と記載があったりする。

パスフレーズを設定した秘密鍵の生成や、パスフレーズ設定済の秘密鍵からパスフレーズを解除したりする際も openssl コマンドで行える。

中身と形式が分かるようにファイル名を変えるとしたら

前述の参考文献の情報に基づき、中身と形式が分かるようにファイル名を変えるとしたら、僕ならこうする。

いずれも、最終的な拡張子は .pem に統一されていて、関連付け設定がしやすいかなと思う。それでいて、拡張子の手前のピリオド区切りを見れば、crt (証明書) なのか key (鍵) なのかの区別が付く、という感じ。

以上

ミドルウェアの種類によってインストール方法は異なるが、基本的には「チェーン証明書」と「秘密鍵」をサーバにセットすれば、Let's Encrypt 証明書を利用した HTTPS サーバが構築できるだろう。