localhost を https 化する ~独自CA証明書を作って~
ローカルなマシンでサーバーアプリを立ちあげて、同じマシンからアクセスしてテストしたりする際に、自己署名サーバー証明書、いわゆるオレオレ証明書を使うことがありますが、ブラウザとかgitとかcurlとかとかが、証明書検証をオフにしても、ワーニングを出したり、最悪の場合エラーを出して動作しなかったりします。昨今、サイバーセキュリティ対策の必要性が大きくなっていく中で、そういうケースが増えてきているようにも感じます。
セキュリティの観点から望ましいことではないのは承知の上で、あくまで個人が自分のためだけに使う前提で、そういうワーニングやエラーが出ないようにする方法を書きます。
繰り返しますが、この方法は、他人と共用するサーバーには使わないでください。
雑な要約
自己署名のサーバー証明書を作るのではなく、独自のCA証明書を作って、そのCA証明書でサーバー証明書を署名します。
手順の概観
ざっくりとした手順を、雑な図を交えて書きます。
(図は mermaid で描いたのですが配置を思うようにコントロールできなくて若干見づらいですすみません。)
- CA用の秘密鍵を生成し、その秘密鍵で公開鍵を自己署名して、CA証明書を作成する
- サーバー用の秘密鍵を生成し、1. で作ったCA証明書で公開鍵を署名して、サーバー証明書を作成する
- サーバーの秘密鍵と証明書をサーバーに、CA証明書をクライアントにインストールする
- クライアントからサーバーにアクセスすると、サーバーからクライアントへ証明書が提示され、クライアントではCA証明書を使ってサーバー証明書が検証される
手順の詳細
1. CA用の秘密鍵を生成し、公開鍵を秘密鍵で自己署名してCA証明書を作成する
- CA用の秘密鍵を生成
$ openssl genrsa -out localCA.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...............+++++
..............................+++++
e is 65537 (0x010001)
- 秘密鍵と識別情報とから CSR (certificate signing request 証明書署名要求) を生成
識別情報は以下の:
の右側のように適切な値を対話式に入力します。
$ openssl req -out localCA.csr -key localCA.key -new
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) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:John Doe
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:John Doe
Email Address []:john-doe@example.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
できあがったCSRの中身はこんなんです。
識別情報 (Subject) と秘密鍵から生成された公開鍵が入っています。
$ openssl req -text -noout -in localCA.csr
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:b1:ea:90:a6:e9:64:d6:d2:c0:67:57:9b:bd:0c:
:snip
55:63:d9:c0:d3:60:21:44:dc:21:ef:6b:59:91:41:
4e:d1
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
9c:e5:6a:02:b7:6b:df:23:ed:64:06:15:18:d2:57:d7:99:d3:
:snip
e8:99:69:f4:49:32:ae:a7:1c:b3:a8:b3:df:2b:72:28:bd:04:
56:eb:41:81
- 公開鍵が入ったCSRを秘密鍵で自己署名してCA証明書を作成
days
は証明書の有効期間で日数で指定します。
$ openssl x509 -req -days 3650 -signkey localCA.key -in localCA.csr -out localCA.crt
Signature ok
subject=C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
Getting Private key
できあがったCA証明書の中身はこんなんです。
Validity (有効期間) と Issuer が入っています。Issuer と Subject とが同じなので自己署名なのがわかります。
$ openssl x509 -text -noout -in localCA.crt
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
2d:e8:64:7a:50:23:62:58:90:9a:25:dc:97:05:9e:1a:1f:46:4f:76
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
Validity
Not Before: Jul 23 04:17:48 2021 GMT
Not After : Jul 21 04:17:48 2031 GMT
Subject: C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:b1:ea:90:a6:e9:64:d6:d2:c0:67:57:9b:bd:0c:
:snip
55:63:d9:c0:d3:60:21:44:dc:21:ef:6b:59:91:41:
4e:d1
Exponent: 65537 (0x10001)
Signature Algorithm: sha256WithRSAEncryption
85:5b:a7:b0:2d:4f:47:13:db:9f:6d:9d:40:b1:6b:fa:e9:76:
:snip
01:ce:05:2d:f0:45:d6:7b:17:ba:b3:77:96:ac:56:1e:6b:f7:
c8:e9:ee:48
2. サーバー用の秘密鍵を生成し、公開鍵を 1. で作ったCA証明書で署名してサーバー証明書を作成する
- サーバー用の秘密鍵を生成
$ openssl genrsa -out localhost.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...+++++
..................................................................................+++++
e is 65537 (0x010001)
- 秘密鍵と識別情報とから CSR (certificate signing request 証明書署名要求) を生成
Common Name
にはサーバーの FQDN (ここではlocalhost
) を入力します。
$ openssl req -out localhost.csr -key localhost.key -new
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) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:John Doe
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:john-doe@example.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
できあがったCSRの中身はこんなんです。
CN (Common Name) がさっき入力した FQDN (ここでは localhost
) になっています。
$ openssl req -text -noout -in localhost.csr
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:a0:e5:5f:04:a6:a2:fc:c1:de:37:ec:9b:76:b1:
:snip
2d:28:95:da:3e:af:ee:af:6a:f2:99:72:2d:a4:af:
d8:39
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
11:4d:90:6e:b4:89:46:f0:52:b9:3c:46:89:f2:0c:d9:0b:ad:
:snip
c5:62:13:87:70:5e:68:b5:ef:3f:15:d4:0b:07:b8:06:64:6e:
c2:e9:c7:64
- 証明書に付加する SAN (Subject Alternative Name サブジェクト代替名) を入れたテキストファイルを作る
CN 以外のアドレスでも、SAN に指定してあれば証明書が有効になります。IPアドレスの他、コンテナで実行する場合は compose のservices:
名なども指定しておくと便利です。
CN と同じlocalhost
まで SAN に指定しているのは以下が理由です。
Chromeがコモンネームの設定を非推奨化
$ echo 'subjectAltName = DNS:localhost, DNS:localhost.localdomain, IP:127.0.0.1, DNS:app, DNS:app.localdomain' > localhost.csx
- CSRにSAN情報も付加し、CA秘密鍵とCA証明書とで署名してサーバー証明書を作成
さっきの自己署名とはオプションがだいぶ違います。
$ openssl x509 -req -days 1825 -CA localCA.crt -CAkey localCA.key -CAcreateserial -in localhost.csr -extfile localhost.csx -out localhost.crt
Signature ok
subject=C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
Getting CA Private Key
できあがったサーバー証明書の中身はこんなんです。
Issuer には CA証明書の Subject が入っています。
Subject Alternative Name も入っています。
$ openssl x509 -text -noout -in localhost.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
5f:90:f8:bf:5f:cb:e2:98:d6:f1:9a:24:af:de:15:02:aa:cc:56:9f
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
Validity
Not Before: Jul 23 05:29:48 2021 GMT
Not After : Jul 22 05:29:48 2026 GMT
Subject: C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:a0:e5:5f:04:a6:a2:fc:c1:de:37:ec:9b:76:b1:
:snip
2d:28:95:da:3e:af:ee:af:6a:f2:99:72:2d:a4:af:
d8:39
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:localhost, DNS:localhost.localdomain, IP Address:127.0.0.1, DNS:app, DNS:app.localdomain
Signature Algorithm: sha256WithRSAEncryption
9d:e0:c0:12:33:0e:f6:58:ae:84:45:c6:64:0b:09:a0:4e:72:
:snip
ac:e0:d3:a5:f0:34:cb:29:f2:8a:00:04:80:82:bd:53:c7:5b:
88:bc:22:3b
3. サーバーの秘密鍵と証明書をサーバーに、CA証明書をクライアントにインストールする
環境によってまちまちなので、ものすごく雑に書きます。
- サーバーに秘密鍵とサーバー証明書とをインストール
例えば Nginx だとこんな感じですね。
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/ssl/localhost.crt;
ssl_certificate_key /etc/nginx/ssl/localhost.key;
...
}
- CA証明書をクライアントにインストールする
例えば、curl なら--cacert
コマンドラインオプションで、git ならばgit config http.sslCAInfo
で、指定すればよいです。
システムに入れてしまうなら、- Ubuntu の場合:
[1]/usr/share/ca-certificates/
の下に適当なディレクトリを掘って/usr/local/share/ca-certificates/
に入れて、sudo update-ca-certificates
を実行。 - CentOS の場合:
/usr/share/pki/ca-trust-source/anchors/
に入れてsudo update-ca-trust
を実行。 - Windows の場合:
certlm.msc
を起動して「信頼されたルート証明機関」へインポート。
- Ubuntu の場合:
4. クライアントからサーバーにアクセスする
openssl s_client
で試してみましょう。
(CA証明書をシステムに入れていない場合は -CAfile /path/to/localCA.crt
も付けて。)
こんな感じに返ってきたら成功です。
$ openssl s_client -connect localhost:443 --servername localhost
CONNECTED(00000003)
depth=1 C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
verify return:1
depth=0 C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
verify return:1
---
Certificate chain
0 s:C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
i:C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIUX5D4v1/L4pjW8Zokr94VAqrMVp8wDQYJKoZIhvcNAQEL
:snip
Pi8g4Z6g3jR8175SfzCJpWudTmOMHuBJgb1O0vwOrpdQO0oaq3IcPBnMVUmcIpxI
zXKynuOuDsis4NOl8DTLKfKKAASAgr1Tx1uIvCI7
-----END CERTIFICATE-----
subject=C = jp, ST = Tokyo, O = John Doe, CN = localhost, emailAddress = john-doe@example.com
issuer=C = jp, ST = Tokyo, O = John Doe, CN = John Doe, emailAddress = john-doe@example.com
:snip
雑なまとめ
作るたびに毎回忘れてて調べ直してたんですが、こうやって書くと覚えますねー。(なんだこのまとめは。)
-
20.04あたりで変わった?っぽくて反映されなかった。要確認 ↩︎
Discussion