社内ネットワークや開発環境で複数のサーバー証明書を使う場合、サーバーごとに自己署名の証明書を作ると、クライアントに1つずつ信頼設定が必要になる。自分用の認証局(CA)を1つ作り、CAの証明書だけをクライアントに信頼させれば、CAが署名したサーバー証明書をまとめて信頼できる。
流れは次のとおり。
graph LR A[CAの秘密鍵と証明書を作る] --> B[サーバーの秘密鍵とCSRを作る] B --> C[CAの秘密鍵でCSRに署名する] C --> D[クライアントにCA証明書を信頼させる]
実行例はUbuntu 24.04のOpenSSL 3.0.13のもの。
CAの秘密鍵と証明書を作る
CAの証明書は、自己署名の証明書として作る。basicConstraintsをCA:TRUE、keyUsageをkeyCertSignにする。
$ openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
-keyout ca.key -out ca.crt -days 3650 \
-subj "/O=Example Inc./CN=Example Private CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
$ openssl x509 -in ca.crt -noout -subject -issuer -ext basicConstraints,keyUsage
subject=O = Example Inc., CN = Example Private CA
issuer=O = Example Inc., CN = Example Private CA
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
ca.keyはCAの秘密鍵である。漏れると、誰でもCAとして証明書を発行できる。パーミッションを600にして、CAの用途以外では使わない。
自己署名の証明書の作り方は別の記事で説明している。
参考: 【openssl】自己署名の証明書を作成する (req -x509)
サーバーの秘密鍵とCSRを作る
サーバー側では、秘密鍵とCSRを作る。SANはCSRに含めておく。
$ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
-keyout server.key -out server.csr \
-subj "/CN=server.example.test" \
-addext "subjectAltName=DNS:server.example.test,DNS:localhost,IP:127.0.0.1"
参考: 【openssl】CSRを作成する (req -new)
CSRに署名する
CAの証明書と秘密鍵を指定して、openssl x509 -reqでCSRに署名する。
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 -extfile ext.cnf
ext.cnfは、証明書に付ける拡張を書いた設定ファイルである。
subjectAltName = DNS:server.example.test,DNS:localhost,IP:127.0.0.1
basicConstraints = CA:FALSE
keyUsage = digitalSignature
extendedKeyUsage = serverAuth
指定したオプションは次のとおり。
| オプション | 説明 |
|---|---|
-in | 署名するCSR |
-CA | CAの証明書 |
-CAkey | CAの秘密鍵 |
-CAcreateserial | シリアル番号のファイル(ca.srl)を作る |
-days | 発行する証明書の有効日数 |
-extfile | 証明書に付ける拡張を書いたファイル |
作成した証明書の内容を確認する。発行者(issuer)がCAになっている。
$ openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName,basicConstraints,extendedKeyUsage
subject=CN = server.example.test
issuer=O = Example Inc., CN = Example Private CA
notBefore=Sep 30 14:56:46 2026 GMT
notAfter=Sep 30 14:56:46 2027 GMT
X509v3 Subject Alternative Name:
DNS:server.example.test, DNS:localhost, IP Address:127.0.0.1
X509v3 Basic Constraints:
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Server Authentication
拡張を指定しないとSANが消える
-extfileを省略すると、CSRに含めたSANも含めて、拡張が1つも付かない証明書が作られる。
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out s0.crt -days 365
$ openssl x509 -in s0.crt -noout -ext subjectAltName
No extensions in certificate
CSRのSANを引き継ぐには、2つの方法がある。
1つ目は、-extfileにSANを書き直す方法である。前述の手順と同じである。
2つ目は、-copy_extensions copyを付ける方法である。CSRの拡張が証明書にコピーされる。
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out s2.crt -days 365 -copy_extensions copy
$ openssl x509 -in s2.crt -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:server.example.test, DNS:localhost, IP Address:127.0.0.1
-copy_extensions copyは、CSRに含まれる拡張をすべてコピーする。CSRの提出者がbasicConstraints=CA:TRUEなどを含めていた場合、意図しない権限を持つ証明書を発行してしまう。他人から受け取ったCSRに署名する場合は、-extfileで必要な拡張だけを指定する。
シリアル番号
証明書にはCAごとに一意のシリアル番号が必要である。-CAcreateserialを付けると、CAの証明書と同じ場所にca.srlが作られ、署名のたびに値が増える。
$ cat ca.srl
6E6E0D8E9E6290E61397AE971D88023484E934F3
2回目以降の署名では、-CAcreateserialの代わりに-CAserial ca.srlを指定してもよい。-CAcreateserialを付けたままでも、既存のca.srlを使って値が続く。
署名した証明書を検証する
CAの証明書を渡して、openssl verifyで証明書を検証する。
$ openssl verify -CAfile ca.crt server.crt
server.crt: OK
CAの証明書を渡さないと、検証に失敗する。
$ openssl verify server.crt
error 20 at 0 depth lookup: unable to get local issuer certificate
error server.crt: verification failed
用途やホスト名も合わせて検証できる。ホスト名がSANに含まれていない場合は失敗する。
$ openssl verify -CAfile ca.crt -purpose sslserver -verify_hostname server.example.test server.crt
server.crt: OK
$ openssl verify -CAfile ca.crt -verify_hostname wrong.example server.crt
error 62 at 0 depth lookup: hostname mismatch
error server.crt: verification failed
サーバーに設定して接続を確認する
サーバーには、サーバー証明書とサーバーの秘密鍵を設定する。CA証明書は、サーバーには設定せず、クライアントに信頼させる。
openssl s_serverで起動して、openssl s_clientで接続を確認できる。
$ openssl s_server -accept 4433 -cert server.crt -key server.key -www
$ echo | openssl s_client -connect localhost:4433 -CAfile ca.crt -verify_hostname localhost 2>&1 | grep -E 'Verif'
Verification: OK
Verify return code: 0 (ok)
CAの証明書を渡さない場合は、検証に失敗する。
$ echo | openssl s_client -connect localhost:4433 2>&1 | grep 'Verify return'
Verify return code: 21 (unable to verify the first certificate)
参考: 【openssl】指定したURLの証明書をコマンドで確認する
クライアントにCAを信頼させる
OSの信頼済みCAにca.crtを追加すると、そのOS上のほとんどのクライアントがCAを信頼する。Ubuntuでは、/usr/local/share/ca-certificates/に.crt拡張子で置いて、update-ca-certificatesを実行する。
$ sudo cp ca.crt /usr/local/share/ca-certificates/example-ca.crt
$ sudo update-ca-certificates
$ openssl verify server.crt
server.crt: OK
-CAfileを指定しなくても、検証に成功する。
OSへ追加せずに、個別のコマンドにだけ渡す方法もある。opensslは-CAfile、curlは--cacertで指定する。OSの信頼済みCAを変更する影響は大きいため、検証用途では個別に渡す方法を優先する。
サーバー証明書とCA証明書を1つのファイルにまとめる場合は、サーバー証明書、CA証明書の順にcatで連結する。
$ cat server.crt ca.crt > chain.pem
バージョンによる違い
OpenSSL 3.5でも、ここまでの例は同じように動く。-extfileを省略すると、拡張が付かない挙動も同じである。
macOS標準の/usr/bin/openssl(LibreSSL 3.3.6)では、以下が異なる。
-copy_extensionsに対応していない-extfileを省略すると、拡張のないバージョン1の証明書が作られる
-extfileでSANを指定する方法は、LibreSSL 3.3.6でも動く。
