今度こそopensslコマンドを理解して使いたい (2) 設定ファイル（openssl.cnf）を理解する

前回: [今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する](https://qiita.com/3244/items/780469306a3c3051c9fe)

前回はスクリプトでルートCAを作成する際に、識別名などの重要な設定値をコマンドラインで指定しましたが、それ以外はノーマルの設定ファイル（openssl.cnf）の値をそのまま使用していました。

今回はOpenSSLの設定ファイルを理解して、適切な設定ファイルを作れるようになることを目指します。

![openssl.cnf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169872/b37a11c2-45c7-1b47-3641-844484e26af7.png)

# 設定ファイルの構成
以降の文書中の引用箇所は、以下のOpenSSL公式ドキュメントからの抜粋＋和訳です。

* 設定ファイル manpage（1.0.2）: https://www.openssl.org/docs/man1.0.2/man5/config.html

## デフォルトセクション
> 設定ファイルの先頭から最初の名前付きセクションの前までを「デフォルトセクション」と言う。

ノーマルの`openssl.cnf`では、ファイル先頭部分にある以下の3行が「デフォルトセクション」の内容になります。

```Ini:/etc/pki/tls/openssl.cnf
HOME			= .
RANDFILE		= $ENV::HOME/.rnd
oid_section		= new_oids
```

## 名前付きセクション
コマンド（`openssl`のサブコマンド）がどの名前付きセクションを参照するかは、各コマンドのドキュメントに明記されています。例えば`ca`コマンドは`[ ca ]`セクションを、`req`コマンドは`[ req ]`セクションを参照し、それらの中で別のセクションが指定されていればそちらも参照します。

* `ca`コマンド manpage（1.0.2）: https://www.openssl.org/docs/man1.0.2/man1/openssl-ca.html
* `req`コマンド manpage（1.0.2）: https://www.openssl.org/docs/man1.0.2/man1/openssl-req.html

以下はノーマルの`openssl.cnf`で、`ca`コマンドと`req`コマンドが参照するセクションです。

```
caコマンドを実行
├──[ ca ]
│   └──[ CA_default ]
│       ├──[ usr_cert ]
│       └──[ policy_match ]
└── デフォルトセクション
    └──[ new_oids ]
```

```
reqコマンドを実行
├──[ req ]
│   ├──[ req_distinguished_name ]
│   ├──[ req_attributes ]
│   └──[ v3_ca ]
└── デフォルトセクション
    └──[ new_oids ]
```

上記はあくまでもノーマルの`openssl.cnf`を使用した場合で、以下のような方法で自由にカスタマイズすることができます。

* 名前付きセクションを任意の名前で作ることができる

* 設定ファイル内の参照先セクションを変更することができる（例: `[ ca ]`セクション内の`default_ca =`）

* コマンドラインオプションで参照先セクションを指定することができる
    * 設定ファイルの定義をオーバーライドできる
    * コマンドによって、設定ファイルのどの定義をオーバーライドできるかが異なる（例: `ca`コマンドの`-name`オプションは設定ファイルの`default_ca`をオーバーライドできる）

## デフォルトセクションの値

名前付きセクションとデフォルトセクションに同じ名前の設定があった場合について、以下のように規定されています。
> 設定が参照される時、まず名前付きセクションが調べられ、**見つからなければ**デフォルトセクションが調べられる。

例えば乱数ファイル`RANDFILE`は、デフォルトセクションで以下のように定義されています。

```Ini:
RANDFILE = $ENV::HOME/.rnd
```

しかし`[ CA_default ]`セクションでは以下の設定値が定義されているので、このセクションが参照された場合は下記の設定値が使用され、上記のデフォルト値は無視されます。

```Ini:
[ CA_default ]
RANDFILE = $dir/private/.rand
```

## 環境変数
> `$ENV::name`の書式を使うことで、環境変数を置き換えることがでる。

`RANDFILE = $ENV::HOME/.rnd`という設定は、`$ENV::HOME`の部分が環境変数`$HOME`に置き換えられるので、`RANDFILE`のパスは`$HOME/.rnd`になります。

この書式を利用すれば、設定ファイル中の任意の設定値を外部から注入することができます。

例えばCA（認証局）のパス`dir`の値は、以下のように`[ CA_default ]`セクションの中でハードコーディングされています。

```Ini:/etc/pki/tls/openssl.cnf
[ CA_default ]
dir       = /etc/pki/CA		# 全体の保存場所
certs     = $dir/certs		# 発行した証明書をどこに保存するか
```

この`dir`の値を環境変数`CA_DIR`を参照するように変更すれば、1つの設定ファイルで複数のパスを使用できるようになります。

```Ini:
[ CA_default ]
dir       = $ENV::CA_DIR	# 全体の保存場所（環境変数）
certs     = $dir/certs		# 発行した証明書をどこに保存するか
```

以下は別々のCAのパスを`CA_DIR`に設定してから`ca`コマンドを実行する例です。

```Shell:
export CA_DIR="/etc/pki/CA/ca1.mydomain"
openssl ca ...

export CA_DIR="/etc/pki/CA/ca2.mydomain"
openssl ca ...
```

## 環境変数の安全な使い方

> 設定値に含めた環境変数が存在しないと、設定ファイルを読み込もうとした時にエラーが発生する。

上記の場合、以下のようにデフォルトセクション内にデフォルト値をハードコーディングしておけば、環境変数`CA_DIR`がなければデフォルト値が使用されるので、エラーの発生を回避することができます。

```Ini:/etc/pki/tls/openssl.cnf
CA_DIR    = /etc/pki/CA		# 全体の保存場所（環境変数がない場合のデフォルト値）

[ CA_default ]
dir       = $ENV::CA_DIR	# 全体の保存場所（環境変数）
certs     = $dir/certs		# 発行した証明書をどこに保存するか
```

## その他の書式
全般的な書式に関するトピックを以下に抜粋します。

### 使用可能文字
> セクション名には英数字とアンダースコアを使用できる。

> `name=value`の`name`には、英数字と`.` `,` `;` `_`などいくつかの記号を使用できる。

### 変数名の重複
> 同一セクション内に同じ名前の変数がある場合、最後の値以外は無視される。
> 識別名のように同じフィールドが複数回定義されるような特殊な場合、通常は名前の先頭から`.`までを無視することで回避している。以下の例はこの方法でフィールド`OU`を2つ定義している。
1.OU="My first OU"
2.OU="My Second OU"

ノーマルの`openssl.cnf`では以下の箇所がこれに相当します。

```Ini:/etc/pki/tls/openssl.cnf
0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= Default Company Ltd

# we can do this but it is not needed normally :-)
#1.organizationName		= Second Organization Name (eg, company)
#1.organizationName_default	= World Wide Web Pty Ltd
```

### 変数展開
> `$var`または`${var}`の書式を含む値の文字列は変数展開され、同一セクション内の変数値で置き換えられる。また`$section::name`か`${section::name}`の書式を使えば、他のセクションの変数値で置き換えることもできる。

### 環境変数の出力
> `ENV::name`の書式を使うことで、値を環境変数に割り当てることもできる。

これは`$ENV::name`の逆で、設定ファイルの値を環境変数に出力することもできるようです。

### エスケープ文字
> 引用符か`\`を使って特殊文字をエスケープすることができる。
行末を`\`にすれば複数行をまたぐことができる。
特殊文字`\n` `\r` `\b` `\t`が使用できる。

# X509 V3 拡張設定
以降の文書中の引用箇所は、以下のOpenSSL公式ドキュメントからの抜粋＋和訳です。

* X509 V3 拡張設定 manpage（1.0.2）: https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html

## critical
> 拡張セクション内の各行は以下の書式を取る:
extension_name=[critical,] extension_options
`critical`があればその拡張設定は重要である。

`critical`はプログラムが設定内容を厳守するように指定しますが、`critical`がない場合の動作は各プログラムの実装に依存するようです。
厳密な運用を目指すなら積極的に指定するべきオプションですが、設定ファイルの`[ v3_ca ]`セクションに以下のコメントが書かれているように、プログラムによっては正常動作を妨げる場合もあるようです。

```Ini:/etc/pki/tls/openssl.cnf
# PKIXはこれを推奨しているが、いくつかの壊れたソフトウェアが「critical」拡張設定で固まってしまう。
basicConstraints = critical,CA:true
# だから私たちは代わりにこうする。
basicConstraints = CA:true
```

## Basic Constraints（基本制約）
> これは証明書がCA証明書かどうかを示す多値の拡張設定で、最初に`CA:{TRUE|FALSE}`の設定が必須である。`CA:TRUE`の場合、`pathlen`に正の数を設定することができる。
例:
basicConstraints=CA:TRUE
basicConstraints=CA:FALSE
basicConstraints=critical,CA:TRUE,pathlen:0

> CA証明書には`CA:TRUE`の値を設定した`basicConstraints`が必須である。
エンドユーザー証明書は`CA:FALSE`とするか、またはこの拡張設定を完全に除外する必要があるが、中にはエンドエンティティ証明書に`CA:FALSE`の`basicConstraints`を含むことを要求するソフトウェアもある。

> `pathlen`は証明書チェーン内でこのCAに連なることができるCAの最大数を示す。したがって、`pathlen:0`のCAはエンドユーザー証明書への署名しかできず、他のCAに署名することはできない。

CA証明書には`basicConstraints=CA:TRUE`が必要で、中間認証局を作る可能性があるなら上位CAの証明書に`pathlen`オプションも必要です。

サーバー／クライアント証明書には`basicConstraints`が必須ではないものの、上記の説明を読むと互換正のために`basicConstraints=CA:FALSE`を入れておいた方が無難なようです。

## Key Usage（鍵の用途）
> これは許可される鍵の使い方の配列を設定する多値の拡張設定である。
設定できる値: `digitalSignature` `nonRepudiation` `keyEncipherment` `dataEncipherment` `keyAgreement` `keyCertSign` `cRLSign` `encipherOnly` `decipherOnly`
例:
keyUsage=digitalSignature, nonRepudiation
keyUsage=critical, keyCertSign

ドキュメントには詳細が記されていませんが、ノーマルの`openssl.cnf`には典型的な例として以下の記述があります。

* クライアント証明書用（[ usr_cert ]）：
    * keyUsage = nonRepudiation, digitalSignature, keyEncipherment
* 証明書署名要求用（[ v3_req ]）：
    * keyUsage = nonRepudiation, digitalSignature, keyEncipherment
* CA証明書用（[ v3_ca ]）：
    * keyUsage = cRLSign, keyCertSign

## Extended Key Usage（鍵の用途の拡張）
> この拡張設定は、証明書の公開キーの用途を示す使用法（オブジェクトの短縮名かOUDのドット区切り数値）の配列で構成されている。以下のPKIX、NSおよびMSの値は特に意味がある。

```
 値                     意味
 -----                  -------
 serverAuth             SSL/TLS Webサーバ認証
 clientAuth             SSL/TLS Webクライアント認証
 codeSigning            コード署名
 emailProtection        Eメール保護 (S/MIME)
 timeStamping           Trusted Timestamping
 msCodeInd              Microsoft Individual Code Signing (authenticode)
 msCodeCom              Microsoft Commercial Code Signing (authenticode)
 msCTLSign              Microsoft Trust List Signing
 msSGC                  Microsoft Server Gated Crypto
 msEFS                  Microsoft Encrypted File System
 nsSGC                  Netscape Server Gated Crypto
```
> 例:
extendedKeyUsage=critical,codeSigning,1.2.3.4
extndedKeyUsage=nsSGC,msSGC

OpenVPNのサーバー／クライアントで試したところ、サーバー側の証明書に`extndedKeyUsage = serverAuth`がないと、クライアント側から接続することができませんでした。

## Subject Key Identifier（サブジェクト鍵識別子）
サブジェクト鍵識別子: 証明書の所有者の公開鍵を識別する値（ハッシュ値）

> 文字列値で`hash`を指定すれば、RFC3280のガイドラインに自動的に従う。これ以外の設定は推奨されない。
例:
subjectKeyIdentifier=hash

## Authority Key Identifier（認証局鍵識別子）
認証局鍵識別子: 証明書に署名したCAの公開鍵を識別する値（ハッシュ値）

> `keyid`と`issuer`の2つのオプションが使用可能で、どちらもオプション値`always`を取ることができる。
`keyid`オプションがある場合、サブジェクト鍵識別子を親証明書からコピーしようとする。 値`always`がある場合、オプションが失敗するとエラーが返される。
`issuer`オプションは、発行者の証明書から発行者とシリアル番号をコピーする。これは`always`フラグが常に値を含まない限り、`keyid`オプションが失敗するか含まれない場合にのみ行われる。
例:
authorityKeyIdentifier=keyid,issuer

## 非推奨の拡張設定
> これらはNetscape用の非標準設定で、すでにほぼ廃止されている。

### Netscape文字列拡張
> 例:
`nsComment` = "Some Random Comment"
このカテゴリの他の拡張設定: `nsBaseUrl` `nsRevocationUrl` `nsCaRevocationUrl` `nsRenewalUrl` `nsCaPolicyUrl` `nsSslServerName`

### Netscape証明書タイプ
> これは証明書の使用目的を示すフラグの配列を設定する多値の拡張設定だが、現在はこれに代わって`basicConstraints`、`keyUsage`および`extendedKeyUsage`の拡張設定が使用されている。
`nsCertType`に使用可能な値: `client` `server` `email` `objsign` `reserved` `sslCA` `emailCA` `objCA`

これらの設定を使用する例がよく紹介されていますが、上記の説明を読む限り、よほど古いソフトウェアを使わない限り必要なさそうです。

# 今後の予定

* 今回の内容を踏まえ、設定ファイルと前回作成したスクリプトを見直してルートCAを再作成する

* 作成したCAでサーバー／クライアント証明書を発行し、OpenVPNでの動作を確認する
    * OpenVPN コミュニティ Wiki: https://community.openvpn.net/openvpn

* 証明書失効処理

# 記事一覧
[今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する](https://qiita.com/3244/items/780469306a3c3051c9fe)
今度こそopensslコマンドを理解して使いたい (2) 設定ファイル（openssl.cnf）を理解する
[今度こそopensslコマンドを理解して使いたい (3) CA証明書の拡張設定を検証する](https://qiita.com/3244/items/2a2a2dc6cd1e2e35beb9)
[今度こそ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)