エラーメッセージ
curl: (60) SSL: no alternative certificate subject name matches target hostname 'api.example.com'
別の表記
- curl: (60) SSL: no alternative certificate subject name matches target host name 'api.example.com'
curl 8.x は「target hostname」と出力します。curl 7.x では「target host name」でした。引用符で囲まれたホストは、URL に含まれていたものがそのまま表示されます。非常に古いバージョンの curl(7.62.0 より前)は、このチェックに終了コード 51 を使っていました。
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, hostname check in lib/vtls/openssl.c
このエラーの意味
証明書チェーンは信頼されていますが、証明書のサブジェクト代替名(SAN)拡張に含まれる DNS 名(または IP アドレス)のどれも、URL のホストと一致しません。curl は URL のホストを SAN のエントリとだけ比較し、SAN 拡張がある場合、サブジェクトのコモンネームは無視されます。末尾に引用符付きで示される名前は、curl に接続先として指定されたホストです。
発生する理由
証明書にこの名前が記載されていない
よくあるexample.com の証明書は、それぞれの名前が SAN リストに含まれていない限り、www.example.com や api.example.com には対応しません。新しいサブドメインや新しい環境(staging、eu.)には、証明書の再発行が必要です。
URL に IP アドレス、localhost、または内部のエイリアスを使っている
よくあるURL が https://10.0.0.5 や https://localhost の場合、または docker-compose のサービス名を使う場合は、正しいサーバーには到達しますが、証明書の発行対象になっていない名前を要求することになります。IP アドレスが一致するのは IP の SAN エントリだけで、DNS のエントリとは決して一致しません。
サーバーが別のサイトの証明書を返した
ときどき共有 IP では、証明書は SNI によって選ばれます。要求された名前に一致する server ブロックがない場合、サーバーはデフォルトの証明書で応答しますが、その証明書には別のサイトの名前が記載されています。
ワイルドカードに実際以上の範囲を期待している
ときどき*.example.com は api.example.com をカバーしますが、example.com と v2.api.example.com はカバーしません。ワイルドカードが表すのは、ちょうど 1 つのラベルだけです。
修正方法
- 1.
SAN のリストと URL を比較する
返される証明書の subjectAltName 拡張を表示してください。解決策は、その名前を含む証明書を使うか、記載されている名前のいずれかを使った URL にするかのどちらかです。
bash # Names the served certificate actually covers echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \ | openssl x509 -noout -subject -ext subjectAltName - 2.
使用中のすべての名前を含めて証明書を再発行する
すべてのホスト名(クライアントが IP で接続する場合は IP アドレスも)を SAN エントリとして要求してください。名前をコモンネームにだけ入れても、現行のクライアントではどれも機能しません。
bash # ACME: list every name the certificate must cover sudo certbot certonly --nginx -d example.com -d www.example.com -d api.example.com # Private CA: put all names into subjectAltName (CN is ignored for matching) openssl req -new -key server.key -out server.csr -subj "/CN=api.example.com" \ -addext "subjectAltName=DNS:api.example.com,DNS:www.example.com,IP:10.0.0.5" - 3.
名前はそのままで、アドレスだけを変える
特定のアドレス(ロードバランサーのノード、トンネル、ステージングの IP)に接続する必要がある場合、--resolve や --connect-to を使えば URL のホストが維持されるため、SNI とホスト名のチェックには引き続き証明書の名前が使われます。
bash # Keep the certificate's name in the URL and steer the connection to another address curl --resolve api.example.com:443:10.0.0.5 https://api.example.com/ curl --connect-to api.example.com:443:lb-internal.example.net:443 https://api.example.com/ - 4.
サーバー管理者:SNI のルーティングを確認する
SNI ありとなしのリクエストで証明書が異なる場合は、その名前の server ブロック(または Ingress のルール)を追加し、実際のホスト名に対してクライアントがデフォルトの証明書を受け取ることがないようにしてください。
bash # Compare what the server returns with and without SNI openssl s_client -connect 10.0.0.5:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject openssl s_client -connect 10.0.0.5:443 -noservername </dev/null 2>/dev/null | openssl x509 -noout -subject
関連するエラー
x509: certificate is valid for example.com, not api.example.comGo crypto/x509Hostname/IP does not match certificate's altnames: Host: api.example.com. is no…Node.js tls.checkServerIdentity()[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch,…Python ssl module (OpenSSL host check)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)fatal: unable to access 'https://git.example.com/team/app.git/': SSL: certifica…git over HTTPS with libcurl-gnutls (Debian, Ubuntu)