OpenSSL の使い方練習: 単純な証明書チェーンを作る

[OpenSSL の使い方練習 - Qiita](https://qiita.com/propella/items/e8c2e25a51dce5e8bcbe) の説明が大げさすぎたので、もっと簡単に証明書チェーンを作る方法です。

ここでは、openssl でできるだけ単純な証明書チェーンを作ります。目標

* root.crt: ルート証明書
* middle.crt: 中間証明書
* leaf.crt: 下っ端の証明書。S/MIME だけに使える。

## ルート証明書を作る

まず、できるだけ簡単にオレオレルート証明書 (root.crt) と秘密鍵 (root.key) を作ります。

    openssl req -x509 -days 36500 -subj "/CN=Tama Root CA" -keyout root.key  > root.crt

* openssl req: 本来証明書要求 (CSR) を作成するコマンドですが、-x509 オプションを付けて一気にオレオレ証明書まで作っています。
* -x509: オレオレ証明書を作るオプションです。
* -days: -x509 と一緒に指定して有効期限を決められます。遊びなので 100 年にしてみました。
* -subj: Subject name を指定します。キーバリューペアで持ち主の情報を指定するもので、X.520 で規定されているそうです。CN だけ指定すれば見分けは付きます。
* -keyout: ついでに秘密鍵も作成します。

ルート証明書の中を確認します。

    openssl x509 -text -noout -purpose < root.crt

-purpose オプションでこの証明書の利用目的を確認出来ます。ほぼ Yes なので、これだけで簡単な実験には十分と思います。

## 中間証明書を作る

次に中間証明書用を作ります。試しに今度は設定ファイルを使ってみます。以下のような middle.conf を作ります。ファイルの説明は [openssl-req(1) CONFIGURATION-FILE-FORMAT](https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html#CONFIGURATION-FILE-FORMAT) にあります。

```conf
[req] # req コマンドではこのセクションを読む
prompt = no # 対話的に設定したくないなら no
distinguished_name = dn # prompt = no の時に subject として参照するセクション。この場合 [dn]

[dn] # ここに持ち主の情報を書きます。属性は X.520 で決まっているそうです。
C = AQ # 国名
L = Penguin Villege # 町名
O = Penguin Police # 組織名
CN = Penguin Middle CA # 名前や URL など
```

今度は真面目に証明書要求 (middle.csr) と秘密鍵 (middle.key) を作ります。

    openssl req -new -config middle.conf -keyout middle.key -nodes > middle.csr

* -new: 証明書要求を作るオプションです。
* -config: コマンドラインにあれこれ書くより設定ファイルを指定出来ます。指定したファイルの `[req]` セクションが使われます。
* -nodes: パスフレーズ無しで秘密鍵を生成したい時に使います。

証明書要求の中を確認します。

    openssl req -text -noout < middle.csr

実は、今回もう一つ設定ファイルが必要です。ルート証明書は openssl req -x509 で作ってしまったので自動生成されましたが、他の証明書を検証するには CA として有効になるように `basicConstraints=CA:TRUE` という制約設定が必要です。このように証明書の細かい機能を指定する物を x509 V3 extension といいます([/docs/man1.1.1/man5/x509v3\_config.html](https://www.openssl.org/docs/man1.1.1/man5/x509v3_config.html))。次のようなファイル middle-ext.conf を作ります。

```conf
basicConstraints=critical,CA:TRUE, pathlen:0
```

ちなにみ、この設定は後に証明書を検証する時にだけ使われます。CA:FALSE でも証明書の署名はできてしまうので注意。

証明書要求 (middle.csr) をルート証明書で署名して、中間証明書 (middle.crt) を作ります。extension の設定を `-extfile middle-ext.conf` で与えます。副産物としてシリアル番号ファイル (root.srl) が出来ます。

    openssl x509 -req -days 36500 -CA root.crt -CAkey root.key -CAcreateserial -extfile middle-ext.conf < middle.csr > middle.crt

* openssl x509: ルート証明書では省略しましたが、本来署名は openssl x509 コマンドで行います。
* -req: 入力として証明書要求を受け取ります。これがないと証明書を受け取るらしいです。
* -CAcreateserial: シリアル番号を記録するファイルを自動的に作ります。ルート証明書に .csr がついた名前になります。
* -CA: 署名する証明書です。
* -CAkey: 署名する証明書の秘密鍵です。

## 下っ端証明書を作る

最後に下っ端証明書を作ります。同じように証明書要求 (leaf.csr) と秘密鍵 (leaf.key) を作ります。今度は再び -subj で Subject name を指定してみます。

    openssl req -new -subj "/L=Musashi/CN=kodaira.example.com" -keyout leaf.key -nodes > leaf.csr 

S/MIME 専用の証明書にするには、やはり x509 V3 extension を使います。leaf-ext.conf を作ります。

```conf
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = emailProtection
```

ここが一番難しく、この記述の組み合わせで証明書の用途を制限します。例えば S/MIME のためには [x509(1)](https://www.openssl.org/docs/man1.1.1/man1/x509.html) の S/MIME Signing, Encryption の条件を満たす必要があります。歴史的事情ですかね。

* S/MIME Signing には Common S/MIME client tests が有効で keyUsage の digitalSignature が必要。
* S/MIME Encryption には Common S/MIME client tests が有効で keyUsage の keyEncipherment が必要。
* Common S/MIME client test には extendedKeyUsage の emailProtection が必要。

いよいよ下っ端証明書要求 (leaf.csr) を中間証明書で署名して、下っ端証明書 (leaf.crt) を作ります。S/MIME 専用にするために extension を `-extfile leaf-ext.conf` で与えます。副産物としてシリアル番号ファイル (middle.srl) が出来ます。

    openssl x509 -req -days 36500 -CA middle.crt -CAkey middle.key -CAcreateserial -extfile leaf-ext.conf < leaf.csr > leaf.crt


下っ端証明書の中を確認します。`-purpose` で S/MIME に使えるか確認します。

    openssl x509 -text -noout -purpose < leaf.crt
    ...
    S/MIME signing : Yes

## 確認する。

中間証明書の検証には openssl verify コマンドを使います。

```shell
$ openssl verify -show_chain -x509_strict -CAfile root.crt middle.crt 
middle.crt: OK
Chain:
depth=0: C = AQ, L = Penguin Villege, O = Penguin Police, CN = Penguin Middle CA (untrusted)
depth=1: CN = Tama Root CA
```

最後に下っ端証明書の検証をしたいのですが、どうやら openssh verify コマンドで証明局を指定する -CAfile オプションが一つしか指定出来ないらしく、middle.crt と root.crt を同時に指定出来ません。そのため -CApath オプションを使う必要があるのですが、このオプションを使うには証明書のファイル名を c_rehash で特殊な形式に変換(シンボリックリンク)する必要があります。

```shell
$ c_rehash .
$ ls -l *.0
lrwxrwxrwx 1 tyamamiya tyamamiya 10 Dec 22 11:44 13c5cc8a.0 -> middle.crt
lrwxrwxrwx 1 tyamamiya tyamamiya  8 Dec 22 11:44 253caadf.0 -> root.crt
lrwxrwxrwx 1 tyamamiya tyamamiya  8 Dec 22 11:44 63d9aef2.0 -> root.crt
lrwxrwxrwx 1 tyamamiya tyamamiya  8 Dec 22 11:44 68c2b9ae.0 -> leaf.crt
lrwxrwxrwx 1 tyamamiya tyamamiya 10 Dec 22 11:44 d349d568.0 -> middle.crt
lrwxrwxrwx 1 tyamamiya tyamamiya  8 Dec 22 11:44 f97b0df8.0 -> leaf.crt

$ openssl verify -show_chain -CApath . leaf.crt
leaf.crt: OK
Chain:
depth=0: L = Musashi, CN = kodaira.example.com (untrusted)
depth=1: C = AQ, L = Penguin Villege, O = Penguin Police, CN = Penguin Middle CA
depth=2: CN = Tama Root CA
```

ここで `invalid CA certificate` のような意味不明のエラーが出たら https://www.openssl.org/docs/man1.1.1/man1/verify.html#DIAGNOSTICS を読むと良いです。


## 参考

* [OpenSSL Cookbook 3rd Edition - 1.2.4  Unattended CSR Generation](https://www.feistyduck.com/library/openssl-cookbook/online/openssl-command-line/unattended-csr-generation.html)
* [/docs/man1.1.1/man1/openssl-req.html](https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html)
* [/docs/man1.1.1/man1/x509.html](https://www.openssl.org/docs/man1.1.1/man1/x509.html)
* [/docs/man1.1.1/man5/x509v3\_config.html](https://www.openssl.org/docs/man1.1.1/man5/x509v3_config.html)
* [/docs/man1.1.1/man1/verify.html](https://www.openssl.org/docs/man1.1.1/man1/verify.html)
* [/docs/man1.1.1/man1/c\_rehash.html](https://www.openssl.org/docs/man1.1.1/man1/c_rehash.html)