今度こそopensslコマンドを理解して使いたい (3) CA証明書の拡張設定を検証する

前回まで:
第1回: [今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する](https://qiita.com/3244/items/780469306a3c3051c9fe)
第2回:. [今度こそopensslコマンドを理解して使いたい (2) 設定ファイル（openssl.cnf）を理解する](https://qiita.com/3244/items/8c7e6892efdb4e500be9)

第1回で作成したルートCAを使ってサーバー／クライアント証明書を作成したいところですが、今回はその前に以下の準備を行います。

1. ルートCAの作成方法と拡張設定を見直す
2. 拡張設定を検証して詳しく調べる

# ルートCA作成方法の見直し
前回調べた設定ファイルの内容を踏まえて、第1回で作成したルートCAの作り方を見直します。

## 設定ファイル（openssl.cnf）
第1回でルートCA証明書の自己署名に使用した拡張セクションは、ノーマルの`openssl.cnf`の`[ v3_ca ]`です。
[→ 「拡張セクションを指定する」](https://qiita.com/3244/items/780469306a3c3051c9fe#%E6%8B%A1%E5%BC%B5%E3%82%BB%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B)

ノーマルの`openssl.cnf`では、拡張セクション`[ v3_ca ]`内の有効な行は以下の3行だけです。

```Ini:/etc/pki/tls/openssl.cnf
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = CA:true
```

前回調べたとおり、拡張セクションでは鍵の用途を明示的に設定することができます。
[→ 「Key Usage（鍵の用途）」](https://qiita.com/3244/items/8c7e6892efdb4e500be9#key-usage%E9%8D%B5%E3%81%AE%E7%94%A8%E9%80%94)

今回はCA用の鍵の用途として、以下の1行を拡張セクション`[ v3_ca ]`に追加します。と言ってもコメントアウトされている既存の行をアンコメントするだけです。

```Ini:
keyUsage = cRLSign, keyCertSign
```
この行を追加したことによる効果は、後ほど検証します。

## シリアル番号
第1回では、CA証明書のシリアル番号を`req`コマンドの`-set_serial`オプションで指定していました。
[→ 「証明書の自己署名を同時に行う」](https://qiita.com/3244/items/780469306a3c3051c9fe#%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E8%87%AA%E5%B7%B1%E7%BD%B2%E5%90%8D%E3%82%92%E5%90%8C%E6%99%82%E3%81%AB%E8%A1%8C%E3%81%86)

しかしよく考えたらルートCAの証明書をCRL（失効リスト）で無効化することはあり得ないので、そもそもシリアル番号を管理する必要がありませんでした。そのため今回はシリアル番号の指定を省略して、自動的に大きな乱数が設定されるようにします。

## スクリプトの変更
第1回で作成したスクリプトを以下のように変更しました。

作成するCAの情報を定義するファイル`ca-list`から、シリアル番号の列を削除してコモンネームだけにします。

```:ca-list
ca1.mydomain
ca2.mydomain
```

CAの秘密鍵に設定するパスフレーズは、第1回と同じくファイル`.capass`に記述します。

```:.capass
mycap@ss
```

スクリプトの`req`コマンドから`-set_serial`オプションを削除します。前回と同じく設定ファイルを指定しないので、自動的に`/etc/pki/tls/openssl.cnf`が使用されます。

```Bash:newca.sh
CATOP=$(dirname "${0}")

# 作成するCAのコモンネームをリストから1件ずつ読み込む
while read ca_name
do
  # CAのディレクトリ、証明書、秘密鍵のパス
  ca_dir="${CATOP}/${ca_name}"
  ca_cert="${ca_dir}/cacert.pem"
  ca_key="${ca_dir}/private/cakey.pem"

  # すでに証明書と秘密鍵が作成済みの場合はスキップ
  [[ -e "${ca_cert}" && -e "${ca_key}" ]] && continue

  # CA用のディレクトリを作成する
  mkdir -p "$(dirname "${ca_cert}")"
  mkdir -p "$(dirname "${ca_key}")"

  # 秘密鍵を生成して自己署名証明書を発行する
  # -new        新しい証明書要求を生成する
  # -keyout     指定のパスで秘密鍵を出力する
  # -out        指定のパスに出力する（-x509オプションを指定した場合は自己署名証明書）
  # -passout    出力する秘密鍵に設定するパスフレーズを指定する
  # -subj       識別名を"/type0=value0/type1=value1/type2=..."の形式で指定する
  # -x509       署名要求を出力する代わりに自己署名証明書を出力する
  # -days       証明書の有効日数を指定する
  # -extensions 設定ファイルの拡張セクション名を指定する
  openssl req \
      -new \
      -keyout   "${ca_key}" \
      -out      "${ca_cert}" \
      -passout  "file:${CATOP}/.capass" \
      -subj     "/C=JP/ST=Chiba/O=myorg/CN=${ca_name}" \
      -x509 -days 1095 -extensions v3_ca

  # 秘密鍵のパーミッションを設定する
  chmod 0400 "${ca_key}"
done <"${CATOP}/ca-list"
```

スクリプトを実行すると、以下のようにCAごとのサブディレクトリに秘密鍵と証明書が出力されます。

```
.
├── ca1.mydomain
│   ├── cacert.pem
│   └── private
│       └── cakey.pem
├── ca2.mydomain
│   ├── cacert.pem
│   └── private
│       └── cakey.pem
```

## 証明書の内容を確認する
以下のように`x509`コマンドを使って証明書の内容を確認します。

```Shell:
openssl x509 -in ca1.mydomain/cacert.pem -text
```

[第1回で作成した証明書](https://qiita.com/3244/items/780469306a3c3051c9fe#%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E5%86%85%E5%AE%B9%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B)との違いとして、シリアル番号がランダムな数値になったことと、拡張設定`keyUsage`の追加が`X509v3 Key Usage: Certificate Sign, CRL Sign`に反映されたことが確認できます。

```Shell:
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            90:e8:58:56:83:91:bc:1f
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, ST=Chiba, O=myorg, CN=ca1.mydomain
        Validity
            Not Before: Aug  8 08:48:19 2019 GMT
            Not After : Aug  7 08:48:19 2022 GMT
        Subject: C=JP, ST=Chiba, O=myorg, CN=ca1.mydomain
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
（略）
            X509v3 Basic Constraints:
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
（略）
```

# 証明書の拡張設定を検証する
次に、拡張設定を追加した結果を検証します。

## 作成した証明書の用途をコマンドで検証する
今回作成した証明書の用途がどのように設定されたかは、以下のコマンドで検証することができます。

```Shell:
openssl x509 -in ca1.mydomain/cacert.pem -purpose -noout
```

```Shell:
Certificate purposes:
SSL client : No
SSL client CA : Yes
SSL server : No
SSL server CA : Yes
Netscape SSL server : No
Netscape SSL server CA : Yes
S/MIME signing : No
S/MIME signing CA : Yes
S/MIME encryption : No
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes
Time Stamp signing : No
Time Stamp signing CA : Yes
```

比較のために、[第1回で作成したCA証明書](https://qiita.com/3244/items/780469306a3c3051c9fe#%E4%BD%9C%E6%88%90%E3%81%97%E3%81%9F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%A7ca%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)の検証結果を以下に貼り付けます。

設定の違いは、拡張設定に鍵の用途`keyUsage = cRLSign, keyCertSign`がないことで、基本制約`basicConstraints = CA:true`は同じです。

```
Certificate purposes:
SSL client : Yes
SSL client CA : Yes
SSL server : Yes
SSL server CA : Yes
Netscape SSL server : Yes
Netscape SSL server CA : Yes
S/MIME signing : Yes
S/MIME signing CA : Yes
S/MIME encryption : Yes
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes
Time Stamp signing : No
Time Stamp signing CA : Yes
```

どちらの証明書もSSLクライアント／サーバー用CA、各種の署名用CAとして使用できることが確認できますが、今回作成した証明書（上: keyUsageあり）はその他の用途では使用できないのに対して、第1回で作成した証明書（下: keyUsageなし）は他の用途でも使用できることが分かります。

つまりCA用として使えるようにするための拡張設定は基本制約`basicConstraints = CA:true`で、鍵の用途`keyUsage = cRLSign, keyCertSign`を追加する意味は、**他の用途で使えなくすること**でした。

## 証明書の拡張設定をドキュメントで確認する
前回設定ファイルの公式ドキュメントを読んでも詳しい説明が見つからなかった部分が`x509`コマンドのドキュメントで見つかったので、以下に抜粋（＋和訳）して引用します。

* `x509`コマンド manpage: https://www.openssl.org/docs/man1.0.2/man1/x509.html

> 証明書の拡張設定
`-purpose`オプションは、証明書の拡張設定を確認して証明書の用途を判定する。実際に行われる確認はかなり複雑で、破損した証明書とソフトウェアを処理するためのさまざまなハックや回避策が含まれる。

> 同じコードが証明書チェーン内の信頼できない証明書の検証にも使用されるので、この節の内容は証明書チェーンが検証コードにリジェクトされた場合にも役立つ。

> 拡張設定`basicConstraints`（基本制約）の`CA`フラグは、証明書がCAとして使用できるかを決定し、値がtrueであればCAであり、falseであればCAではない。すべてのCAは`CA`フラグがtrueでなければならない。

> `basicConstraints`がない証明書は「CAの可能性あり」とされ、意図された証明書の用途にしたがって他の拡張設定がチェックされる。そのような証明書は本当はCAと見なされるべきではないので警告が発せられるが、いくつかの壊れたソフトウェアでの動作のためにCAとして認められている。

参考: [前回の「Basic Constraints（基本制約）」](https://qiita.com/3244/items/8c7e6892efdb4e500be9#basic-constraints%E5%9F%BA%E6%9C%AC%E5%88%B6%E7%B4%84)

> 拡張設定`keyUsage`（鍵の用途）を追加することで、証明書の用途を制限することができる。CA証明書に拡張設定`keyUsage`がある場合は、必ず`keyCertSign`ビットがセットされなければならない。

今回`x509`コマンドで調べた結果のとおり、拡張設定`keyUsage`は「証明書の用途を制限する」と明記されていました。

参考: [前回の「Key Usage（鍵の用途）」](https://qiita.com/3244/items/8c7e6892efdb4e500be9#key-usage%E9%8D%B5%E3%81%AE%E7%94%A8%E9%80%94)

> Extended Key Usage（鍵の用途の拡張）は、証明書の使用を制限する。この拡張機能が存在する場合（`critical`の有無を問わず）、鍵は指定された目的にしか使用できなくなる。

こちらも「証明書の使用を制限する」と書かれていますが、CA証明書以外ではこの設定がないと目的の用途に使用できない場合があるようです。

参考: [前回の「Extended Key Usage（鍵の用途の拡張）」](https://qiita.com/3244/items/8c7e6892efdb4e500be9#extended-key-usage%E9%8D%B5%E3%81%AE%E7%94%A8%E9%80%94%E3%81%AE%E6%8B%A1%E5%BC%B5)

> それぞれのテストの説明は下記のとおりで、上記の`basicConstraints`と`keyUsage`に関するコメントはすべてのCA証明書に適用される。

> **SSL Client**
Extended Key Usageは存在しないか、または "web client authentication" OIDが含まれること。
keyUsageは存在しないか、または`digitalSignature`ビットが設定されること。
Netscape証明書タイプは存在しないか、またはSSLクライアントのビットが設定されること。

> **SSL Client CA**
Extended Key Usageは存在しないか、または "web client authentication" OIDが含まれること。
Netscape証明書タイプは存在しないか、またはSSL CAのビットが設定されること（`basicConstraints`が存在しない場合の措置として使われる）。

> **SSL Server**
Extended Key Usageは存在しないか、または "web server authentication"および/またはSGC OIDの1つが含まれること。
keyUsageは存在しないか、または`digitalSignature`、`keyEncipherment`のセットまたは両方のビットが設定されること。
Netscape証明書タイプは存在しないか、またはSSLサーバーのビットが設定されること。

> **SSL Server CA**
Extended Key Usageは存在しないか、または "web server authentication"および/またはSGC OIDの1つが含まれること。
Netscape証明書タイプは存在しないか、またはSSL CAのビットが設定されること（`basicConstraints`が存在しない場合の措置として使われる）。

> **Netscape SSL Server**
keyUsageがある場合は、SSLサーバーに接続するネットスケープSSLクライアントのためには`keyEncipherment`ビットが必要。
ただし一部の暗号スイートはデジタル署名に鍵を使用するため、これが常に有効とは限らない。それ以外は通常のSSLサーバーと同じ。

> **共通S/MIMEクライアントテスト**
Extended Key Usageは存在しないか、または "email protection" OIDが含まれること。
Netscape証明書タイプは存在しないか、またはS/MIMEビットのセットが必要。Netscape証明書タイプにS/MIMEビットが設定されない場合、SSLクライアントのビットが代替として許容されるが、警告が表示される（一部のVerisign証明書がS/MIMEビットを設定しないため）。

> **S/MIME Signing**
共通S/MIMEクライアントテストに加えて、keyUsageに`digitalSignature`ビットが設定されること。

> **S/MIME Encryption**
共通S/MIMEクライアントテストに加えて、keyUsageが存在する場合は`keyEncipherment`ビットが設定されること。

> **S/MIME CA**
Extended Key Usageは存在しないか、または "email protection" OIDが含まれること。
Netscape証明書タイプは存在しないか、またはS/MIME CAビットのセットが必要（`basicConstraints`が存在しない場合の措置として使われる）。

> **CRL Signing**
keyUsageは存在しないか、またはCRL署名のビットが設定されること。

> **CRL Signing CA**
通常のCAテストが適用される。この場合を除き、`basicConstraints`拡張機能が必要。

以上の調査結果を元に、次回以降でサーバー／クライアント証明書を発行してOpenVPNでの動作を確認する予定です。

# 今後の予定

* サーバー／クライアント証明書を発行してOpenVPNでの動作を確認
* 証明書失効処理

# 記事一覧
[今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する](https://qiita.com/3244/items/780469306a3c3051c9fe)
[今度こそopensslコマンドを理解して使いたい (2) 設定ファイル（openssl.cnf）を理解する](https://qiita.com/3244/items/8c7e6892efdb4e500be9)
今度こそopensslコマンドを理解して使いたい (3) CA証明書の拡張設定を検証する
[今度こそopensslコマンドを理解して使いたい (4) サーバー／クライアント証明書を一括生成する](https://qiita.com/3244/items/2618429ebe6dc16c074e)
[今度こそopensslコマンドを理解して使いたい (5) CRL（証明書失効リスト）を作成してOpenVPNに配布する](https://qiita.com/3244/items/21c2cb009de53db03f0e)
[今度こそopensslコマンドを理解して使いたい (補足1) サンプルスクリプトのまとめ](https://qiita.com/3244/items/65dd3926fab76b9dd4aa)