自己署名か CA 署名かは、公開鍵と名前の結び付きを誰が保証するのかという問題です。自己署名証明書はその証明書自身の鍵で署名されているため、証明できるのは保有者がその鍵を持っていることだけです。クライアントがこれを信頼するのは、まさにその証明書を信頼するよう設定されている場合に限られます。CA 署名の証明書は、クライアントがすでにルートを信頼している認証局によって署名されています。インターネット向けのパブリック CA か、自分で運用して配布するプライベート CA のどちらかです。
比較表
| 自己署名 | プライベート CA | パブリック CA | |
|---|---|---|---|
| リーフ証明書に署名するもの | リーフ証明書自身の鍵 | 自分のルート CA または中間 CA | ブラウザーと OS のルートストアに含まれる CA |
| クライアントが信頼するようになる方法 | 各クライアントがこの証明書そのものをピンニングまたはインストール | ルートを 1 つインストール(MDM、構成管理、コンテナーイメージ) | デフォルトで信頼済み |
| リーフ証明書のローテーション | すべてのクライアントの更新が必要 | クライアントからは透過的 | クライアントからは透過的 |
| 失効 | なし。すべてのクライアントから削除 | 独自の CRL、OCSP、または短い有効期間 | CA が公開する CRL |
| 記載できる名前 | 任意 | ルートの制約の範囲内で任意 | 公開ドメイン名とパブリック IP アドレスのみ |
| 有効期間の上限 | ブラウザーによる上限はなし。Apple のプラットフォームでは、2019年7月以降に発行された TLS 証明書を 825 日に制限 | Apple のプラットフォームでは同じく 825 日が上限 | 現在は 200 日、2029年3月からは 47 日 |
| Certificate Transparency | 記録されない | 記録されない | すべてのホスト名を含めて公開ログに記録される |
| コストと手間 | 初期コストはなし。ローテーションのたびに負担大 | CA の運用とその鍵の保護 | ACME なら無料。DNS または HTTP による検証が必要 |
| 代表的なツール | openssl req -x509、ローカル開発には mkcert | step-ca、HashiCorp Vault PKI、クラウドのプライベート CA、cert-manager | Let's Encrypt などの ACME 対応 CA |
クライアントが証明書を信頼するかどうかを決める仕組み
パス検証(RFC 5280 §6)は、サーバーの証明書からクライアントのルートストアにあるトラストアンカーまでチェーンを構築し、その過程で各署名、有効期間、基本制約、鍵用途、名前制約を確認します。自己署名証明書は発行者とサブジェクトが同じで、自身の鍵で署名されているため、構築できるチェーンはその証明書自体だけです。検証に成功するのは、その証明書自体がクライアントにとってトラストアンカーである場合に限られます。自己署名証明書を「ピンニングする」あるいは「インストールする」とは、まさにこのことを意味します。
CA 署名のリーフ証明書は中間 CAによって署名され、その中間 CA はさらにルート CAへとチェーンでつながります。クライアントはリーフ証明書を事前に知っておく必要がなく、必要なのはルートだけです。この間接性があるからこそ、ローテーション、失効、自動化が可能になります。
次のスクリプトを OpenSSL 3.6.5 で実行すると、自己署名証明書、.internal 配下の名前に制限されたプライベートルート、そのルートで署名された 2 つのリーフ証明書が作成されます。
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
P256="-newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc"
# 1. Self-signed server certificate
openssl req -x509 $P256 -keyout self.key -out self.pem -days 30 \
-subj "/CN=dev.internal" \
-addext "subjectAltName=DNS:dev.internal,IP:127.0.0.1" \
-addext "basicConstraints=critical,CA:FALSE" 2>/dev/null
openssl verify self.pem
openssl verify -CAfile self.pem self.pem
# 2. Private root CA limited to .internal names
openssl req -x509 $P256 -keyout ca.key -out ca.pem -days 3650 \
-subj "/CN=Example Internal Root" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "nameConstraints=critical,permitted;DNS:.internal" 2>/dev/null
# 3. Two server certificates signed by that CA
for name in api.internal www.example.com; do
openssl req -x509 $P256 -CA ca.pem -CAkey ca.key -keyout "$name.key" \
-out "$name.pem" -days 90 -subj "/CN=$name" \
-addext "subjectAltName=DNS:$name" \
-addext "basicConstraints=critical,CA:FALSE" \
-addext "extendedKeyUsage=serverAuth" 2>/dev/null
done
openssl verify api.internal.pem
openssl verify -CAfile ca.pem api.internal.pem
openssl verify -CAfile ca.pem www.example.com.pem
cd / && rm -rf "$dir"
CN=dev.internal
error 18 at 0 depth lookup: self-signed certificate
error self.pem: verification failed
self.pem: OK
CN=api.internal
error 20 at 0 depth lookup: unable to get local issuer certificate
error api.internal.pem: verification failed
api.internal.pem: OK
CN=www.example.com
error 47 at 0 depth lookup: permitted subtree violation
error www.example.com.pem: verification failed
自己署名証明書の検証に成功するのは、その証明書自体をトラストアンカーとして渡した場合だけです。プライベート CA のリーフ証明書はルートを信頼すれば検証に成功し、ルートの名前制約によって、このルートは www.example.com を保証できません。これにより、CA の鍵が万一悪用されても被害が限定されます。なお、openssl req -x509 は設定ファイルにより、デフォルトで証明書を CA としてマークします。CA:FALSE を明示することで、自己署名のサーバー証明書が発行者として使われるのを防いでいます。
信頼していない証明書に対するクライアントのエラー
トラストストアに何も追加しない状態で、それぞれの証明書を提示するローカルサーバーに 5 つのクライアントから接続しました(2026年10月20日、macOS)。
| クライアント | 自己署名証明書 | 信頼されていないプライベート CA のリーフ証明書 |
|---|---|---|
| Node.js 26.10 | DEPTH_ZERO_SELF_SIGNED_CERT | UNABLE_TO_VERIFY_LEAF_SIGNATURE |
| Python 3.14(OpenSSL 3.6.5) | certificate verify failed: self-signed certificate | certificate verify failed: unable to get local issuer certificate |
| Go 1.25(macOS の検証機能) | x509: certificate signed by unknown authority | x509: “localhost” certificate is not trusted |
| curl 8.7.1(LibreSSL) | (60) SSL certificate problem: self signed certificate | (60) SSL certificate problem: unable to get local issuer certificate |
OpenSSL 3.6.5 s_client | Verify return code: 18 (self-signed certificate) | Verify return code: 20 (unable to get local issuer certificate) |
ブラウザーには NET::ERR_CERT_AUTHORITY_INVALID(Chrome)または MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT(Firefox)が表示されます。検証を無効にすること(NODE_TLS_REJECT_UNAUTHORIZED=0、verify=False、InsecureSkipVerify、curl -k)は決して解決策ではありません。そのプロセスが行うすべての接続から認証が失われてしまいます。代わりに、信頼すべき証明書またはルートをクライアントに渡すか(Node.js では ca:、Python では cafile=、Go ではカスタムの RootCAs プール、curl では --cacert)、ルートを OS の証明書ストアにインストールしてください。Subject Alternative Name 拡張に DNS 名がない証明書は、別の理由で失敗します。Chrome はバージョン 58(2017年)でコモンネームとの照合をやめ、Apple のプラットフォームも iOS 13 と macOS 10.15 以降で照合しなくなりました。
中間の選択肢:プライベート CA
プライベート CA は、自己署名の主な利点(外部の関係者が不要、任意の内部名を使える、公開ログに記録されない)を保ちながら、その主なコスト(ローテーションのたびに信頼を配布し直すこと)をなくします。実践上のルールは次のとおりです。
- ルートはオフラインで保管し、中間 CA から発行します。中間 CA が危殆化しても、すべてのクライアントに手を加えずに置き換えられます。
- デモと同様に名前を内部ゾーンに制約し、
pathlenと鍵用途も最小限に絞ります。 - リーフ証明書の発行を自動化する:step-ca、Vault PKI、cert-manager は ACME または独自の API で発行できるため、リーフ証明書の有効期間を数時間〜数日にでき、失効は不要になります。
- それでもクライアントのルールは守る:Apple のプラットフォームでは、2019年7月1日以降に発行された TLS 証明書に対し、SAN に DNS 名があること、
serverAuthの拡張鍵用途、825 日以下の有効期間が求められ、これはプライベートルートにも適用されます。398 日の上限が適用されるのは、公的に信頼されたルートだけです。 - ルートの配布を計画する:OS のストア、Java のキーストア、コンテナーイメージ、独自のストアを持つブラウザー(Firefox)、独自のバンドルを同梱するソフトウェア(Python の
requestsが使うcertifiパッケージや、--use-system-caを付けて起動しない限り組み込みの Mozilla バンドルを使う Node.js)のそれぞれにルートが必要です。
パブリック CA では対応できない場合
パブリック CA は、内部名や予約済み IP アドレスを含む証明書を発行することが認められていません(Baseline Requirements §4.2.2)。そのため、printer.local、シングルラベルのホスト名、10.0.0.5、あるいは ICANN が 2024年7月29日にプライベート用途として恒久的に予約した .internal 配下の名前は、プライベート CA か自己署名証明書でしか扱えません。公開の名前を持つ内部ホスト(たとえば db.corp.example.com)は、インターネットから到達できなくても DNS-01 チャレンジで公開証明書を取得できます。ただしその場合、すべての名前が公開の Certificate Transparency ログに記録され、ログを検索する誰にでも内部のトポロジーが明らかになります。
それぞれの用途
- 公開 Web サイト、API、メールサーバー:ACME でパブリック CA を使います。
- 内部サービス、サービスメッシュ、ワークロード間の mTLS:短期のリーフ証明書を発行するプライベート CA を使います。
- ローカル開発:ツールごとに異なる形で拒否される自己署名証明書ではなく、自分のトラストストアにルートをインストールする
mkcertのようなローカル開発用 CA を使います。 - 初回起動時のデバイスとアプライアンス:ユーザーまたはプロビジョニングシステムがフィンガープリントを一度だけ検証する自己署名証明書を使い、その後プライベート CA に登録します。
- テストフィクスチャ:テスト自体が生成した自己署名証明書を使い、テスト対象のクライアントに明示的に渡します。
- mTLS によるパートナー連携や B2B 連携:その関係専用のプライベート CA を使います。汎用の内部ルートは決して使いません。
移行時の注意点
- 使用中の自己署名証明書を棚卸しする:内部のエンドポイントをスキャンし、クライアントがピンニングしている証明書を集めます。証明書デコーダーで、発行者、SAN、有効期限を確認できます。
- プライベート CA を立ち上げる:オフラインのルートと発行用の中間 CA で構成し、内部ドメインに制約します。証明書署名要求は CSR 生成ツールで生成できます。
- ルートを先に配布する:サーバーの切り替えはルートの配布が済んでから始め、サービスごとにリーフ証明書を置き換えます。証明書チェーンビルダーで、各サーバーが中間証明書を送信していることを確認できます。
- 安全でない回避策を取り除く:クライアントがルートを信頼するようになったら回避策(
-k、verify=False、InsecureSkipVerify)を削除し、再び持ち込まれないようにコードベースを検索します。 - 公開向けの名前をパブリック CA に移す:ACME を使います。ピンニングを続ける場合は、証明書ではなく鍵を対象にします。SPKI ピン生成ツールは、同じ鍵で更新しても有効なままのピンを計算します。