証明書について深堀する②証明書の検証

## これは何?

前回，証明書について語る前に，証明書に関する概要とデジタル署名について記事にしました。

https://qiita.com/sigma_devsecops/items/fc3eb4959390b9cc1b65

今回は証明書について記載します。

---

## PKIの用語を整理する

- Public Key Infrastructureの略。
- PKIとは: 電子証明書によって，証明書の発行元や公開鍵の正当性を証明するための基盤。
- PKIは以下の要素により構成される。

### CA(認証局)

- Certificate Authorityの略
- 証明書を発行し，そのシリアル番号の発行履歴を管理する。
- 証明書の有効期限前に無効にし，CRLにとして公開する。

:::note info
CRL: Certificate Revocation List)の略で，失効したデジタル証明書のシリアルのリスト。
:::

CAはRAとIAで構成される。

#### RA(登録局)

- Registration Authorityの略。
- 証明書を要求/執行要求された時に申請を受け付け，CAに対して発行/執行を要請する。

#### IA(発行局)

- Issuing Authorityの略。
- 証明書の正当性を確認するアプリケーション等が直接やりとりするのがIAになる
- プリケーションからの要求に応じて証明書やCRLの発行を行う

---

## なぜ証明書を使う必要があるか

[前回記事](https://qiita.com/sigma_devsecops/items/fc3eb4959390b9cc1b65)でデジタル署名単体では公開鍵を発行した人と署名を発行した人が同じことまでしかわからず，発行した人が誰かを確認することは不可能であるという話をした。
この問題を解決するために，証明書を使う。証明書を使うことで，公開鍵とドメインの結びつきを保証することができるので，サイトの運営者の情報が正しいことを認証局(CA)が保証してくれるのだ。

[デジタル証明書とは](https://www.infraexpert.com/study/security6.html#google_vignette)にある以下の図がわかりやすいので貼っておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/b9ff5cfe-1720-5414-838f-be0634d2c522.png)

(※ここでは中間CAは一旦無いものとする)

1. Webサーバは予め公開鍵や所有者情報をCAに送信して証明書の作成依頼(CAR)を行う。
2. CAは証明書を作成してサーバに返す。

    :::note info
    証明書は以下のような構成になっている
    - サブジェクト情報 (例: 所有者の名前、組織名、公開鍵など)
    - 発行者情報 (例: 証明書を発行した認証局(CA)の名前)
    - 有効期限
    - シリアル番号
    - 公開鍵
    - デジタル署名(証明書のデジタル署名以外の項目をハッシュ化したものを認証局の秘密鍵で暗号化したもの)
    
3. サーバは証明書を発行できるように準備する
4. クライアントはサーバと接続する際に証明書を取得する
5. クライアントはCブラウザに登録されているルートCAの公開鍵と証明書に含まれる公開鍵が等しいことを確認する。
6. クライアントは証明書のデジタル署名をルートCAの公開鍵を複合する。ルートCAの公開鍵でデコードできるということから，証明書の情報は改ざんされておらず，認証局により発行されていることが確認できる。


---

## CAの信頼モデル

上の例ではCAは1つしかなかったが，負荷の分散のため中間CAをが存在する。中間CAはより上位の認証局に署名してもらうことで正当な認証局であることを保証することができる。

注意すべきなのはルートCAは自身でデジタル署名を作成している(自己署名)ことである。(このため，ルート証明書の検証を行うためにはルート証明書のデジタル証明書をルートCAの公開鍵で複合することになる。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/5e07ef86-8b82-d5f1-aec5-2407e2832e14.png)
図は[デジタル証明書とは](https://www.infraexpert.com/study/security6.html#google_vignette)より。

以下に中間証明書を含んだ場合の証明書の検証の流れを書いておく。

1. クライアントは，自分よりも上位の証明書をまとめて取得する(これを証明書チェーンという)
2. ルートCAの公開鍵はクライアントのブラウザ等に保存されている。この公開鍵がルートCAの公開鍵と一致していることを確認する。
3. ルート証明書の署名をルートCAの公開鍵で複合して検証。
4. 中間証明書のデジタル署名をルートCAの公開鍵で複合して検証。→中間証明書が持つ，サーバ証明書の公開鍵が正しいことが確認できた。
5. サーバ証明書のデジタル署名を中間証明書から取得したサーバ証明書の公開鍵を使って複合して検証

---

## opensslを使って証明書の検証をやってみる

### opensslで証明書チェーンを試す

https://qiita.com/thetsuthetsu/items/5292aab4ba5f61ce11ca

こちらのQiitaを元に進めたので，本家を見ることを推奨する。
以下はあくまで自分のメモである。

#### ルートCAの構築

事前にディレクトリを用意しておく。
opensslのデフォルトディレクトリはdemoCA配下に以下のように構築するようになっている。

:::note info
ディレクトリの名前付けルールが気に入らない場合には/usr/lib/ssl/openssl.cnf等で変更が可能だが，自分は気にしないことにした。
:::

```shell
mkdir root_ca
cd root_ca
mkdir -p demoCA
mkdir -p demoCA/private # 秘密鍵保存用
mkdir -p demoCA/newcerts
touch demoCA/index.txt
```

- 証明書を発行するには証明書要求が必要なので，証明書要求careq.pemと秘密鍵cakey.pemを作成する。
- パスフレーズは`hogehoge`を設定し，それ以外は空欄でOK

```shell
openssl req -new -keyout demoCA/private/cakey.pem -out demoCA/careq.pem
```

- 証明書要求careq.pemに自己署名し，ルート証明書cacert.pemを作成する。
    - 自己署名の際に先程作成した秘密鍵cakey.pemを使う。

```shell
openssl ca -create_serial -out demoCA/cacert.pem -days 1095 -batch -keyfile demoCA/private/cakey.pem -selfsign -extensions v3_ca -infiles demoCA/careq.pem 
```

### 中間CAの構築

- 中間証明書用のディレクトリを作成する。

```shell
mkdir ica
cd ica
mkdir -p demoCA/private         
mkdir -p demoCA/cacert
mkdir -p demoCA/newcerts # 中間CAから発行するサーバ証明書が格納される。
touch demoCA/index.txt
```

- 中間証明書を発行するため，icareq.pemと秘密鍵icakey.pemを作成する。
    - この際にパスフレーズは`hogehoge1`，Common Nameに`ica.com`を入力しておく。

```shell
openssl req -new -keyout demoCA/private/icakey.pem -out icareq.pem -days 365
```

- 作成した中間証明書の要求icareq.pemをルートCAに署名してもらう。
    - この際にroot_caの秘密鍵で署名していることに注意!

```shell
cd ../root_ca

openssl ca -policy policy_anything -out ../ica/demoCA/cacert/icacert.pem -extensions v3_ca -infiles ../ica/icareq.pem
Enter pass phrase for ./demoCA/private/cakey.pem:
Check that the request matches the signature
Signature ok
```

成功したら証明書が作成されていることを確認する。

```shell
ls demoCA/cacert/icacert.pem
demoCA/cacert/icacert.pem
```

- 公開キーのシリアルを作成する。

```shell
openssl x509 -in demoCA/cacert/icacert.pem -noout -next_serial -out demoCA/serial
```

### 証明書チェインを作成する

ルート証明書，中間証明書からCERTIFICATEブロック以外を削除してチェインを作成する。

```shell
# CERTIFICATEブロック以外を削除する
openssl x509 -outform pem -in ica/demoCA/cacert/icacert.pem -out ica.pem
openssl x509 -outform pem -in root_ca/demoCA/cacert.pem -out rca.pem

# 証明書チェインを確認
openssl verify -CAfile rca.pem ica.pem       1
ica.pem: OK

# 証明書チェインを作成
cat rca.pem ica.pem >> cert_chain.pem
```

### サーバ証明書を作成する

```shell
mkdir server
cd server
```

- サーバの秘密鍵を作成し，パスフレーズを消去する。

```shell
openssl genrsa -aes256 2048 > server_key.pem

# パスフレーズを消去
openssl rsa -in server_key.pem -out server_key.pem
```

:::note info
暗号化した秘密鍵はサーバで起動するたびにパスワードを求められて面倒なため，復号化して使うのが一般的らしい。
:::


- 証明書要求を作成する。

```shell
openssl req -new -key server_key.pem -out server_req.pem
```

- 中間CAからサーバ証明書を発行してもらう

```shell
cd ../ica

openssl ca -batch -in ../server/server_req.pem -keyfile demoCA/private/icakey.pem -cert demoCA/cacert/icacert.pem -out ../server/server.crt -days 365
```

:::note warn
COUNTRYとかが異なっていると証明書の発行に失敗する。

```shell
openssl ca -batch -in ../server/server_req.pem -keyfile demoCA/private/icakey.pem -cert demoCA/cacert/icacert.pem -out ../server/server.crt -days 365
Using configuration from /usr/lib/ssl/openssl.cnf
Enter pass phrase for demoCA/private/icakey.pem:
Check that the request matches the signature
Signature ok
The countryName field is different between
CA certificate (JP) and the request (AU)
```
:::
証明書チェーンを使ってサーバ証明書が検証できればよい。

```shell
openssl verify -CAfile cert_chain.pem server/www_vubuntults_mydomain.crt
```

---

## 感想

証明書の以下の部分が混乱を招きやすいと思う(個人的には)

- (デジタル)証明書は以下で構成される
    - 本人確認情報
    - 公開鍵
    - その他(シリアル等)
    - 上記3つを上位のCAの秘密鍵で署名したデジタル署名
(記事によっては，証明書にデジタル署名が含まれていることを強調しようとして2つ送信されているかのような誤解を生む)

- ルートCAの公開鍵はブラウザや端末に保存されているが，中間CAの公開鍵は証明書チェーンの中にある(通信によって取得する)