オレだよオレオレ認証局で証明書つくる

- *2017.05.08 Chrome58でエラーになる問題追記*

Webサービスの開発するにあたって、開発検証を行うサーバにもSSL証明書が必要になることが多々ある。
限られたメンバーがアクセスするだけの環境に、お金を出してSSL証明書を買うのもねぇ。。。
ってな時に自己証明書、つまりオレオレ証明書を作って利用する場面があるわけだけど。
毎度毎度やり方忘れてぐぐってると、色んなやり方があって、どれがいいの？って悩む。
しかも、今まで意味も分からず動けばいーや的な感じだったので、今回はちゃんと考えた。

てことで、オレオレ証明書でオレオレ認証局を作って、このオレオレ認証局で署名したサーバー証明書を作る方式にする。
そうすれば、オレオレ認証局の証明書だけを信頼できるルート証明書にすることで、発行したすべてのサーバー証明書が信頼性あるものになる。
ブラウザーで緑の鍵マークになるって寸法よ。

# 鍵の種類
拡張子が色々あって混乱するので、まずは一覧。

拡張子 | ざっくりした内容
--- | ---
.pem | rsa暗号方式をで暗号化されたファイルを現したもので、鍵の種類を表しているわけではない
.key | もれたら危険が危ない秘密鍵
.csr | 証明書発行要求のファイル
.crt | 認証局が署名した証明書のファイル

ほうほう。.pemは鍵の種類ではなく暗号方式を表しているから、rsa方式で暗号化された秘密鍵も証明書も同じ拡張子になってる場合があるのか。
この辺は好みの問題なのか？そこはようわからん。

# オレオレ認証局開設
世の中で見られるオレオレ証明書の作り方って、root認証局の証明書だった。
root認証局とは証明書の署名をしてくれているラスボス。ゾーマだよ。
ゾーマは子分であるバラモスが本物だって署名してくれている。
バラモスは子分である・・・と階層構造で署名がたどれる。
じゃぁゾーマを署名しているのは？
それはゾーマ自身が自分の証明書に署名している。これが自己証明書だ。
この自己証明書が本物だって登録がOSにある。

## 前提条件
今回はCentOS6のyumインストールで入ってるopensslでの記録。
コマンドパスとかファイルの場所とか違っても基本やることは一緒。
Macの場合は純正インストールのopensslでは古いので、brewでインストールしたopensslを使用したほうがよい。

## 環境設定
作業するにあたってopensslの設定を前もって変更しておく。
初期値とかもできるので先に入れておく。

```ini:/etc/pki/tls/openssl.cnf
# 75行目の項目でハッシュ方式をsha256に変更
default_md = sha256

# 107行目の[req]セクション内もハッシュ方式を変更
default_md = sha256

# [req_distinguished_name]項目で毎度入れる内容の初期値を入れておく
countryName_default = JP #日本
stateOrProvinceName_default = Tokyo #都道府県
localityName_default = Shibuya #市区町村
0.organizationName_default = OredayoOre inc. #組織名
```

認証局で使用するファイルのパスはそのままで使うので、各コマンド実行時のファイル指定には設定値を使うようにする。

## オレオレ証明局の秘密鍵作成
`# openssl genrsa -aes256 -out /etc/pki/CA/private/cakey.pem 2048`
<pre>
Generating RSA private key, 2048 bit long modulus
...............................................+++
......................................................................................................................................................+++
e is 65537 (0x10001)
Enter pass phrase for /etc/pki/CA/private/cakey.pem: __<= 鍵のパスフレーズを設定__
Verifying - Enter pass phrase for /etc/pki/CA/private/cakey.pem: __<= 再度パスフレーズを入力__
</pre>

ざっくり言うと、SHA256のハッシュを使ってのRSA方式で2048bitで生成って意味だ。と思う。
ここら辺のアルゴリズムは詳しくは知らないけど、今の時代ではこいつを使っておけば問題なさそうだ。
登録する際にパスフレーズを聞かれるので、登録しよう。
確認用にもう一度聞かれるぞ！
ちなみに、これは絶対に漏らしてはならぬ物だ！！

## 証明書発行要求 CSRファイル 作成
`# openssl req -new -key /etc/pki/CA/private/cakey.pem -out /etc/pki/CA/cacert.csr`
<pre>
Enter pass phrase for /etc/pki/CA/private/cakey.pem:  __<= CAの鍵パスフレーズ__
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
 \-----
Country Name (2 letter code) [JP]: __<= 何も入力しないでEnter__
State or Province Name (full name) [Tokyo]: __<= 何も入力しないでEnter__
Locality Name (eg, city) [Shibuya]: __<= 何も入力しないでEnter__
Organization Name (eg, company) [OredayoOre inc.]: __<= 何も入力しないでEnter__
Organizational Unit Name (eg, section) []: __<= 何も入力しないでEnter__
Common Name (eg, your name or your server's hostname) []: __<=オレオレ認証局だって自分でわかる名前__
Email Address []: __<= 何も入力しないでEnter__

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: __<= 何も入力しないでEnter__
An optional company name []: __<= 何も入力しないでEnter__
</pre>

秘密鍵から生成された公開鍵が本物の証であることのファイルを発行してもらうCSRファイル作成。
お前誰的な内容を聞かれるので入力する。
今回はcnfファイルに設定済みの値をそのまま利用するので、ほとんど未入力。

## オレオレ証明書発行
`openssl x509 -days 3650 -in /etc/pki/CA/cacert.csr -req -signkey /etc/pki/CA/private/cakey.pem -out /etc/pki/CA/cacert.pem`
<pre>
Signature ok
subject=/C=JP/ST=Tokyo/L=Shibuya/O=OredayoOre inc./CN=OredayoOre CA
Getting Private key
Enter pass phrase for /etc/pki/CA/private/cakey.pem: __<=認証局の鍵パスフレーズ__
</pre>

自分自身の鍵を自分自身で署名する。これが俗に言うオレオレ証明書。
これをそのままSSL証明書として使う事例が多いけど、
今回はオレオレ認証局を作成して沢山証明書を発行するのが目的なので、SSL証明書には使用しない。
SSL証明書に利用するのは、オレオレ認証局が発行した正規の証明書(笑)だから。
ここまでの作業は一度だけすればいいさ。

## 認証局運用に必要なファイルの生成

```bash
touch /etc/pki/CA/index.txt
echo 00 > /etc/pki/CA/serial
```
証明書発行を記録していくファイル。
こいつを用意しておかないとエラー出るぞ。

# 鍵作成からオレオレ認証局での証明書発行
基本的な流れは認証局の作業と似てる。

## 鍵作成
ではApacheなどで使用したいサーバーの証明書を発行してもらうために、まずは対象サーバーで鍵作成。

`openssl genrsa -aes256 -out /etc/pki/tls/private/privkey.pem 2048`

鍵ファイルの置き場以外は認証局と同じ。

## 発行要求CSRファイル作成
`openssl req -new -key /etc/pki/tls/private/privkey.pem -out /etc/pki/tls/certs/domain_name.csr`
<pre>
Enter pass phrase for /etc/pki/tls/private/privkey.pem:  __<= 鍵パスフレーズ__
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
 \-----
Country Name (2 letter code) [JP]: __<= 何も入力しないでEnter__
State or Province Name (full name) [Tokyo]: __<= 何も入力しないでEnter__
Locality Name (eg, city) [Shibuya]: __<= 何も入力しないでEnter__
Organization Name (eg, company) [OredayoOre inc.]: __<= 何も入力しないでEnter__
Organizational Unit Name (eg, section) []: __<= 何も入力しないでEnter__
Common Name (eg, your name or your server's hostname) []: __<=証明書を発行したいドメイン__
Email Address []: __<= 何も入力しないでEnter__

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: __<= 何も入力しないでEnter__
An optional company name []: __<= 何も入力しないでEnter__
</pre>

基本認証局と同じなんだけど、注意点は Common Name の入力にドメインを指定すること。
www.example.com の証明書を作りたいのであれば www.example.com と入力する。
マルチドメイン証明書にするには、*.example.com とすれば、 www.example.com や hoge.example.com でも同じ証明書で大丈夫。
ただし、huga.hoge.example.com の様な場合は *.hoge.example.com にしないとブラウザーでエラー。

## 証明書発行
`openssl ca -in domain_name.csr -out /etc/pki/tls/certs/domain_name.crt.pem -days 825`
<pre>
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem: __<=認証局の鍵パスフレーズ__
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 0 (0x0)
        Validity
            Not Before: Jan 28 17:48:31 2017 GMT
            Not After : Jan 28 17:48:31 2027 GMT
        Subject:
            countryName               = JP
            stateOrProvinceName       = Tokyo
            organizationName          = OredayoOre inc.
            commonName                = domain-name.example.com
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
            X509v3 Authority Key Identifier:
                DirName:/C=JP/ST=Tokyo/L=Shibuya/O=OredayoOre inc./CN=OredayoOreCA
                serial:xx:xx:xx:xx:xx:xx:xx:xx

Certificate is to be certified until Jan 28 17:48:31 2027 GMT (3652 days)
Sign the certificate? [y/n]: __<= y__


1 out of 1 certificate requests certified, commit? [y/n] __<= y__
Write out database with 1 new entries
Data Base Updated
</pre>

ここがおおきな違い。
自己署名`openssl x509`ではなく、認証局`openssl ca`として署名するのだ。

# 鍵を使う
Apacheやnginxなどで使用する場合、鍵のパスフレーズを聞かれて簡単に起動できない。
そこでパスフレーズなしの鍵を生成する。
`# openssl rsa -in /etc/pki/tls/private/privkey.pem -out /etc/pki/tls/private/privkey-nopass.pem`
パスフレーズを聞かれるので、鍵を作成したときに入力したパスフレーズを入れる。

これらを使用したいデーモンに読み込ませるようにすれば使える。
例えばApacheで使いたいなら最低限の設定は以下の通り。

```apache
<VirtualHost *:443>
    ServerName domain-name.example.com
    SSLEngine On
    SSLCertificateFile /etc/pki/tls/certs/domain-name.crt.pem
    SSLCertificateKeyFile /etc/pki/tls/private/privkey-nopass.pem
</VirtualHost>
```

# 2017.05.08追記
GW明けから問題発生。
Chromeでオレオレ証明書ページがエラーにorz
「SSL_ERROR_BAD_CERT_DOMAIN」エラーになり、詳細には
「このサーバーが example.com であることを確認できませんでした。このサーバーのセキュリティ証明書は [missing_subjectAltName] から発行されています。原因として、設定が不適切であるか、悪意のあるユーザーが接続を妨害していることが考えられます。」
って言われたよ。

どうやら、GW前位にChrome58が配信されて証明書周りの仕様が変わった模様。
今までCN属性で指定していたドメインが判定に使われず、X509 Subject Alternative Name DNSで判定を行うのだと。

ナンノコッチャ？

## Subject Alternative Name
マルチドメインの証明書とかで利用できる、ざっと丸めるとversion３で設定できる追加項目。
詳しくはどこかの誰かが教えてくれるはず。

## どうすればいい？
最終の証明書発行の段階で対応すればOKで、その他には影響出さずに対応可能だった。

1. /etc/pki/tls/openssl.cnf を編集
  - `[ usr_cert ]`セクション内に`subjectAltName = @alt_names`を追記
1. 証明書発行の手前で、追加設定ファイルを作成する
  1. san.ext(ファイル名はなんでもok）作成
  2. 作成したファイルに `subjectAltName=DNS:my-domain.example.com` を記載
  3. 複数のドメインを指定したい場合はカンマ区切りで続けて記載  
     `subjectAltName=DNS:my-domain.example.com,DNS:*.my-domain.example.com`
1. opensslコマンドに -extfile san.ext を追加して証明書発行を実行する
  - `openssl ca -in domain_name.csr -out /etc/pki/tls/certs/domain_name.crt.pem -days 825 -extfile san.ext`
  - 下記のような内容が出力されていればOK<pre>
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:my-domain.example.com
</pre>
1. 「TXT_DB error number 2」ってエラーが出たら...初期設定では重複して登録できないのでrevokeしてあげよう
  1. `cat /etc/pki/CA/index.txt` を実行して作成済み証明書確認
  2. 重複しているドメイン行の３列目位にある番号を確認
  3. 番号が03の場合は `openssl ca -revoke /etc/pki/CA/newcerts/04.pem` を実行
1. エラーとなってしまう証明書と今回作成した証明書を入れ替えて、WEBサーバー再起動で完了

# 2019.12.18 追記

ここ最近オレオレ認証局で発行した証明書を使うと、MacOSXのChromeで **NET::ERR_CERT_REVOKED** となってしまう問題に遭遇した。
どうやらOSXの証明書の有効性判定が厳しくなった様で、Catalinaに切り替わった以降に発行された証明書が対象となる。
なので、証明書発行をコマンド履歴からファイルパスだけを変えて実行したとして、同じ認証局の同一方法で生成した証明書でもタイミングによって問題ない場合とエラーになる場合がある。
またややこしいことに、 ``openssl verify`` コマンドや ``openssl s_client -status`` コマンドで確認してもエラーにならない。
さらには、エラーになっているChromeから証明書を参照するとエラーになっていない。
Safariでもエラーになり、証明書を確認するとこちらはエラーになっている。基準に準拠していないとか何とか言われる。
独自にCA情報を持っているFireFoxは影響がない。

では、何が影響しているのか！？
それは **有効期限が825日以内** でなければならない仕様に変わったらしい。
つまり、826日以上である10年の3650日で作っていた証明書は無効になる！！
そのため ``-days 3650`` のパラメータを ``-days 825`` にしなければならないのだ。

サンプルで載せていたコマンドも有効期限を変更。