Nginxに自己署名証明書を設定してHTTPS接続してみる

## はじめに

SSL/TLS の勉強として、自己署名証明書を用意して SSL 通信をやってみたのでまとめる。

## やること

今回やることは以下。

- 自己署名証明書の用意
  - 秘密鍵の作成
  - CSR の作成
  - 証明書に自己署名
- 実際に SSL 通信する
  - `nginx.conf`の設定

## 環境の用意

今回は、Nginx の Docker イメージを使って証明書の用意から SSL 通信をするところまでやる。
また、以下の`docker-compose.yml`を用意し、すべて Docker コンテナ内で行う。

```yml:docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
```

```
$ docker compose up -d
$ docker compose exec nginx /bin/bash
```

## 自己署名証明書の用意

流れとしては、

1. 秘密鍵を作成して、
1. 証明書署名要求（CSR）を作成し、
1. CSR をもとに、自分の秘密鍵で署名した証明書を発行する

### 秘密鍵の作成

まずは`openssl`コマンドを使い、サーバーの秘密鍵を生成する。

```
$ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -aes-256-cbc -out privkey.pem

.......................................................................+++++
.................................................+++++
Enter PEM pass phrase: （パスフレーズを入力）
Verifying - Enter PEM pass phrase: （パスフレーズを再度入力）
```

現在のディレクトリに`privkey.pem`が生成されていれば秘密鍵の作成は完了。

```
$ ls -l privkey.pem

-rw------- 1 root root 1874 Mar  1 00:23 privkey.pem
```

参考: https://www.openssl.org/docs/man1.1.1/man1/genpkey.html

秘密鍵の暗号化方式の種類は、`openssl help`の`Cipher commands`から確認できた。

```
$ openssl help

...略

Cipher commands (see the `enc' command for more details)
aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb
aes-256-cbc       aes-256-ecb       aria-128-cbc      aria-128-cfb
aria-128-cfb1     aria-128-cfb8     aria-128-ctr      aria-128-ecb
aria-128-ofb      aria-192-cbc      aria-192-cfb      aria-192-cfb1
aria-192-cfb8     aria-192-ctr      aria-192-ecb      aria-192-ofb
aria-256-cbc      aria-256-cfb      aria-256-cfb1     aria-256-cfb8
aria-256-ctr      aria-256-ecb      aria-256-ofb      base64
bf                bf-cbc            bf-cfb            bf-ecb
bf-ofb            camellia-128-cbc  camellia-128-ecb  camellia-192-cbc
camellia-192-ecb  camellia-256-cbc  camellia-256-ecb  cast
cast-cbc          cast5-cbc         cast5-cfb         cast5-ecb
cast5-ofb         des               des-cbc           des-cfb
des-ecb           des-ede           des-ede-cbc       des-ede-cfb
des-ede-ofb       des-ede3          des-ede3-cbc      des-ede3-cfb
des-ede3-ofb      des-ofb           des3              desx
rc2               rc2-40-cbc        rc2-64-cbc        rc2-cbc
rc2-cfb           rc2-ecb           rc2-ofb           rc4
rc4-40            seed              seed-cbc          seed-cfb
seed-ecb          seed-ofb          sm4-cbc           sm4-cfb
sm4-ctr           sm4-ecb           sm4-ofb
```

### 証明書署名要求（CSR）の作成

証明書を発行するにあたり、証明書署名要求（CSR）を作成する必要がある。

CSR も`openssl`コマンドで作成する。
今回は、`myssl.test`というドメインで試すため、`Common Name`（CN）は`myssl.test`にしている。

```
$ openssl req -new -key privkey.pem -out csr.pem

Enter pass phrase for 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) [AU]:JP <-入力
State or Province Name (full name) [Some-State]:Tokyo <-入力
Locality Name (eg, city) []:Shibuya-ku <-入力
Organization Name (eg, company) [Internet Widgits Pty Ltd]:ohakutsu <-入力
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:myssl.test <-入力
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
```

参考: https://www.openssl.org/docs/man1.1.1/man1/req.html

`csr.pem`が生成されていれば完了。

```
$ ls -l csr.pem

-rw-r--r-- 1 root root 985 Mar  1 00:44 csr.pem
```

### CSR をもとに証明書を発行する

自分の秘密鍵を使って、自己署名した証明書を発行します。（自己署名した証明書は別名オレオレ証明書と呼ばれている）

`openssl`コマンドを使って行います。

```
$ openssl x509 -req -in csr.pem -signkey privkey.pem -days 90 -out crt.pem

Signature ok
subject=C = JP, ST = Tokyo, L = Shibuya-ku, O = ohakutsu, CN = myssl.test
Getting Private key
Enter pass phrase for privkey.pem: （パスフレーズを入力）
```

参考: https://www.openssl.org/docs/man1.1.1/man1/x509.html

これで自己署名証明書ができた。

証明書の情報は以下のように確認できる。

```
$ openssl x509 -in crt.pem -noout -text

Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            57:e9:cf:01:72:e8:c5:0c:4c:37:c4:2e:5d:50:55:be:d3:62:39:83
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = JP, ST = Tokyo, L = Shibuya-ku, O = ohakutsu, CN = myssl.test
        Validity
            Not Before: Mar  3 11:08:30 2022 GMT
            Not After : Jun  1 11:08:30 2022 GMT
        Subject: C = JP, ST = Tokyo, L = Shibuya-ku, O = ohakutsu, CN = myssl.test
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:c3:18:9e:d2:9a:88:ae:5c:60:76:e4:8f:a1:31:
                    f6:02:98:0d:f1:22:4f:22:7d:b0:d3:60:e2:47:97:
                    4e:00:0f:33:5b:3e:4b:8f:c4:81:14:1c:44:c8:1b:
                    bc:19:fb:d7:fb:18:5f:f8:61:9c:f6:ff:af:4b:86:
                    21:7c:74:d9:84:01:be:15:66:e5:c3:4c:ca:d7:50:
                    13:20:81:c2:56:92:58:93:ce:32:e0:c7:f6:be:17:
                    37:9f:bb:86:09:56:34:0e:af:96:07:cf:72:19:d8:
                    be:04:c9:b2:c5:36:84:97:05:36:33:7c:f0:a2:74:
                    cb:9e:f6:87:94:66:15:c4:09:42:b6:f5:ce:6e:9f:
                    cf:6f:ec:f1:56:dd:9c:5f:79:b2:19:82:47:c3:a2:
                    5d:04:9d:a6:25:32:e5:e2:99:a9:76:64:10:b1:e3:
                    bf:49:80:60:64:a2:9a:c7:1c:e9:74:4f:c5:f9:3a:
                    42:bc:c6:d7:8c:7f:9e:4f:52:75:90:d5:ed:ec:8f:
                    5f:f3:bd:22:36:e1:56:ee:e5:8d:8f:97:da:90:cf:
                    7d:9c:ba:5e:f1:01:33:86:ba:72:ef:61:ca:ef:1b:
                    b6:89:96:79:52:1c:f4:8d:92:61:24:15:55:28:e0:
                    c1:f1:4b:09:8a:23:c7:de:98:3c:11:3d:63:c8:95:
                    3f:2d
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         b6:88:19:65:62:18:b0:47:b3:27:01:e5:d0:84:c2:29:49:59:
         e9:79:63:e7:85:bf:27:a3:5c:25:c6:b3:7b:98:02:6b:ee:a4:
         69:a8:f2:f1:fb:36:91:e6:ef:17:85:96:e6:f9:81:23:b3:c5:
         69:59:6a:bb:29:fe:aa:d8:d1:b6:41:d6:b2:92:86:be:6b:c1:
         4b:41:f5:5a:d1:93:20:29:f3:70:c0:f4:0a:fd:b5:90:64:5f:
         0d:a0:4c:f3:29:d8:c8:7f:8b:cf:20:fc:d9:e0:12:70:5d:73:
         c4:ba:6c:28:2b:61:89:88:9d:a3:dd:37:60:ad:82:7a:a0:5c:
         5c:ca:b3:61:fe:48:35:c3:2e:a6:07:10:6f:93:54:b9:18:ec:
         86:6d:f0:bc:aa:f7:b8:e6:2f:aa:cd:7c:dc:a1:5f:c7:14:38:
         6b:f6:da:9d:1a:c5:f5:58:b6:6e:3e:20:70:c6:d3:f7:8d:a9:
         71:65:0e:9a:13:97:31:c4:8c:fe:30:fa:f3:89:86:e3:26:75:
         e4:9e:49:4b:c8:04:fc:7e:be:0c:f0:f7:a7:7b:a5:3d:68:81:
         cf:73:66:b3:d0:42:19:ab:c0:0b:02:51:a7:13:29:36:f0:5f:
         c9:88:b8:49:0b:75:f3:97:ad:4c:34:2f:36:fc:d7:da:12:e0:
         95:c2:3c:fa
```

## Nginx で SSL 通信をしてみる

Nginx で SSL の設定を行い、SSL 通信ができるかを試す。

`nginx -V`をしてみると、Nginx の Docker イメージではビルドオプションに

- `--conf-path=/etc/nginx/nginx.conf`
- `--with-http_ssl_module`

があることがわかる。

```
$ nginx -V

nginx version: nginx/1.21.6
built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
built with OpenSSL 1.1.1k  25 Mar 2021
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-1.21.6/debian/debuild-base/nginx-1.21.6=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
```

そのため今回は、`nginx.conf`を触るだけで良さそう。
（ソースからビルドする場合は、`http_ssl_module`はデフォルトで有効でないため、ビルド時に`--with-http_ssl_module`をつけてあげる必要がある）

### `nginx.conf`を修正する

`/etc/nginx/nginx.conf`を見てみると、`server`ディレクティブがないが、
`include /etc/nginx/conf.d/*.conf;`とあるため、`/etc/nginx/conf.d/default.conf`に記述があることがわかる。

```
$ cat /etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

略...

http {
    略...

    include /etc/nginx/conf.d/*.conf;
}

$ cat /etc/nginx/conf.d/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    略...
}
```

今回は検証するだけなので`/etc/nginx/conf.d/default.conf`を以下の様に書き換える。
また、`/etc/nginx/ssl`配下に秘密鍵（`privkey.pem`）と証明書（`crt.pem`）、秘密鍵のパスフレーズを書いた`passwd`を配置しておく。

```
$ mkdir -p /etc/nginx/ssl

$ mv crt.pem /etc/nginx/ssl/crt.pem
$ mv privkey.pem /etc/nginx/ssl/privkey.pem

$ touch /etc/nginx/ssl/passwd
$ chmod 600 /etc/nginx/ssl/passwd
$ echo 'パスフレーズ' > /etc/nginx/ssl/passwd

$ ls -l /etc/nginx/ssl
-rw-r--r-- 1 root root 1180 Mar  3 11:28 crt.pem
-rw------- 1 root root    9 Mar  3 11:37 passwd
-rw------- 1 root root 1874 Mar  3 11:27 privkey.pem
```

```diff:/etc/nginx/conf.d/default.conf
server {
-   listen       80;
+   listen       443 ssl;
-   listen  [::]:80;
+   listen  [::]:443 ssl;
-   server_name  localhost;
+   server_name  myssl.test;
+
+   ssl_certificate /etc/nginx/ssl/crt.pem;
+   ssl_certificate_key /etc/nginx/ssl/privkey.pem;
+   ssl_password_file /etc/nginx/ssl/passwd;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
```

あとは、Nginx の設定を再読み込みすれば完了。

```
$ nginx -s reload
```

### アクセスしてみる

ホストマシンから`curl`コマンドでアクセスしてみると、SSL 通信できていることがわかる。

```
$ curl --verbose --insecure --header 'Host: myssl.test' https://localhost

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Shibuya-ku; O=ohakutsu; CN=myssl.test
*  start date: Mar  3 11:28:32 2022 GMT
*  expire date: Jun  1 11:28:32 2022 GMT
*  issuer: C=JP; ST=Tokyo; L=Shibuya-ku; O=ohakutsu; CN=myssl.test
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> Host: myssl.test
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.21.6
< Date: Thu, 03 Mar 2022 11:51:12 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
< Connection: keep-alive
< ETag: "61f01158-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host localhost left intact
* Closing connection 0
```

ブラウザでも試してみる。

ホストマシンの`/etc/hosts`に`127.0.0.1 myssl.test`を追加し、`https://myssl.test`にアクセスする。（検証が終わったら削除しましょう）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/2ba02138-28d0-6608-e0e5-40b098e8056e.png)

SSL 通信できていることが確認できる。

# 最後に

自己署名証明書の発行から Nginx での設定、検証までやった。

今度は、自己ルート CA、中間 CA を立ててやってみたい。