x509: certificate signed by unknown authorityの原因・調査方法
概要
golangのアプリケーションから、あるサーバーに対してhttpリクエストを実行した際、エラーが発生しました。
エラーメッセージはx509: certificate signed by unknown authority
。
TLS証明書まわりの問題ですね。
せっかくなので発生原因のパターンを分けて、調査を行った方法をまとめておきます。
調査を行った環境は以下です。
- macOS Catalina バージョン10.15.7
- opensslコマンド LibreSSL 2.8.3(macプリインストール)
x509: certificate signed by unknown authority
とは
goの標準パッケージcrypto
内のエラー(UnknownAuthorityError
)に含まれるメッセージ。
go1.17.7ではこちらで実装されています。
なぜ発生する?
発生原因を3つのパターンに分けてみます。
1. 実際に不正なサーバーが不正な証明書を提示したパターン
まずは不明な認証局によって署名された証明書
なので、サーバーが提示してきたTLS証明書は不正である可能性がある、というメッセージ通りのパターン。
実際に接続しようとしているサーバーそのものが怪しい輩で、そいつが提示してくる証明書に不備があり、しっかり忠告を出してくれているということです。
真摯に受け止めましょう。無理に接続するのはやめておきましょう。
ただ、いろいろな状況を鑑みて、不正サーバーの可能性よりも、クライアントあるいはサーバー側の設定不備では???って場合は残りの2パターンを疑いましょう。
2. クライアント側による証明書検証設定の不備
クライアントはサーバー側が提示した証明書を検証してから通信を行います。
その検証の際に利用されるトラストストアの設定に不備があるパターンです。
トラストストアは、(一般的に)OSにあらかじめインストールされているルート証明書の格納庫です。
クライアントは、自身のトラストストアを使ってサーバーの証明書を検証します。
この設定不備は以下のようなケースにさらに細分できそうです。
- トラストストア内のルート証明書が古いまま更新されておらず、サーバー証明書を検証できなかった。
- トラストストアのパスを正しく指定できておらず、読み込めなかった。
- そもそもトラストストアが存在しない。ルート証明書が存在しない。
x509: certificate signed by unknown authority
を日本のGoogleで検索してみると、このパターンへの対処法を紹介している記事が多い印象があります。
ベースにしているコンテナイメージのトラストストアが古い、docker開発環境がルート証明書を使えていない、などの原因が多いようです。
goがコンテナ環境で採用されやすいというのも一因でしょうか。
このパターンの対処法はトラストストアを更新し、正確に利用されるようにすることです。個々の対処内容は他の記事が詳しいのでそちらにお任せします。
3. サーバー側による証明書設定不備
私がぶつかったのはこのパターンです。なのでこのパターンを深堀りします。
通常、サーバーは「自身の実在証明」と「通信の暗号化」のためにサーバー証明書を持っており、クライアントとの通信開始時に提示します。
クライアントはトラストストア内のルート証明書を使ってサーバー証明書の検証を行います。
この時、サーバー証明書とルート証明書の間を埋めてくれる働きをするのが中間証明書です。
サーバー証明書を中間証明書aが検証する。
↓
中間証明書aを中間証明書bが検証する。
↓
...
↓
中間証明書xをルート証明書が検証する。
というふうにサーバー証明書からルート証明書までを順番に検証していく仕組みが証明書チェーン(Certificate Chain)と呼ばれるものです。
ルート証明書まで検証の連鎖が繋がることでサーバーが提示した証明書を「信頼」することができます。
つまり中間証明書がないと、全体として検証が失敗してしまい、x509: certificate signed by unknown authority
が発生してしまうわけですね。(go標準パッケージを利用した通信の場合)
中間証明書の確認
中間証明書を用意するのはサーバーの責任範囲です。サーバーが中間証明書を含めて提示してくれているか確かめてみましょう。
まずは正常なサイトの例として、google.comに対してopenssl s_client ${host:port}
コマンドを実行してみます。
echo | openssl s_client -connect google.com:443 2> /dev/null
CONNECTED(00000005)
---
Certificate chain
0 s:/CN=*.google.com
i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIO.....
...
-----END CERTIFICATE-----
subject=/CN=*.google.com
issuer=/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
---
...
...
Certificate chain
以下にサーバー証明書 -> 中間証明書(複数の場合もあり) -> ルート証明書と列挙されています。
google.comは正常に中間証明書を含めてサーバー証明書を提示してくれているので、ルート証明書まで検証の連鎖が成功し、TLS通信が可能となっています。
次に、中間証明書を設定してくれていないサーバーを確認してみましょう。
https://incomplete-chain.badssl.com/ という、検証用のサイトがあります。
素晴らしい👏
echo | openssl s_client -connect incomplete-chain.badssl.com:443 2> /dev/null
CONNECTED(00000005)
---
Certificate chain
0 s:/C=US/ST=California/L=San Francisco/O=BadSSL Fallback. Unknown subdomain or no SNI./CN=badssl-fallback-unknown-subdomain-or-no-sni
i:/C=US/ST=California/L=San Francisco/O=BadSSL/CN=BadSSL Intermediate Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIE8DC...
...
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=San Francisco/O=BadSSL Fallback. Unknown subdomain or no SNI./CN=badssl-fallback-unknown-subdomain-or-no-sni
issuer=/C=US/ST=California/L=San Francisco/O=BadSSL/CN=BadSSL Intermediate Certificate Authority
---
...
...
google.comのときとは違い、Certificate chain
以下にはサーバー証明書が単体で表示されています。
これではルート証明書までの検証の連鎖が成功しないので、通信は失敗します。
ここまで調べるとサーバー側の設定不備である、と判断できます。
サーバーの担当者に連絡できるならば対応依頼となるでしょう。
広く公開されたサーバーで同じ状態が見つかったらレアだと思います。すぐに検知されて対応されるでしょう。
まとめ
x509: certificate signed by unknown authority
というエラーに出会ったことから、証明書検証フローの理解を整理できました。
ちなみに中間証明書のないサイト https://incomplete-chain.badssl.com/ にChrome等のブラウザでアクセスすると普通に画面が見れる(普通に通信できている)のですが、これはブラウザがAIA(Authority Information Access) Fetchingという機能を実装していて、中間証明書を補完しているからです。
いろんな仕組みがあって面白いですねぇ。
Discussion