自己署名証明書をdocker内で作ってみた

Windows環境でdockerを使ってSSLの自己署名証明書（いわゆるオレオレ証明書）を作ってみる。

たまに作ると手順を忘れてるのでメモしておく。
opensslのバージョンが古かったりするとオプションパラメータが使えなかったりするので、docker内で作成すると楽である。

ブラウザで警告が表示されないようなサーバ証明書を作成するにはいくつか制約があるので注意。
* サーバ証明書はバージョンV3かつサブジェクト代替名（SAN=Subject Alternative Name）が必要。
* サーバ証明書の有効期限は最大398日間まで。
* FireFoxはCAの自己署名証明書のみだと使用できない模様（色々と試してみたがダメだった）。このため、CA証明書とサーバ証明書をわけて作成する。

# 環境
* Windows 10 Pro
* docker (ver 20), docker-compose(ver 2.6)

# 手順 

## 準備
まずはalpine linuxを起動。

```sh
docker run -it -p 443:443 -v c:\temp\cert:/var/nginx/cert alpine:3.17.2 sh
```

後でnginxで動作確認するため443ポートを開いておく。

証明書の保存先をボリュームで指定してローカルに保存する。（ここではc:\temp\certに保存される）
ボリュームの指定(-v)の左側（ローカルドライブ側）はWindowsだとフルパスが必要。

opensslをインストール

```sh
apk add openssl
```

ディレクトリを移動して、opensslの実行に必要なファイルを作成。

```sh
cd /var/nginx/cert

mkdir demoCA
echo "01" > demoCA/serial
echo "00" > demoCA/crlnumber
touch demoCA/index.txt
```

## CA証明書作成

まずは自己署名でCAの証明書を作成する。

秘密鍵を作成。

```sh
CERT_CA_NAME=ca
openssl genrsa 2048 > $CERT_CA_NAME.key
```

| オプション  | 説明                |
|------------|---------------------|
| genrsa     | RSA秘密鍵を作成      |
| 2048       | 鍵の長さ(bit)。通常は2048 |

CSR（署名要求）を作成。

```sh
openssl req -new -subj "/C=JP/ST=Tokyo/O=$CERT_CA_NAME/CN=$CERT_CA_NAME" -key $CERT_CA_NAME.key -out $CERT_CA_NAME.csr
```

| オプション  | 説明                |
|------------|---------------------|
| req        | 署名要求を作成       |
| -new       | 新規に作成 |
| -subj xxx  | 所在地・組織名を指定 |
| -key xxx   | 秘密鍵を指定 |
| -out xxx   | 生成したCSRの出力先 |

CSRをもとに自己署名し証明書を作成。

```sh
openssl ca -selfsign -batch -days 3650 -extensions v3_ca -outdir . -keyfile $CERT_CA_NAME.key -in $CERT_CA_NAME.csr -out $CERT_CA_NAME.crt
```

| オプション  | 説明                |
|------------|---------------------|
| ca         | 署名を行う       |
| -selfsign  | 自己署名を指定 |
| -batch     | 確認のプロンプトを非表示 |
| -days 3650 | 有効期限（日） |
| -extensions v3_ca  | CA用を指定 |
| -outdir xxx | 出力先。カレントディレクトリ(.)にしておく |
| -keyfile xxx | CAの秘密鍵 |
| -in xxx | CSRファイル |
| -out xxx | 作成した証明書ファイル |

CAの証明書(ca.crt)と秘密鍵(ca.key)が作成されていれば成功である。

## サーバ証明書作成

CA証明書を作成したので、これを使用してサーバ証明書に署名する。

CA証明書と同様に秘密鍵を作成。

```sh
CERT_NAME=cert
openssl genrsa 2048 > $CERT_NAME.key
```

SANを指定してCSRを作成。
SANはDNS:example.comのようにホスト名の先頭に`DNS:`をつけて指定する。
（複数指定する場合は`DNS:a.example.com, DNS:b.example.com`のようにカンマ区切りにする）

```sh
SUBJECT_ALT="subjectAltName = DNS:localhost"
openssl req -new -x509 -subj "/C=JP/ST=Tokyo/O=$CERT_CA_NAME/CN=$CERT_NAME" -extensions v3_req -addext "$SUBJECT_ALT" -key $CERT_NAME.key -out $CERT_NAME.csr
```

| オプション  | 説明                |
|------------|---------------------|
| req        | 署名要求を作成       |
| -x509       | x509形式で作成 |
| -extensions v3_req   | サーバ用 |
| -addext xxx   | 追加の設定を指定 |

注意点として、`-x509`を指定しないとSANが保存されなかった。
また、SANは通常のパラーメータでは指定できないので、`-addext`で設定ファイルの表記に従った形式で指定する必要がある。

CAの証明書と秘密鍵で署名して完了。
```sh
openssl x509 -text -days 365 -CA $CERT_CA_NAME.crt -CAkey $CERT_CA_NAME.key -in $CERT_NAME.csr -out $CERT_NAME.crt
```

| オプション  | 説明                |
|------------|---------------------|
| x509        | x509形式で署名       |
| -text       | 詳細を証明書に出力 |
| -days 365   | 有効期限（日）。398日以内 |
| -CA xxx   | CAの証明書を指定 |
| -CAkey xxx   | CAの秘密鍵を指定 |
| -in xxx | CSRを指定 |
| -out xxx | 作成した証明書の出力先 |

以上でcert.key, cert.crtが作成できていれば成功である。

※`openssl ca`ではバージョンV3にならなかったので、`openssl x509`で署名している。
CA証明書とサーバ証明書でコマンドが異なるのでややこしい。。。

証明書の内容は以下のコマンドで確認できる。

```sh
openssl x509 -noout -text -in $CERT_NAME.crt

/var/nginx/cert # openssl x509 -noout -text -in $CERT_NAME.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            2a:4e:bc:f7:8f:76:ab:ae:68:93 ...
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = JP, ST = Tokyo, O = ca, CN = ca
        ...
        Subject: C = JP, ST = Tokyo, O = ca, CN = cert
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                ...
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name:
                DNS:localhost
            X509v3 Subject Key Identifier:
                53:21:94:5A:0C:C4:12:BA:BE:38 ...
            X509v3 Authority Key Identifier:
                16:06:3C:F5:D6:8D:EF:B6:29:51 ...
        ...
```

サーバ証明書は以下の点を確認する。
* Version: 3になっていること
* X509v3 Basic ConstraintsがCA:FALSEになっていること
* X509v3 Key UsageにDigital Signatureが設定されていること
* X509v3 Subject Alternative NameにSANが指定されていること
* X509v3 Subject Key IdentifierとX509v3 Authority Key Identifierが設定されていること。

# 確認

nginxを起動して証明書がブラウザで警告表示されないか確認する。

nginxを追加。
```sh
apk add nginx
```

nginxの設定ファイルを作成する。
SSL証明書を指定して443ポートで起動するように設定。
（証明書のパスやファイル名が異なる場合は正しいパスを指定する必要がある）
```sh
echo "
server {
    listen 443 ssl;

    ssl_certificate /var/nginx/cert/cert.crt;
    ssl_certificate_key /var/nginx/cert/cert.key;
}
" > /etc/nginx/http.d/default.conf
```

nginxを起動
```sh
nginx -g "daemon off;"
```

CAの証明書(ca.crt)をWindowsのエクスプローラー上で実行し証明書のインストールを行う。証明書ストアで参照から **『信頼されたルート証明機関』** を選択してインポートする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/21ee26db-ba18-f7dd-8792-454a0470d2e2.png)


FireFoxの場合は証明書ストアを独自に管理しているので、FireFoxの設定でインポートする。

https://localhost にアクセスして証明書のエラーが表示されなければ成功である。

CAの証明書は10年を指定してもエラーにはならなかったので、CA証明書の有効期限は長めにしておいて、サーバ証明書を毎年再発行して差し替える運用にするのがよいと思われる。

# 参考

https://www.openssl.org/docs/man1.0.2/man1/openssl-req.html