はじめに
この記事は、おそらく多くの人が既に知っているであろう、ごく簡単な内容です。
ほぼポエムなので、逆に分かりにくくなっている説もありますが、気軽に読んでください。
こんなコード見たこと、または、書いたことありますか?
3つの言語の例を書きますが、他の言語やライブラリなどでも同様の方法があるはずです。
(もしなかったら、なんていい環境なんだろうか!!)
中身は、ざっと斜め見する程度で大丈夫です。
絶対に手を染めてはいけない闇魔術なので、邪悪な例として捉えてください!!
- Python + requestモジュール
# え?
res = requests.get('https://darkside.example.com/', verify=False)
おまけでこんなの付いてることあります。
# エエェェ??Σ(*゚□゚Σ
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
- C#
// え?
private static bool TrustAnyway(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
とやった上で、こうとか。
// エエェェ??Σ(*゚□゚Σ
System.Net.ServicePointManager.ServerCertificateValidationCallback = TrustAnyway;
- Java
// え?
public class TrustAnyway implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
とやった上で、こうとか。
// エエェェ??Σ(*゚□゚Σ
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new TrustAnyway() }, new SecureRandom());
慈悲の心か?
上のコードはすべて、HTTPSやTLSの暗号化された通信の初期段階で、
サーバー側の証明書の検証を無効にする、
もしくは、どのような通信相手の証明書も信頼するという実装です。
本来、検証をカスタマイズするような機能ですが、
検証をしないという実装にカスタマイズしている危険な闇魔術です。
誰でも信頼する慈悲深い実装にも見えますが、果たしてそうでしょうか?
サーバー星のダニエル君を信頼すべきか?
TLSの細かい動作の説明が目的ではないので、全然正しくない説明をします。
厳密には全然違うので、TLS/HTTPSなどの詳細が知りたい場合は、他の解説記事に譲ります。
この例えでは、いろんな部分が表現しきれていません。。。
あくまでも雰囲気だけです。
あなたは、異星(サーバ星)の外交官(仮にダニエル君と呼びましょう)と
秘密の情報をやりとりすることになりました。
ダニエル君は、身分証明書のようなものを見せてくれて、
「よかったら、この超強力な暗号機を使って、暗号化した上で秘密の情報をお互い交換しよう。
絶対に他の人に盗み見られることはなくて、君と僕しか暗号解読できないよ。」と言いました。
身分証明書には、特殊な塗料を使った刻印があって、
正式な審査を経て発行された身分証明書には、限られた認定製造元の塗料で刻印されています。
塗料の製造元が異なると同じ成分の塗料を作ることはできません。
特殊な光を当てることで、どの製造元の塗料なのかを見分けることができます。
反応する光は、塗料ごとにすべて異なります。
塗料は誰でも(自分でも)作ることもできますが、反応する光は、他の塗料とそれぞれが異なります。
また、塗料は経年劣化して、一定期間で光に反応しなくなるので、
身分証明書は定期的に発行する必要があります(正式な審査を経れば認定製造元の塗料で刻印される)。
身分証明書の刻印に、認定製造元の塗料に反応する特殊な光を当てて確認していけば、
認定製造元の塗料が塗られているかどうか確認することができるので、
正式な審査を経て発行された身分証明書かどうかが確認できます。
さて、前述のダニエル君は、ダニエルだと名乗り、なにやら身分証明書のようなものを持っていて、
しかも超強力な暗号機を使って暗号化するので、
あなたは、面倒な光による塗料の確認は省略して、
暗号機を使った秘密の情報のやりとりを行うことにしました。
お、お前、本当にダニエルか?
確認してないなら分かりません。
塗料を確認していれば、正式な審査を経て発行された身分証明書かどうか一定の確認ができます。
暗号機がどれだけ強力でも、あなたとダニエルは、やり取り内容を復号できます。
ダニエル君が、あなたが思っているダニエル君ではなくて、
悪徳星のエージェントだったら、あなたの秘密の情報だけ入手して、さよならされることでしょう。
相手が「ダニエルだよ」って言ってるし、まあ知っている相手だったら、
確認なんてしなくても大丈夫でしょうか?
悪徳星のエージェントが、ダニエル君を装っている可能性も考えましょう!!
信頼できる相手だから大丈夫だなんて、なんてお優しい。
とはなりません。
信頼できると言っているのは、あなたの感性であって、検証されたものではありません。
検証していないなら、信頼に値するかの確認ができません。
しかも厄介なのは、ほぼ最初に聞いてきます。
「信頼しているなら、あなたの認証情報を教えて」と。
(例えば、どこかに入るためのIDとパスワードとか。)
相手が偽物だった場合、認証情報渡した時点でジ・エンドです。
2度と連絡は来ないでしょう。
その代わり、あなたの認証情報使って、
あなたに代わって悪の限りを尽くしてくれるかもしれません。
ちなみに、最初の方に記載した以下のコードは、
「さすがにヤバいでしょ」とアドバイスをくれる友人を撃ち殺しているようなものです。
絶対にやめましょう!
# エエェェ??Σ(*゚□゚Σ
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
さらに、場合によっては、本物のダニエル君であっても、悪の道に落ちている場合もあります。
悪の道に落ちた人リストの確認も重要になります。
(この部分は、TLS関連でいうと、証明書のRevoke(失効)、CRLなどが関連キーワードです。
失効理由はいろいろあって、悪の道に落ちたことだけが理由になるとは限りません。)
ここまでの手順で、実際にはダニエル本人かどうかの確認は行っていません。
「厳格なプロセスと手順を踏んで発行された有効な証明書かどうかの検証」に重きを置いています。
その厳格なプロセスを通過した有効な証明書を持っていると確認されたダニエル君なら、
信用しましょうといった感じです。
ダニエル本人たる十分な証拠があるかどうかの検証には別途確認が必要ですが、
その確認までやるというのは、デメリットの方が大きくなる場合もあるかもしれません。
一時期、TLS/SSLの公開証明書のピン留め(Pinning)しようぜという時代もありましたが、
デメリットの方が多いんじゃないかという風潮になって、今ではピン留めは、お勧めせず、
証明書の有効性の検証をより高いレベルにしていこうという意見が多いと思います。
Certificate Transparency(CT)などのキーワードも是非調べてみてください。
確認できないなら拒否!
これが基本です。
https/TLSを利用する時点で、証明書の検証は必須です。
例外はありません。
あえていえば、アンチパターンの説明をするときにのみ、
絶対に手を染めるべきではない悪い実装例として見せることはあるかと思います。
証明書は、お金を払って公的な発行機関(認証局)で発行してもらうのが必須というわけではありません。
相手が信頼たる機関が認めた相手かどうかの確認、検証が必須なので、
どの証明書発行機関(認証局)が発行した(これは自分が本当に信頼するどこかかもしれません)有効な証明書か、
もしくは、どんな証明書かなどが重要です。
相手の名前(サーバでいうとホスト名など)の確認だけでは、確認、検証したことにはなりません。
TLSのサーバ証明書を適切に検証することが重要です。
自分達で証明書の発行機関(OpenSSLなど利用)を厳重に管理しているのであれば、
その認証局の公開証明書を決め打ちで信頼することもできます。
どの認証局を信頼するかは、自分達で明確に決めることもできます。
多くの場合は、OSやフレームワーク、ランタイム側で、
一般的な公的な証明書発行機関(認証局)がデフォルトで信頼されています。
より狭く、厳しく信頼範囲を制限する実装も可能です。
なぜ、多くのプログラマーが、この手の闇魔術に手を染めるか
作ったアプリが動かなかったからです。
「証明書の検証エラーが出たから、とにかく動くようにしたい」という理由に1点懸けでもいいくらいです。
とりあえず、手っ取り早く、その場をしのぎたいという場合が多いです。
最近でこそ、ちゃんと検証しようと考えている人が多くなったと思いますが、
10年前だったら、この手の闇魔術に手を染めた、染めそうになった方も多いのではないかと思います。
よくある言い訳と、その反論
証明書の検証を無効にしたり、どの証明書でも受け入れる実装をしている場合に、
よくある言い訳と、それに対する反論を列挙します。
この種の闇実装は、結構広まりやすいので、見かけるたびに、みなさんの手で防いでいきましょう!
水際対策が本当に大事です!
このような闇実装なコードを書くこと自体よりもむしろ、
これを他人が真似して広まってしまうことを問題として強く意識して欲しいです!!
広めないためにも、自分は絶対にやらないことも重視!
- 「デモアプリだから。」
そのコード見た、後輩が、あなたの真似してもいいんですか?
癖になりますよ!
- 「サンプルだから。」
それ、あなたがコントロールできない範囲まで広まりますよ!
みんなが真似してもいいんですか?
- 「コメントで、"製品とかではちゃんと検証すべき"って書いてあるから」
あなたもちゃんとやりましょう!
今やりましょ!
システム側でデフォルトで信頼されている発行機関以外の証明書の検証の例
いくつかの例になります。
より厳しい検証を実装する方法があったり、
他の言語やフレームワークでは、その言語、フレームワークの方法があったりします。
また、証明書失効リスト(CRL)の処理は別途考慮が必要な場合があります。
ここで、「アプリが動作しないから、とりあえず、この証明書信頼しちゃえ」
というようなことをやってしまうと、
結局、上で説明した闇実装と大して変わりはないことになるので、その点は注意ください。
- Python + requestモジュール
信頼する証明書発行機関(認証局)の証明書のパス指定もできます。
res = requests.get('https://darkside.example.com/', verify='/path/to/certfile')
- Java
keytool
などを利用して、信頼する証明書発行機関(認証局)の証明書をトラストストアに追加できます。
環境変数の、javax.net.ssl.trustStore
やjavax.net.ssl.trustStorePassword
で、
トラストストアのパスや、パスワードを指定できます。
- C#
Windowsの場合は、OS側に信頼する証明書発行機関(認証局)として証明書を登録するなど。
証明書の期限切れでエラーになって困っている場合
- 自分または関係者が運用しているサーバーの場合
一刻も早く、証明書を更新しましょう。
本来は期限が切れる前に対応しておくべきだったと反省しましょう。
- 自分ではどうすることもできない運営者が運営している場合
たぶん、やる気がないです。
その接続先と、さよならすることを考える時が来たのかもしれません。