背景
X.509のプロトコル
X.509のプロトコルとは
- X.509 は、公開鍵証明書(Public Key Certificate)の形式や認証局(CA: Certification Authority)の運用方法を定めた標準規格
- X.509は、PKIの標準規格で、デジタル証明書を使ってデバイスやサーバー、ユーザー間の通信を認証し、暗号化するために使用される
X.509の特徴
証明書の構造
- バージョン(Version)
- X.509証明書のバージョン情報。現在では主にバージョン3(v3)が使用されてる
- シリアル番号(Serial Number)
- 各証明書に一意に割り当てられる番号。CAによって発行された証明書を識別するために使用される
- 署名アルゴリズム(Signature Algorithm)
- 証明書の署名に使用されたアルゴリズム(例: SHA256 with RSA)
- 発行者(Issuer)
- 有効期間(Validity)
- 証明書の有効開始日時(Not Before)と有効終了日時(Not After)
- 対象者(Subject)
- 証明書が証明するエンティティ(例: ドメイン名や個人)の情報
- 公開鍵情報(Subject Public Key Info)
- 拡張情報(Extensions)
- 証明書の用途や制約を追加で記述するセクション
- 例として、キー使用目的(Key Usage)、拡張キー使用目的(Extended Key Usage)、サブジェクト代替名(Subject Alternative Name)などがある
- 証明書の署名(Signature)
- 発行者CAによる証明書全体の署名。証明書の改ざんを防止する
証明書の種類
- エンドエンティティ証明書(End-Entity Certificate)
- 最終的な利用者(サーバやクライアント)に発行される証明書
- 中間CA証明書(Intermediate CA Certificate)
- ルートCAとエンドエンティティ証明書の間に位置するCAに発行される証明書
- チェーン認証を構築する
- ルートCA証明書(Root CA Certificate)
- 信頼の基点となるCAの証明書
- 通常、オペレーティングシステムやブラウザにプリインストールされている
- 証明書チェーン(Certificate Chain)
- 複数の証明書が連なって信頼関係を構築する仕組み
- エンドエンティティ証明書が中間CA証明書によって、中間CA証明書がルートCA証明書によって信頼される
- これにより、信頼できるルートCAから発行された証明書のみが有効と認識される
PKIとは?
公開鍵基盤(PKI:Public-Key Infrastructure)
- PKI(Public-Key Infrastructure)は公開鍵暗号を利用した認証基盤
- PKIでは通信相手が本物であるかどうかの確認(認証)に公開鍵証明書(以下、証明書)を利用する
- PKIにおける証明書は、発行者の名前といった情報が記述されており、身分証明書の役割を果たす
- 身分証明書を、信頼できるTTP(Trusted Third Party)に発行してもらうことで、その身分証明書を信頼できるようにするのがPKIの特徴
認証局(CA:Certification Authority)
- PKIで用いられる証明書は、認証局(CA:Certification Authority)によって発行される
- PKIでは、このCAが「信頼できる第三者」にあたる
- CAは、いわば身分の証明を行っている機関なので、ユーザーに強く信頼されていなければならない
- ユーザーは、自分が信頼しているCAが発行した証明書であれば信じられるということになる
X.509証明書(Cert:Certificate)
オンライン証明書の失効状態の確認(OCSP)
- OCSP(Online Certificate Status Protocol)は、証明書の失効をリアルタイムで確認する仕組み
- 暗号化やデジタル署名に用いるX.509証明書の有効期限前に失効している証明書を、速やかに知ることができる
PKIのアナロジー
PKIはある意味運転免許とおなじような仕組み。
- 運転免許の発行の仕組み: PKI
- 運転免許証:X.509証明書(pemファイル形式)
- 公安委員会:CA
- 運転免許証の有効性の確認:OCSP
他には、印鑑証明書もおなじ仕組み。
X.509証明書の詳細
証明書の拡張子
概ね、X.509証明書は次の3つにおフォーマットがある。
拡張子/形式 | エンコード形式 | ファイル内容の形式 | 主な用途 |
---|
.pem | Base64 | テキスト形式 | Linux系、Apache、汎用的な用途 |
.crt | Base64 または DER | テキストまたはバイナリ形式 | Webサーバー、ブラウザ |
.cer | Base64 または DER | テキストまたはバイナリ形式 | Windows環境 |
pemファイル
pem証明書のフォーマット
pemファイルとはpemの書式はシンプルで、任意の証明書、鍵をbase64エンコードして以下のBEGIN/END行で挟んでつなげたテキストファイル。
pemの書式(鍵+証明書)
1
2
3
4
5
6
| -----BEGIN RSA PRIVATE KEY-----
鍵ファイルのbase64エンコード
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
証明書ファイルのbase64エンコード
-----END CERTIFICATE-----
|
pemの例(証明書+鍵)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -----BEGIN CERTIFICATE-----
MIIGQTCCBSmgAwIBAgIIM5HjhF+86iswDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
・・・略・・・
HqrtjOPEFu2v5H++eeyw9WTXhfOD2H6t/pXD6A62E6O0q21FiIZnITQlxvDCLqq6
I+0w8Lymm9iXGYdxzm6tCEekpzYyJ5TiiMGKMCez8IjFq7hrEqiARoE0U7zR1JOP
qPTsfdoBrs5Wr8szzLo6NMMLknnT
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuvvBkPx5p5HIRbSnihQAlGP8CKTrH+981nMhU93OWWBzmXBT
IULoO2Ah2a57H3Kxt5UHzyvawqGXOMgYk5SYq13Wnd7yqQA5JIKg+3jS2vd2C8oQ
・・・略・・・
SV4ZZCLL0FlWadPo3NVD0nnw5JvepABwdMQzCk/47efVA8LugjBGApWSpyCEEqRk
1uS4FuJSyQGczMcHLnVHBhlyfTt6DtFtwFqI3coimOU1cQj0Gg9w
-----END RSA PRIVATE KEY-----
|
このままでは人の目では証明書・鍵の情報が見えないので、
それを見やすくするには openssl コマンドを使う。
pem証明書の中身の例
example.orgでlet’s encryptで発行した例。Issuerはlet’s encryptで、subjectはexample.org.になっている
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
| $ openssl x509 -text -noout -in cert.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
03:5e:45:ba:d3:d7:57:c4:45:b6:a3:6a:5e:d5:9a:1b:68:4c
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Let's Encrypt, CN = R3
Validity
Not Before: Jan 21 12:08:47 2022 GMT
Not After : Apr 21 12:08:46 2022 GMT
Subject: CN = example.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:cb:f2:2d:8f:a6:6c:fc:78:33:36:96:cc:bb:5d:
8b:bf:ff:42:81:b0:1e:9e:a3:63:ec:ef:78:fd:60:
0b:7e:62:e4:86:fc:0d:69:54:a3:b8:6a:c1:ed:43:
ce:94:61:c0:4b:67:71:07:2b:0d:36:fc:b1:c6:e5:
25:06:87:89:e4:28:6f:1c:6c:ce:12:a3:39:bd:6a:
0d:21:3f:2d:aa:5a:ad:36:b8:f3:d9:85:64:71:94:
5c:fa:c3:4c:47:74:d2:4c:41:82:4f:0a:64:ec:b2:
b0:88:3d:d2:2d:93:4f:0e:7d:28:94:09:54:d9:df:
0f:af:c9:cb:db:30:04:e3:aa:10:80:41:f3:35:80:
11:6e:57:e3:fb:1a:01:7f:11:97:87:55:10:4b:06:
e9:fb:fa:b4:e6:9c:1b:e9:18:a7:ad:23:e0:8e:96:
9f:47:aa:a9:72:75:3f:c3:c9:50:5b:42:6e:f3:0b:
49:c3:5e:3d:d5:04:66:d4:54:f7:95:0c:10:5b:2d:
2c:04:fa:fe:9d:7e:27:1e:1e:c6:51:f8:7f:a5:97:
51:93:19:29:f5:86:be:23:43:36:a9:4a:29:4f:bb:
d1:5f:dc:66:e7:73:a0:d1:97:87:fc:e5:56:ac:e3:
85:53:a8:e4:f3:fe:02:52:95:e9:6a:7b:5c:e0:12:
9d:cd
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
9B:BF:4D:2A:49:41:A9:B6:3D:DD:D9:B2:27:94:71:5D:C0:FC:D3:34
X509v3 Authority Key Identifier:
keyid:14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6
Authority Information Access:
OCSP - URI:http://r3.o.lencr.org
CA Issuers - URI:http://r3.i.lencr.org/
X509v3 Subject Alternative Name:
DNS:example.org, DNS:www.example.org
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1
Policy: 1.3.6.1.4.1.44947.1.1.1
CPS: http://cps.letsencrypt.org
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 46:A5:55:EB:75:FA:91:20:30:B5:A2:89:69:F4:F3:7D:
11:2C:41:74:BE:FD:49:B8:85:AB:F2:FC:70:FE:6D:47
Timestamp : Jan 21 13:08:47.935 2022 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:21:00:DF:B2:A6:49:8C:8A:48:48:04:A3:59:
07:0F:6B:E5:7D:AD:84:48:74:0E:95:3E:37:A5:B2:D1:
8F:E7:44:01:A7:02:20:5C:37:5C:89:B0:49:BD:0E:D7:
C1:50:37:E1:97:5B:8B:CD:9C:95:13:95:45:C3:B0:80:
14:19:C2:66:66:DF:84
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 41:C8:CA:B1:DF:22:46:4A:10:C6:A1:3A:09:42:87:5E:
4E:31:8B:1B:03:EB:EB:4B:C7:68:F0:90:62:96:06:F6
Timestamp : Jan 21 13:08:48.405 2022 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:20:01:D0:6F:15:9C:B3:6E:9A:07:A5:45:07:
DE:C3:84:5D:5E:49:A4:BA:F2:AA:20:D5:E5:12:55:1F:
EE:97:F7:78:02:21:00:88:47:A9:41:9E:08:76:BA:A4:
73:ED:CE:43:65:A6:06:1F:78:EB:0D:1A:C5:D0:6C:66:
48:B5:DB:DA:F6:C3:1C
Signature Algorithm: sha256WithRSAEncryption
7c:e3:54:0f:63:57:4d:6e:7f:01:bd:8e:ec:5f:a2:72:73:ee:
da:79:2e:ca:2a:d4:13:f6:b7:af:52:d1:c2:84:0d:f2:9d:c1:
37:03:eb:af:4d:76:61:dc:ca:37:a3:c1:f3:76:85:b7:d1:9e:
5e:51:7a:02:9b:74:12:34:43:d7:94:fe:26:28:f3:12:c2:f1:
0a:ad:72:47:56:bd:d8:bb:4f:60:bc:55:51:62:dd:fb:b9:7b:
58:46:30:a2:c6:7d:8c:95:03:3e:16:c2:d3:5d:b0:f5:ef:e5:
97:09:67:35:20:ac:25:16:48:6a:16:25:ef:10:12:97:2f:4f:
1d:d0:8c:4e:49:9f:fb:c9:2b:9e:92:6f:8d:d4:c6:76:85:d3:
64:16:55:d5:69:81:e9:55:0e:46:2f:77:8a:86:bf:50:99:e8:
ae:3b:19:53:ab:d4:d6:72:3e:d9:27:a6:75:7b:c7:67:48:f6:
7a:e0:b9:23:5c:ad:8a:bd:4f:7f:60:01:99:00:68:ce:5d:72:
c4:b7:54:65:47:c7:01:45:af:1c:1e:cf:48:dd:e4:bc:b0:40:
90:e7:a3:64:d6:84:af:aa:96:fa:99:3e:83:fd:e0:dc:27:cd:
c3:51:66:16:2d:c7:f4:8f:76:dd:7c:f3:00:ff:5c:f9:e5:c7:
f4:ea:5f:d7
|
pem証明書の中身の意味
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| Certificate:
Data (データ部)
Version (X509のバージョン)
Serial Number (シリアルナンバー)
これにより同一CAで同一機関への証明書を2度作っても区別できる
Signature Algorithm
Signature Algorithm (CAが利用する暗号化アルゴリズム)
Issuer (証明書の発行者)
Validity (有効期限)
Not BeforeからNot Afterまで有効
Subject (証明される対象)
Subject Public Key Info (証明される対象の公開鍵に関する情報)
Public Key Algorithm (公開鍵のアルゴリズム)
RSA Public Key (公開鍵)
X509v3 extensions (X509 Version.3の拡張部)
X509v3 Subject Key Identifier (サブジェクトの公開鍵の識別番号)
X509v3 Authority Key Identifier (この証明書を発行したCAに関する情報)
keyid
DirName
X509v3 Basic Constraintsextensions
CA (SubjectのがCAかどうか)
Signature Algorithm (CAが利用する暗号化アルゴリズム)
Signature(デジタル署名)
|
ASN.1とは?
- ASN.1はAbstract Syntax Notation Oneの意味
- 下のような形式のドキュメント
- pemファイルはこの形式に従っている
1
2
3
4
5
6
7
8
9
10
11
12
13
| Foo DEFINITIONS ::= BEGIN
Question ::= SEQUENCE {
id INTEGER,
question IA5String
}
Answer ::= SEQUENCE {
id INTEGER,
answer BOOLEAN
}
END
|
ACME
ACMEとは?
- ACMEは、X.509証明書の自動ドメイン検証とインストールのための標準プロトコル
- ACMEはAutomated Certificate Management Environment(自動証明書管理環境)の意味
- つまり、Webサーバと認証局との間の相互作用を自動化するための通信プロトコル、いわゆるチャレンジ方式のこと
ACMEチャレンジとは?
- ACMEチャレンジは、ACMEプロトコルに基づいて実行される「ドメイン所有権を証明するための特定のタスクや手続き」のこと
- 証明書を発行する前に、認証局(CA)がドメインの所有者であることを確認するために実施する
ACMEチャレンジの種類
- DNS-01チャレンジ:
- ドメインのDNS設定に特定のTXTレコードを追加し、CAがDNSを確認
- HTTP-01チャレンジ:
- ウェブサーバーに特定のトークンを含むファイルを配置し、CAがそのファイルをHTTPで確認
- TLS-ALPN-01チャレンジ:
- サーバーが特定のTLSプロトコルを利用して応答し、CAがそれを検証
ACMEチャレンジのフロー
HTTP-01のACMEチャレンジのフロー
ACMEチャレンジの詳細
DNS-01 チャレンジ
- あるドメインのSSL証明書をリクエストしたユーザーが、そのドメインに対する権限を有しているかを、DNSサーバーを通じて確認・認証する方式
- DNS サーバー上に認証用のTXT レコードを生成し、Let’s Encrypt 側からその TXT レコードを読み取ることで認証を行う
- TXTレコードは手動で設定するか、自動化したスクリプトなどで設定する
- いわゆる、AWS Certificate Managerでやっていること
HTTP-01チャレンジ
- HTTP-01 チャレンジは、あるドメインのSSL証明書をリクエストしたユーザー(Web サーバー)がそのドメインに対する権限を有しているかを HTTP通信経由で確認・認証する方式
- Web サーバー上に認証用のファイルを生成し、Let’s Encrypt 側からそのファイルにHTTPアクセスすることで認証を行う
- いわゆるcertbotの
--webroot
を指定した時の処理
tls-sni-01チャレンジ とは
- HTTPS(443)をつかうデジタル署名の更新手法
オレオレ証明書
オレオレ証明書
- 正式な認証機関(CA)によって発行されていない、自己署名のSSL/TLS証明書のこと
- 通常のSSL証明書は、信頼されたCAによって署名され、第三者による認証が行われる
Lets’encrypt vs. オレオレ証明書
Let’s EncryptはISRGがRootで、ブラウザから信頼されている。
他方、自己証明書の場合は信頼されない。
- なお、CNはCommon Name(コモンネーム)の略称
- これは証明書の主体を一意に識別するためのフィールド
オレオレ証明書の作成
CA無し
1
| $ openssl req -nodes -newkey rsa:2048 -keyout privkey.pem -x509 -sha256 -days 1 -out chain.pem -subj "/C=JP/ST=Tokyo/L=Shinjuku-ku/O=EXAMPLE CORPORATION/OU=EXAMPLE DEPARTMENT/CN=`curl ifconfig.co`"
|
引数
req
- 秘密鍵と証明書を一度に生成する
- 使いまわすわけではないので、証明書が出来てしまえばCSRは不要なので生成しない
-nodes
node
の複数形ではなく、no DES
= 秘密鍵を暗号化しないの意味
-newkey rsa:2048
RSA
で 2048bit
の秘密鍵を生成を指定する
-keyout privkey.pem
-x509
- 証明書の生成を示す
req
コマンドはデフォルトだと CSR
生成
-sha256
SHA256
の証明書が生成される- デフォルトだと
SHA1
なので注意
-days 1
- で有効日数を指定する
- 0日や1日未満での指定はできないが
-days -1
とすれば遡った指定もできる(必要性はないだろうけど)
-out chain.pem
-subj "/C=.../ST=.../L=.../O=.../OU=.../CN=..."
- CSRを生成する場合に指定するのと同じで証明書の情報
CA有り
オレオレ中間CAの生成
1
| $ openssl req -x509 -sha256 -days 3650 -subj "/C=JP/ST=Tokyo/L=Shinjuku-ku/O=EXAMPLE CORPORATION/OU=EXAMPLE DEPARTMENT/CN=My Private CA" -nodes -newkey rsa:2048 -keyout cakey.pem -out chain.pem
|
自身のIPに対するCSRと秘密鍵の生成
1
| $ openssl req -subj "/C=JP/ST=Tokyo/L=Shinjuku-ku/O=EXAMPLE CORPORATION/OU=EXAMPLE DEPARTMENT/CN=`curl ifconfig.co`" -nodes -newkey rsa:2048 -keyout privkey.pem -out csr.pem
|
オレオレ認証局による証明書の生成
1
| $ openssl x509 -req -sha256 -days 3650 -in csr.pem -CA chain.pem -CAkey cakey.pem -set_serial 1 -out fullchain.pem
|
独自のRootCAのインストール方法
前提として、Ubuntu Linuxを使っている。
CA更新コマンド
1
2
3
4
5
6
7
| $ cd /usr/share/ca-certificates
$ mkdir mylocal
$ cp somewhere/mylocal-root-cacert.crt mylocal/
$ cp -p /etc/ca-certificates.conf /etc/ca-certificates.conf.bak
$ echo "mylocal/mylocal-root-cacert.crt" >> /etc/ca-certificates.conf
$ update-ca-certificates
$ ls -l /etc/ssl/certs/ | grep mylocal-root-cacert
|
コマンドの意味
- フォルダの作成
/usr/share/ca-certificates/
の下に適当なディレクトリを作成する- ここでは、
mylocal
とする - 以下、
/usr/share/ca-certificates/mylocal/
とする - ディレクトリは管理組織単位などでまとめるためのもの
- (おそらく、
mozilla/
というディレクトリがあると思うが、これは触らない)
- ディレクトリは無くても構わないが、ローカルなものは、ディレクトリでまとめた方が、後々、管理しやすい
- 証明書の設置
- 作成したフォルダ
mylocal
の下に、証明書ファイルを置く - ここでは、
mylocal-root-cacert.crt
とする - つまり、
/usr/share/ca-certificates/mylocal/mylocal-root-cacert.crt
となる
- 設定ファイルの編集
/etc/ca-certificates.conf
に、追加したファイルの /usr/share/ca-certificates/
以下の相対パスを追加する- つまり、
mylocal/mylocal-root-cacert.crt
を追加する
- CA-certの更新
update-ca-certificates
(/usr/sbin/update-ca-certificates
)を実行する
- シンボリックリンクの確認
/etc/ssl/certs/
下に /usr/share/ca-certificates/mylocal/mylocal-root-cacert.crt
へのシンボリックリンクができていることを確認
NOTE:
- 余談だが、これが正しく行われると、CA bundleとしてすべてのCA証明書が含まれる
/etc/ssl/certs/ca-certificates.crt
が作成される - なので、必要に応じて、これを参照すると良い
- 例
- python の
requests()
の引数に CA Bundleのパスを指定するような場合、 update-ca-certificates
が自動的に /etc/ssl/certs/ca-certificates.crt
をアップデートする- なのので、独自に追加するのは、厳禁
Let’s Encryptの基本
Let’s Encryptとは?
- Let’s Encrypt は、無料で自動化された証明書発行サービスを提供する認証局(CA)
- Webサイトが HTTPS(TLS/SSL)通信を導入するために必要な X.509 証明書を簡単に取得・更新できる仕組みを提供する
Certbotとは?
- Certbotとは Certbotは無料かつ自動でSSL証明書を発行できるツール
- CSRとKEYファイルの作成からWebサーバーの設定まで自動で行ってくれる
- さらにCronと組み合わせることで、証明書の更新作業までも完全に自動化することが可能
ISRGとEFF
- ISRG(Internet Security Research Group)
- HTTPSの普及を通じて、インターネット全体のセキュリティを向上させることを主な目的とする非営利団体
- EFF(Electronic Frontier Foundation)
- プライバシー、表現の自由、オープンインターネットの推進を目指す非営利団体
Certbot
HTTPSまでの流れ
- 有効なドメイン名を取得
- ドメインのDNS設定で、サーバーのIPアドレスに向ける
- NGINXのインストールと基本設定を行う
- NGINXをインストール
- ドメイン名に対応するサーバーブロック(仮想ホスト)を設定
- ポート80でHTTPリクエストを受け付けるように設定
- Certbotのインストール
- CertbotはLet’s Encryptの公式クライアントで、SSL証明書の取得と更新を自動化
- サーバーにCertbotをインストール
- SSL証明書の取得
- Certbotを使用して、Let’s Encryptから無料のSSL証明書を取得
- ウェブサーバーの設定に応じて、
webroot
やstandalone
モードを選択
- NGINXのSSL設定
- 取得した証明書を使用して、NGINXでSSLを有効化
- ポート443でHTTPSリクエストを受け付けるように設定
- HTTPからHTTPSへのリダイレクトを設定
- SSL証明書の自動更新設定
- Let’s Encryptの証明書は有効期限が90日間
- Certbotをcronやsystemdタイマーで定期的に実行し、証明書を自動更新
Certbotのプラグインとオプションの違い
--dns-<provider>
オプション- チャレンジタイプ: DNS-01チャレンジ
- 使用条件: DNSプロバイダのAPI経由でTXTレコードを変更できる場合
- 特徴:
- ポート80や443を開放する必要がなく、内部ネットワークやファイアウォールの背後でも使用可能
- 自動化が可能で、サーバーの公開ポートに依存しません
--webroot
オプション- チャレンジタイプ: HTTP-01チャレンジ
- 使用条件: ポート80がアクセス可能で、既存のウェブサーバーが稼働している場合
- 特徴:
- ウェブサーバーを停止する必要がなく、既存のウェブサーバーのドキュメントルートを利用
- 自動化が容易で、ウェブサーバーの設定変更が最小限で済む
--standalone
オプション- チャレンジタイプ: HTTP-01チャレンジ
- 使用条件: ウェブサーバーがない、または一時的に停止できる場合
- 特徴:
- Certbotが一時的なウェブサーバーを起動し、ポート80または443を使用
- 既存のウェブサーバーが稼働している場合は停止が必要
--nginx
オプション- チャレンジタイプ: HTTP-01チャレンジ
- 使用条件: NGINXプラグインであり、NGINXが稼働している場合に利用
- 特徴:
- CertbotがNGINXの設定を自動的に編集し、SSLを有効化
- ウェブサーバーの停止が不要で、設定のバックアップや復元も自動化
- HTTPからHTTPSへのリダイレクト設定も自動的に行われる
他には、--apache
プラグイン(オプション)もある。
AuthenticatorとInstallerの違い
CertbotはAuthenticatorとInstallerを柔軟に組み合わせることで、様々な環境でSSLの取得と設定を自動化する。
- Authenticator(認証器)
- 役割: ドメインの所有権を証明するために、Let’s Encryptのサーバーと通信して認証を行うプラグイン
- 機能: ドメインの認証プロセス(チャレンジ)を処理する
- Installer(インストーラー)
- 役割: 取得したSSL証明書をウェブサーバーや他のサービスにインストールし、必要な設定変更を自動的に行うプラグイン
- 機能: サーバーの設定ファイルを編集し、SSL/TLSの設定を有効化
Certbotの使用するファイル
フォルダの意味
/etc/letsencrypt/live/
/etc/letsencrypt/archive/
/etc/letsencrypt/renewal/
- 証明書の更新に必要なパラメータ持つファイル(
xxx.conf
)を保持 - 自動更新プロセスでそのパラメーターを使用
ファイルの意味
cert.pem
: サーバー証明書privkey.pem
: 秘密鍵chain.pem
: 中間証明書fullchain.pem
: サーバー証明書と中間証明書を結合したもの
webroot
の設定ファイルの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| #########################################
# Liveの証明書の設定
#########################################
# renew_before_expiry = 30 days 証明書の有効期限
version = 0.40.0
archive_dir = /etc/letsencrypt/archive/example.org # 過去の証明書と鍵が保存されているディレクトリ
# 最新の証明書や鍵へのシンボリックリンクのパス
cert = /etc/letsencrypt/live/example.org/cert.pem
privkey = /etc/letsencrypt/live/example.org/privkey.pem
chain = /etc/letsencrypt/live/example.org/chain.pem
fullchain = /etc/letsencrypt/live/example.org/fullchain.pem
#########################################
# 証明書の更新時に使用されるパラメータ
#########################################
# Options used in the renewal process
[renewalparams]
account = cde5598f6f44f9f55646b9d23817ed48 # Let's Encryptアカウントのハッシュ値
authenticator = webroot # ドメインの認証方法
webroot_path = /var/www/html # プラグインが使用するウェブルートのパス
[[webroot_map]] # ドメインごとにウェブルートのパスをマッピング
example.org = /var/www/html
www.example.org = /var/www/html
server = https://acme-v02.api.letsencrypt.org/directory # Let's EncryptのACMEプロトコルのエンドポイントURL
|
--staging
と--dry-run
オプションの違い
両方ともLet’s Encrypt のレートリミットを回避できるが次の違いがある。
--staging
- テスト用の証明書を実際に取得し、ディスクに保存する
- 証明書の取得や設定のテストに使用される
- テスト後は証明書が残るため、設定ファイルやディレクトリ構造を確認できる
--dry-run
- 証明書の取得プロセスをシミュレートするが、証明書は保存されない
- 自動更新のテストやエラーの検証に使用される
- ディスクに変更を加えないため、安全にテストできる
オプションの違い
--nginx
- SSL/TLS サーバ証明書を取得と、Nginxへの証明書のインストール(Nginx設定ファイルの書き換え)を自動的に行う
- Certbot 0.9.0以降に、Nginxプラグインのアルファ版が同梱されている
設定ファイル
authenticator
とinstaller
はnginxになっているwebroot
と違い、.well-known/acme-challenge
フォルダをnginxで設定することは不要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| $ cat /etc/letsencrypt/renewal/example.org.conf
# renew_before_expiry = 30 days
version = 0.40.0
archive_dir = /etc/letsencrypt/archive/example.org
cert = /etc/letsencrypt/live/example.org/cert.pem
privkey = /etc/letsencrypt/live/example.org/privkey.pem
chain = /etc/letsencrypt/live/example.org/chain.pem
fullchain = /etc/letsencrypt/live/example.org/fullchain.pem
# Options used in the renewal process
[renewalparams]
account = f67679fe4ad67d525774e1e37c0bb36a
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
|
--webroot
- 動作の流れは以下のようになる
- 事前準備
- アクター
- Certbot
- Let’s encrypt server
- Nginx server
- 動作
- CertbotはLet’s Encrypt Serverに証明書発行依頼をし、トークンを発行してもらう
- CertbotはACMEプロトコルに定義されている
.well-known/acme-challenge
にそのトークンを設置する - Let’s Encrypt の認証サーバがHTTPリクエストで
.well-known/acme-challenge
にグローバルからアクセスし、そのトークンのチェックを行う - トークンが正しければLet’s Encryptの証明書を発行しCertbotに返す
- Certbotは証明書を
/etc/letsencrypt
以下に設置する
準備として、ACMEプロトコル用のフォルダを作る。
1
| $ mkdir /var/www/html/acme-challenge/
|
また、NGINXの設定は次のような感じになる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| $ cat /etc/nginx/conf.d/my-site.conf
server {
listen 80;
server_name example.org www.example.org;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html/acme-challenge;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;
}
|
下のように指定した場合は、指定した全てのドメイン名に使用できる1枚のSSL/TLSサーバ証明書が発行される。
1
2
3
| $ certbot certonly --webroot \
-w /var/www/example/ -d www.example.com -d example.com \
-w /var/www/other -d other.example.net -d another.other.example.net
|
- renewalの設定ファイルは次のようになっている
authenticator
がwebroot
になっている
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| $ cat /etc/letsencrypt/renewal/example.org.conf
# renew_before_expiry = 30 days
version = 0.40.0
archive_dir = /etc/letsencrypt/archive/example.org
cert = /etc/letsencrypt/live/example.org/cert.pem
privkey = /etc/letsencrypt/live/example.org/privkey.pem
chain = /etc/letsencrypt/live/example.org/chain.pem
fullchain = /etc/letsencrypt/live/example.org/fullchain.pem
# Options used in the renewal process
[renewalparams]
account = cde5598f6f44f9f55646b9d23817ed48
authenticator = webroot
webroot-path = /var/www/html/.well-known/acme-challenge/
server = https://acme-v02.api.letsencrypt.org/directory
|
--standalone
- Webrootの自動版、チャレンジタイプはhttp-01となる
- ドメイン使用権者の認証の際には、Certbotクライアントに内蔵されている簡易的なウェブサーバが機能が一時的に使われる
- このプラグインは80や443のPortをListenしている動作中のウェブサーバを一時的に終了させる必要がある
実際のSSLの設定の例
前提
- DockerでNginxとCertbotを使う
- ドメインはApexドメインとwwwサブドメインを使う
- apexドメインはwwwサブドメインにリダイレクトする
- そのリダイレクトはNGINXでリバースプロキシで行う
- ドメイン名は仮にhoge.netにする
フォルダ構造
1
2
3
4
5
6
7
8
9
10
| $ tree -L 2
.
├── certbot
│ ├── conf/
│ └── www/
├── docker-compose.yaml
├── docker-compose.cert.yaml
├── nginx
│ ├── nginx_init.conf
│ └── nginx_ssl.conf
|
DNSの設定
- apexドメインはCNAMEが使えないのでAレコードを使う
- apwxドメインはAレコードでServerアドレスにする
- wwwサブドメインはCNAMEでServerのホスト名
1
2
3
4
5
6
7
| $ nslookup hoge.net
Server: 192.168.3.1
Address: 192.168.3.1#53
Non-authoritative answer:
Name: hoge.net
Address: 153.123.52.130
|
UFWの設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| $sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
4649 ALLOW Anywhere
22 DENY Anywhere
8000 ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
4649 (v6) ALLOW Anywhere (v6)
22 (v6) DENY Anywhere (v6)
8000 (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
|
DNSの確認
仮サーバーを起動する。
1
| $ sudo python3 -m http.server 80
|
アクセスしてDNSの疎通を確かめる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| $ curl www.hoge.net
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="certbot/">certbot/</a></li>
<li><a href="django/">django/</a></li>
<li><a href="docker-compose.yaml">docker-compose.yaml</a></li>
<li><a href="Makefile">Makefile</a></li>
<li><a href="nginx/">nginx/</a></li>
<li><a href="postgres/">postgres/</a></li>
</ul>
<hr>
</body>
</html>
|
nginxとcertbotで起動
docker-compose.cert.yaml
は以下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| services:
nginx:
image: nginx
volumes:
- ./nginx/nginx_init.conf:/etc/nginx/nginx.conf
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
ports:
- "80:80"
certbot:
image: certbot/certbot
depends_on:
- nginx
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
|
フォルダの関係配下。
- host:
certbot/www/
- certbot:
/var/www/certbot/'
- nginx:
/var/www/certbot/
- host:
certbot/conf/
- certbot:
/etc/letsencrypt/
- nginx:
/etc/letsencrypt/
- url:
/.well-known/acme-challenge/
nginx_init.conf
の中身は以下。
1
2
3
4
5
6
7
8
9
10
11
12
13
| events { }
http {
server {
listen 80;
server_name hoge.net www.hoge.net;
# Certbot challenge location
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
}
|
起動
1
2
| $ docker compose -f docker-compose.cert.yaml down --remove-orphans
$ docker compose -f docker-compose.cert.yaml up -d
|
プロセスの確認
1
2
3
| $ docker compose -f docker-compose.cert.yaml ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-nginx-1 docker-nginx "/docker-entrypoint.…" nginx 20 minutes ago Up 20 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp
|
外部からのアクセス確認
1
2
3
4
5
6
7
8
| $ curl www.hoge.net/.well-known/acme-challenge/
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.27.2</center>
</body>
</html>
|
空なので404でOK。
log
1
2
3
4
| $ docker compose -f docker-compose.cert.yaml logs
nginx-1 | 2024/11/28 16:26:20 [error] 28#28: *11 "/var/www/certbot/.well-known/acme-challenge/index.html" is not found (2: No such file or directory), client: 153.122.52.107, server: hoge.net, request: "GET /.well-known/acme-challenge/ HTTP/1.1", host: "www.hoge.net"
certbot-1 | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot-1 | Certbot doesn't know how to automatically configure the web server on this system. However, it can still get a certificate for you. Please run "certbot certonly" to do so. You'll need to manually configure your web server to use the resulting certificate.
|
アクセスログが残っているのでOK。
certの発行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| $ docker compose -f docker-compose.cert.yaml run --rm certbot certonly --webroot --webroot-path=/var/www/certbot \
-d hoge.net -d www.hoge.net \
--email support@hoge.net --agree-tos --no-eff-email
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for hoge.net and www.hoge.net
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/hoge.net/fullchain.pem
Key is saved at: /etc/letsencrypt/live/hoge.net/privkey.pem
This certificate expires on 2025-02-26.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
すると、Certが生成される(rootじゃないと見れないので注意)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| $ sudo tree -L 4 certbot/
certbot/
├── conf
│ ├── accounts
│ │ └── acme-v02.api.letsencrypt.org
│ │ └── directory
│ ├── archive
│ │ └── hoge.net
│ │ ├── cert1.pem
│ │ ├── chain1.pem
│ │ ├── fullchain1.pem
│ │ └── privkey1.pem
│ ├── live
│ │ ├── hoge.net
│ │ │ ├── cert.pem -> ../../archive/hoge.net/cert1.pem
│ │ │ ├── chain.pem -> ../../archive/hoge.net/chain1.pem
│ │ │ ├── fullchain.pem -> ../../archive/hoge.net/fullchain1.pem
│ │ │ ├── privkey.pem -> ../../archive/hoge.net/privkey1.pem
│ │ │ └── README
│ │ └── README
│ ├── renewal
│ │ └── hoge.net.conf
│ └── renewal-hooks
│ ├── deploy
│ ├── post
│ └── pre
├── Dockerfile
└── www
|
certがliveにsymbolic linkとしてあるのでOk。
初回用のdockerプロセスを終了する。
1
| $ docker compose -f docker-compose.cert.yaml down --remove-orphans
|
後処理の確認
1
2
| $ docker compose -f docker-compose.cert.yaml ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
|
SSLで起動
SSL用のnginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| events { }
http {
#############################################
# HTTP apex and www to https
#############################################
server {
listen 80;
server_name hoge.net www.hoge.net;
# Certbot challenge location
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect all other traffic to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
#############################################
# HTTPS apex to www redirect
#############################################
server {
listen 443 ssl;
server_name hoge.net;
ssl_certificate /etc/letsencrypt/live/hoge.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hoge.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
return 301 https://www.hoge.net$request_uri;
}
#############################################
# HTTPS www handling
#############################################
server {
listen 443 ssl;
server_name www.hoge.net;
ssl_certificate /etc/letsencrypt/live/hoge.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hoge.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
|
ssl用のdocker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| services:
nginx:
image: nginx
volumes:
- ./nginx/nginx_ssl.conf:/etc/nginx/nginx.conf
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
ports:
- "80:80"
- "443:443"
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
backend:
image: python:3
command: python -m http.server 8000
expose:
- "8000"
|
起動
1
2
| $ docker compose -f docker-compose.yaml down --remove-orphans
$ docker compose -f docker-compose.yaml up -d
|
ps確認
1
2
3
4
5
6
7
| $ make ps
docker compose -f docker-compose.yaml ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-certbot-1 docker-certbot "/bin/sh -c 'trap ex…" certbot 27 seconds ago Up 26 seconds 80/tcp, 443/tcp
docker-nginx-1 docker-nginx "/docker-entrypoint.…" nginx 27 seconds ago Up 25 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
docker-backend-1 python:3 "python -m http.serv…" backend 27 seconds ago Up 25 seconds 8000/tcp
|
アクセス確認。
- http => httpsへリダイレクト
- https + apexドメイン => https + wwwへリダイレクト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| $ curl http://hoge.net
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.27.2</center>
</body>
</html>
$ curl http://www.hoge.net
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.27.2</center>
</body>
</html>
$ curl https://hoge.net
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.27.2</center>
</body>
</html>
$ curl https://www.hoge.net
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".dockerenv">.dockerenv</a></li>
<li><a href="bin/">bin@</a></li>
<li><a href="boot/">boot/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="lib/">lib@</a></li>
<li><a href="lib64/">lib64@</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin@</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
</ul>
<hr>
</body>
</html>
|
OK
参考文献