自己署名証明書（CA 含む）を簡単に作成する方法

背景
--------------------------------------------------------------------------------

開発時に利用する用途で、自己署名証明書をローカルホスト向けに発行したいケースがあった。
[Let's Encrypt](https://letsencrypt.org) の[ドキュメント](https://letsencrypt.org/docs/certificates-for-localhost/)によると、[minica](https://github.com/jsha/minica) を使って CA を含めた証明書を簡単に用意できるとのことなので実際に動かしてみた。


成果物
--------------------------------------------------------------------------------

役割              | ファイル名
---------------- | ------------
CA 鍵            | minica-key.pem
CA 証明書         | minica.pem
自己署名証明書（鍵）| key.pem
自己署名証明書     | cert.pem


証明書生成手順
--------------------------------------------------------------------------------

### minica のインストール

[minica](https://github.com/jsha/minica) の README に従いインストールする（Go がインストールされている前提）:

```shell-session
$ go get github.com/jsha/minica
```

### 証明書を生成する

`-domains` に Server Alternative Names に登録するドメインをカンマ区切りで記載して実行する:

```shell-session
$ minica -domains 'example.localhost,*.example.localhost'
```

生成されたファイルを確認する:

```shell-session
$ tree
.
|-- example.localhost
|   |-- cert.pem
|   `-- key.pem
|-- minica-key.pem
`-- minica.pem
```

### 生成された証明書の確認

CA 証明書の確認:

```shell-session
$ openssl x509 -text -noout -in minica.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            6b:f5:aa:ab:5d:00:bc:34
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=minica root ca 6bf5aa
        Validity
            Not Before: Nov  9 03:25:39 2018 GMT
            Not After : Nov  9 03:25:39 2118 GMT
        Subject: CN=minica root ca 6bf5aa
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:e7:b3:b2:78:38:70:4c:20:34:e7:f1:d4:e6:1b:
                    d6:43:92:6c:cb:49:8b:c7:f7:a7:e4:4f:7b:a6:0d:
                    73:d2:2a:4e:fa:5f:3d:2e:94:b6:d4:ba:43:e6:2f:
                    67:1b:0b:c5:2b:ff:84:c8:f4:4f:40:e1:27:db:f2:
                    f7:88:1a:c7:32:a6:b9:65:3d:d3:d7:78:17:ae:77:
                    29:e2:81:70:08:15:f5:c1:06:3a:17:94:56:b6:c1:
                    f1:bd:b3:8a:09:66:5d:4d:75:31:46:bc:1f:26:cd:
                    26:0e:74:d3:49:c8:80:8e:4a:07:f0:0b:fe:b1:56:
                    66:8b:e2:9c:3e:36:ad:b2:04:ec:fb:62:4e:bf:40:
                    04:ab:89:1c:d4:4d:0c:f9:af:46:a3:c1:e1:ed:55:
                    ca:27:2b:29:eb:80:9e:13:07:54:33:27:c6:a4:4e:
                    ff:d1:78:32:05:c9:9d:f0:95:0c:42:1f:39:20:35:
                    c3:4b:21:a1:ec:42:98:8f:b0:98:8a:d2:d0:d3:1a:
                    4f:20:0b:a2:45:48:34:77:a0:64:48:d4:1e:2b:6b:
                    68:07:ec:a8:73:8b:cd:d4:99:74:64:a1:77:37:5b:
                    f8:c0:d2:35:21:31:33:05:fe:d6:37:90:f7:a4:6a:
                    87:52:b4:ff:47:8e:f4:c4:e9:24:0e:8a:27:f7:33:
                    03:a5
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
    Signature Algorithm: sha256WithRSAEncryption
        62:cc:29:59:0c:01:79:b9:98:ae:ba:57:cf:57:3f:94:33:14:
        6f:20:42:e5:56:81:76:a2:22:1f:a9:42:e0:c9:c7:df:9f:b6:
        51:63:f9:6b:9e:e6:05:cf:9f:97:2b:82:9a:83:fb:94:4f:da:
        88:4d:39:c5:bc:b7:5d:a5:53:a8:69:77:73:ea:f1:d2:bb:4e:
        30:bc:c2:d8:52:51:8f:1b:92:1d:dc:6a:af:f3:c6:75:8c:f6:
        aa:3c:45:cd:d6:b4:70:5a:55:a4:bc:76:a6:77:10:5d:b9:51:
        f2:72:16:26:35:e8:80:01:9b:f3:d9:b0:21:0b:b9:3a:91:1f:
        d6:1d:5d:7a:56:d4:f3:7b:9a:d5:62:da:1f:a3:f2:57:dd:42:
        b1:0c:aa:f4:64:27:aa:53:85:b0:7e:8b:3b:65:09:a2:8c:a9:
        b5:88:00:03:20:64:ee:81:68:40:49:61:2e:e8:7f:22:58:cc:
        83:89:7c:81:e9:ec:0f:d0:05:4a:3d:44:1d:85:d4:fd:d7:42:
        55:72:d9:63:82:31:ad:30:13:72:db:d4:43:6b:79:37:2f:e0:
        b5:25:d9:73:ce:73:e1:29:72:e2:d2:6a:cf:bd:e7:57:5f:7a:
        c8:94:03:7c:56:df:da:89:d4:61:58:01:a2:28:31:bc:b6:93:
        1e:72:c0:00
```

自己署名証明書の確認:

```shell-session
$ openssl x509 -text -noout -in example.localhost/cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            42:49:cf:ad:ad:fb:b3:23
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=minica root ca 6bf5aa
        Validity
            Not Before: Nov  9 03:25:39 2018 GMT
            Not After : Nov  9 03:25:39 2108 GMT
        Subject: CN=example.localhost
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:bd:d6:8b:ce:83:0d:82:98:fd:65:d3:8c:01:9a:
                    d4:72:78:36:2b:a3:8c:a4:52:f1:31:70:ed:08:80:
                    a5:d1:ec:2b:ae:68:e1:47:e9:46:81:7f:97:fd:5e:
                    cf:0a:a1:f0:a0:0f:e7:30:8a:b5:40:4c:93:ea:5a:
                    35:1a:87:b8:ee:83:85:d8:00:7f:a9:dc:35:35:d2:
                    02:33:ef:08:9a:f8:2b:2b:83:34:93:51:28:cd:f4:
                    f8:a0:03:83:ae:dd:46:b2:81:e0:b7:9c:5a:e7:97:
                    5c:f2:87:04:dd:3c:30:88:ee:05:79:e9:16:17:79:
                    e3:51:bb:95:f1:f1:01:73:7e:cf:06:87:46:1e:e6:
                    cb:7e:6b:4d:30:37:98:7a:f9:0b:c3:c9:c4:69:66:
                    db:eb:28:68:aa:7b:68:2e:17:0b:a8:6d:1c:ab:67:
                    65:12:ab:2a:f5:c1:b3:9e:ab:67:57:93:2d:29:35:
                    79:4a:2b:d1:5f:71:02:01:15:05:8f:3f:3f:40:4b:
                    d4:df:4c:fa:df:00:2e:fc:15:36:73:63:2e:87:3e:
                    ab:e8:a2:79:3f:05:35:f8:d5:da:5a:ce:fa:4c:78:
                    0f:99:bb:8a:ef:40:4b:5f:44:32:2d:bb:1b:a3:48:
                    4a:32:c7:33:fa:2c:5c:40:cb:05:e6:89:c5:2a:82:
                    23:b5
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Alternative Name:
                DNS:example.localhost, DNS:*.exapmple.localhost
    Signature Algorithm: sha256WithRSAEncryption
        63:2d:4d:1b:81:ed:6c:67:dc:aa:a4:a4:35:78:ba:01:38:2d:
        b8:51:ea:e3:d8:66:16:77:58:26:05:48:bc:1f:b6:0d:ad:74:
        d1:ce:c3:2b:02:05:c4:39:11:bc:49:40:99:58:45:f4:79:e3:
        25:f1:96:f0:24:bd:ee:70:e1:d2:6f:36:84:c5:25:07:58:da:
        cd:30:c9:06:54:3f:15:db:5c:2a:c0:47:f4:42:a7:d2:c7:34:
        1a:dc:49:b6:3d:85:ce:58:95:51:3f:51:d4:f1:1b:b2:55:35:
        a9:9b:81:7f:c9:72:39:12:0a:eb:d4:aa:dc:32:c7:da:36:35:
        47:64:a1:a6:84:f2:b0:11:09:7e:f9:6f:58:40:de:8e:07:85:
        a2:cf:6d:f0:ec:7d:bb:1d:1d:6c:06:4e:89:b1:30:b3:e4:4c:
        c1:b6:82:fc:70:d3:32:fa:e0:ae:cb:b2:06:94:1f:68:74:a0:
        80:0b:4d:b4:71:c3:d0:0a:99:77:cd:8d:36:b8:01:b4:de:1b:
        0e:8b:b8:ea:07:be:07:56:ea:1b:02:5e:c3:3f:76:13:36:5c:
        73:91:a9:98:e7:0d:ec:2d:5b:06:de:c0:c9:59:dc:2a:74:c8:
        00:25:cb:39:3d:1f:2d:10:f8:72:c2:4f:5d:f8:c5:4c:5c:68:
        db:c5:6b:66
```


CA 証明書を信頼する手順（macOS 向け）
--------------------------------------------------------------------------------

### CA 証明書を開いて "system" に追加する

<img width="647" alt="01_Add_CA_Certificate.png" src="https://qiita-image-store.s3.amazonaws.com/0/1026/aa97a6f3-a3b6-ca4a-d45d-e3109c635bde.png">

### Keychain の Certificates から追加した CA 証明書を開く

<img width="954" alt="02_List_CA_Certificate.png" src="https://qiita-image-store.s3.amazonaws.com/0/1026/0c262dff-fe0b-c42b-1ff3-b18c6d41a18d.png">

### "Trust" の設定を "Always Trust" に変更する

![03_Trust_CA_Certificate.png](https://qiita-image-store.s3.amazonaws.com/0/1026/1376bb49-5441-9903-d3c5-34c745c227d6.png)


付録: 証明書が信頼されているか検証する
--------------------------------------------------------------------------------

Python3 で簡易的な SSL Web サーバを用意:

```python
# `https_server.py` として保存する
from http.server import HTTPServer, SimpleHTTPRequestHandler, HTTPStatus
import ssl

httpd = HTTPServer(('example.localhost', 443), SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, keyfile='key.pem', certfile='cert.pem')
httpd.serve_forever()
```

Web サーバを起動:

```shell-session
$ sudo python3 ./https_server.py
```

ブラウザで https://example.localhost にアクセスし自己署名証明書が信頼済みであることを確認する

<img width="1113" alt="04_Valid_Certificate.png" src="https://qiita-image-store.s3.amazonaws.com/0/1026/f3953e5b-e432-53d7-b2a4-d4f0a97aa2f5.png">
<img width="1113" alt="05_Check_Certificate.png" src="https://qiita-image-store.s3.amazonaws.com/0/1026/483d6d2e-4e31-60a8-eb5d-86b29bfd532c.png">




参考資料
--------------------------------------------------------------------------------

* [Certificates for localhost - Let's Encrypt - Free SSL/TLS Certificates](https://letsencrypt.org/docs/certificates-for-localhost/)
* [GitHub - jsha/minica: minica is a small, simple CA intended for use in situations where the CA operator also operates each host where a certificate will be used.](https://github.com/jsha/minica)
* [Python SimpleHTTPServer with SSL](https://gist.github.com/rozifus/c529caf170699f117c53#gistcomment-2652879)