未来を形作るテクノロジーの深掘り記事。

eMudhraの脆弱なCA鍵:PKIの常識を5つ覆す

信頼されたCAが、数ミリ秒で解読できる秘密鍵を配布していました。eMudhraの事件を題材に、PKI・鍵生成・監査に関する5つの思い込みを検証します。

小さなおもちゃの南京錠だけで守られた巨大な金庫の扉
17か月にわたるCAのセキュリティは、わずか8ビットのエントロピーに支えられていたのです。

eMudhraのCA秘密鍵のひとつは「4」という数値です。384ビットの数字で先頭が4、という話ではありません。文字通り4で、そこに47バイトのゼロを詰めて、それらしく見せているだけです。CAの鍵生成でこんな失敗が起きるなんて、不可能だと言っていたタイプのミスです。それでも監査を通過し、Sectigoによるクロス署名も受け、本番環境に近い基盤で17か月も使われ続けました。誰かが一番単純な方法、つまり1バイトの総当たりを試すまでは。この事件によって完全に崩れるPKIの思い込みを5つ紹介します。

思い込み1:「構造的に正しい証明書は安全な証明書である」

パイプラインの自動チェックは、どれもこの証明書は問題ないと判定していました。そして、狭い意味では確かに正しかったのです。d = 4に対応する公開鍵は、P-384曲線上の有効な点です。ASN.1のエンコードもパースでき、署名も検証でき、証明書チェーンも組み立てられます。リンターも、構造バリデータも、証明書を読んで整形式であることを確認するコンプライアンスチェックリストも、すべて素通りさせます。整形式なのだから、当然です。

ここが罠です。構造のバリデーションが答えるのは「この証明書は正しい形式か?」という問いだけです。「中身の鍵は正しく生成されたか?」には答えません。楕円曲線の鍵では、この2つはほぼ完全に独立した性質です。秘密鍵は単なるスカラーdで、公開鍵はd × Gです。ここでGは曲線の生成点です。安全性の議論は、公開点からdを求めるには離散対数問題を解く必要がある、という前提に基づいています。ただし、それはdが本当に384ビットの空間から選ばれていた場合だけです。dを8ビットの空間から選んでいたら、「不可能な」問題は高々255回のループで終わってしまいます。

生成された証明書を見ても、この問題を示す手がかりは何もありません。d = 4から導かれた公開点は、正しく生成された鍵から導かれたものと同じくらいランダムに見えます。目で見て判断することはできず、リンターにかけても分かりません。分かる方法は、小さな値や既知の弱いパターンを試すことだけです。実際にそれが起きました。報告者はGoogleのparanoid_cryptoテストスイートを、そのまま既定の設定でCommon CA Databaseのコーパスに対して実行し、ヒットしたのです。ノートPCでミリ秒で終わるチェックで、実在のブラウザの信頼を支えている鍵が破られていました。

最初の教訓はこうです。有効であることは必要条件ですが、十分条件とはほど遠いのです。PKIのテストが「証明書がパースでき、チェーンが組める」で止まっているなら、それは封筒をテストしているだけで、中身は見ていません。

思い込み2:「ベースライン要件と監査で鍵生成のバグは捕まる」

CAはCA/Browser Forumのベースライン要件に従い、WebTrustやETSIの基準で監査を受けています。セレモニーの文書があり、HSMの要件があり、鍵長の要件もあります。eMudhraの鍵は、紙の上では正しい長さの384ビットでした。監査の盲点は、コンプライアンスが検証するのは鍵の表現であって、それを生み出したプロセスのエントロピーではない、という点です。47バイトのゼロで埋められた鍵も、ディスク上では48バイトを占めます。

Mozillaのインシデントチケットに投稿されたeMudhraの公式説明によると、根本原因はブラックコメディのような内容です。セレモニー用スクリプトのバグによって、HSM上に弱い鍵ペアがもう一組生成されました。2組の鍵ペアは名前が似ていて、人間がCSRに使うほうを間違えて選んでしまいました。スクリプトのバグ、名前の衝突、選択ミスという3つの独立した失敗です。そして、どの統制も「この鍵は、HSM代金に見合うだけのエントロピーを本当に持っているか?」とは一度も問いかけませんでした。

これは、自社内でPKIを運用しているエンジニア全員が心配すべき話です。公開で信頼されているCAが、規制当局の目も監査のスケジュールもある中で、値が4の秘密鍵を出荷できてしまうなら、Vault PKIのバックエンドやActive Directory Certificate Servicesのテンプレートの中身はどうなっているでしょうか。対策は安上がりで、やらない理由はありません。鍵を生成したら、何かに署名する前に必ずparanoid_cryptoか、最低限でも簡単な健全性チェックにかけてください。

from cryptography.hazmat.primitives.asymmetric import ec
CURVE_ORDER_BITS = 384
def sanity_check_private_key(key: ec.EllipticCurvePrivateKey) -> None:
d = key.private_numbers().private_value
# Reject keys whose effective entropy is far below the curve size.
# A healthy P-384 key should have a bit length close to 384.
if d.bit_length() < CURVE_ORDER_BITS - 32:
raise ValueError(
f"Suspiciously small private scalar: {d.bit_length()} bits"
)
# Also reject known bad values outright.
if d < 2**16:
raise ValueError("Private scalar is trivially brute-forceable")

作業はわずか30秒です。これを実行していれば、eMudhraの8つの鍵はすべて検出できていたはずです。監査はプロセスが存在することを確認しますが、4月のどこかの火曜日にプロセスが正しく動いたかどうかは確認しません。それを確かめられるのは、出力のテストだけです。

思い込み3:「侵害されたCA鍵は封じ込められる」

通常の意味で「CA鍵が侵害された」とき、そこには盗んだ人がいます。ある時点で誰かが鍵を持ち出したのであり、影響範囲は見当がつきます。盗まれた後に、盗んだ鍵を持つ者によって発行された証明書が対象です。厄介ではありますが、範囲は限定されています。中間CAの侵害で信頼チェーンが崩れるケースについては以前にも書きましたが、失効、信頼の取り消し、再発行という定番の対応手順は、侵害の前と後がはっきりしていることを前提にしています。

今回の事件には、前と後の区別がありません。証明書が作られた時点で、その鍵は事実上公開されていたのです。証明書を見た人なら誰でも、ミリ秒で鍵を復元できたからです。限定できるような盗難イベントはありません。影響の窓は証明書の有効期間全体、つまり2025年4月以降すべてです。誰がその開いた扉をくぐったのか、もしくは誰もくぐらなかったのかを知る方法はありません。eMudhraの「中間CAはエンドエンティティ証明書を一枚も発行していない」という声明は、Certificate Transparencyログによっても裏付けられており、CAが実際に何をしたかという点では確かに安心材料です。ただし、d=4を持つ誰かがネットワーク経路上で機会を捉えて何をできたかについては、何も語っていません。

さらに悪いことに、対応そのものを完全に行き渡らせることもできません。ChromeとFirefoxは、CRLSetとOneCRLを通じて数時間以内に中間CAをブロックしました。どちらもCTログへの記録を必須としているため、偽造されたTLS証明書はどのみちそれらのブラウザでは検証を通らなかったはずです。それは良いことです。しかし、その防御はブラウザの境界で終わっています。そこで次の思い込みに話を移しましょう。

思い込み4:「ブラウザが安全なら、エコシステム全体も安全だ」

暗闇の中で、数百台の露出した接続機器に囲まれた、光り輝く装甲ブラウザのウィンドウ
ブラウザは数時間でパッチを当てられましたが、TLSエコシステムのそれ以外はいまだに無防備です。

ブラウザはTLSエコシステムの中では最も恵まれたケースで、運用の観点で重要なTLSクライアントのごく一部にすぎません。自分たちのインフラでHTTPS接続を実際に行っているのは何か考えてみてください。curl、Pythonのrequests、Node、Goのバイナリ、Javaのサービス、git、パッケージマネージャー、クラウドSDKなどです。これらのクライアントには3つの特徴があり、それが悪い方向に重なります。ディスク上のCAバンドルを信頼すること、既定では失効確認を行わないこと、そしてCertificate Transparencyを確認しないことです。Chromeのユーザーを守ってきた3つの防御はどれも、ここにはありません。

Scott Helmeは、これを具体的に示しました。弱いeMudhraの中間CAの一つにチェーンする、api.stripe.com向けの偽造証明書を用意したところ、既定のcurlはSSL certificate verify okと報告し、Pythonはハンドシェイクを完了し、Nodeは接続が認証済みと報告し、Windows CryptoAPIもそのチェーンを受け入れました。テストしたすべてのLinuxとmacOSのマシンでそうなったのです。どのホスト名についても偽造証明書が通ってしまい、しかもそれはデプロイスクリプトやCIランナーが前提としている既定のツールで起きます。これはブラウザの問題ではなく、みなさんのインフラの問題です。

露出の範囲は、多くの人が想像するよりも深刻です。

  • curl | shのインストーラーと自己更新ツール。rustup、nvm、Homebrewのインストーラー、数えきれないほどの「install.sh」ページ。ここで偽造証明書が使われると、多くの場合rootとして攻撃者のコードを実行することになります。
  • CI/CDパイプライン。ランナーはクラウドの認証情報や署名鍵を持ったまま依存関係を取得し、成果物をプッシュします。しかも古いベースイメージを使っていることが多いのです。
  • サーバー間の通信。決済のWebhook、OAuthのトークン交換、ヘッダーにシークレットを載せたクラウドSDKの呼び出しなど。
  • IoTと組み込み機器。ファームウェアが凍結されており、CAバンドルは証明書の期限切れ、つまり2039年7月まで変わりません。
  • Windowsのコード署名。弱い8つの鍵のうち2つはコード署名用CAのものでした。それらで署名されたマルウェアは、正規の発行元によるソフトウェアのように見えてしまいます。

実行できる対策は地味ですが、正しいものです。CAバンドルを同梱しているものはすべて更新してください。OSのca-certificatesパッケージ、certifi、Node、Homebrewなどです。ベースイメージが失効を取り込んだら、コンテナイメージも再ビルドしてください。レイヤーは古い状態を覚えているからです。一方、どうしても更新できない機器は対策の打ちようがありません。あのスマートテレビは、この先あと15年ほど、値が4の鍵を信頼し続けるでしょう。IoT機器をネットワークで分離すべき理由が、もう一つ増えたわけです。

思い込み5:「誰かがすでにチェックしているはずだ」

この事件で最もさりげなく痛烈な一文は、報告者の言葉です。発見のきっかけは、サブスクライバー証明書に進む前に、CCADBのコーパスに対してparanoid_cryptoを手早いコーパステストとして実行したことでした。まさに手の届く低い果実です。報告者自身の言葉はこうです。「私の時間の大半は、誰かがすでに済ませているはずだと全員が思い込んでいた基本事項の確認に費やされました。セキュリティの現場では、これは決してやってはいけない重大なミスです。」

もう一度読んでください。公開で信頼されたCAの秘密鍵、つまりその認証局の王冠の宝石ともいえる8つの鍵は、Sectigoの AAA Certificate Servicesルートにクロス署名されたうえで、17か月間にわたって総当たりで破れる状態にありました。それが見つかったのは、監査人でも、ルートプログラムでも、CA自身でもなく、オープンソースのツールを既定のまま動かした一人の人間によってでした。エコシステムは全体として基本は押さえられていると思い込んでいました。しかし、基本を押さえた人は誰もいなかったのです。

これはセキュリティインフラ全般に見られるパターンで、だからこそ私は壊滅的な事態に耐える防御的エンジニアリングについて繰り返し書いているのです。正しい動作を前提とするシステムではなく、大惨事に抵抗するシステムを作れ、という話です。今回の問題を捕まえられたはずのチェックは、拍子抜けするほど安上がりです。

  1. 鍵の生成時と、CSRに署名する前のすべてのCSRに対して、paranoid_cryptoまたは同等の弱い鍵のチェックを実行する。
  2. 秘密スカラーのビット長を実測してエントロピーを検証し、実効エントロピーがありえないほど低いものは拒否する。上のコードサンプルのように。
  3. 設定だけでなくセレモニーもテストする。鍵セレモニーをドライランして、生成された実際の成果物を確認し、どの名前付きHSMオブジェクトがCSRに入ったのかまで検査する。
  4. 発行した証明書を継続的にスキャンする。テンプレートだけでなく証明書そのものをチェックする。証明書こそが真実であり、それより上流にあるものは意図にすぎない。

証明書こそが真実です。スクリプト、セレモニー、監査などそれより上流にあるものは、すべて意図にすぎません。

8ビットあれば十分だった

原因がばかばかしい大事故には、どこか妙に安心させられるところがあります。スクリプトのバグ、HSM上の名前の似た2つの鍵、そして間違えて選んだ人間。国家レベルの攻撃も新しい暗号解読法もなく、17か月の導火線がついたドタバタ喜劇のようなものです。しかし、そのばかばかしさこそが警告です。公開で信頼されたCAで最も弱い環がわずか1バイトになりえるなら、PKIの強度は曲線の選択やHSMの予算では決まりません。パイプラインのどこかで、誰かが実際に出力をテストしたかどうかで決まるのです。

ブラウザベンダーは数時間以内に自分たちの役割を果たしました。残りは私たちの責任です。自社の鍵生成を監査し、安価なチェックを実行し、手の届くすべてのCAバンドルを更新し、手の届かない機器はネットワークから分離してください。そして、誰かがすでに基本を確認しているはずだという思い込みはやめてください。セキュリティの現場では、それが最も高くつく思い込みなのです。